diff --git a/frontend/src/components/Map.vue b/frontend/src/components/Map.vue
index 422e9df..db2732e 100644
--- a/frontend/src/components/Map.vue
+++ b/frontend/src/components/Map.vue
@@ -32,6 +32,12 @@
           </div>
         </div>
 
+        <MapCircle v-if="selectedPoint?.accuracy != null && mapObj"
+                   :map="mapObj"
+                   :latitude="selectedPoint.latitude"
+                   :longitude="selectedPoint.longitude"
+                   :radius="selectedPoint.accuracy" />
+
         <PointInfo :point="selectedPoint"
                    :device="selectedPoint ? devicesById[selectedPoint?.deviceId] : null"
                    ref="popup"
@@ -94,7 +100,7 @@
 <script lang="ts">
 import _ from 'lodash';
 import Map from 'ol/Map';
-import Overlay from 'ol/Overlay';
+import MapCircle from './MapCircle.vue';
 import Point from 'ol/geom/Point';
 import PointInfo from './PointInfo.vue';
 import VectorLayer from 'ol/layer/Vector';
@@ -141,6 +147,7 @@ export default {
     FilterButton,
     FilterForm,
     FloatingButton,
+    MapCircle,
     MapSelectOverlay,
     PointInfo,
     Timeline,
@@ -154,7 +161,6 @@ export default {
       mapView: null as Optional<View>,
       pointToRemove: null as Optional<GPSPoint>,
       pointsLayer: null as Optional<VectorLayer>,
-      popup: null as Optional<Overlay>,
       refreshPoints: 0,
       routesLayer: null as Optional<VectorLayer>,
       selectedFeature: null as Optional<Feature>,
@@ -197,6 +203,14 @@ export default {
           return acc
         }, {})
     },
+
+    mapObj(): Map | null {
+      if (!this.map) {
+        return null
+      }
+
+      return this.map as Map
+    },
   },
 
   methods: {
diff --git a/frontend/src/components/MapCircle.vue b/frontend/src/components/MapCircle.vue
new file mode 100644
index 0000000..99d3d8d
--- /dev/null
+++ b/frontend/src/components/MapCircle.vue
@@ -0,0 +1,130 @@
+<template>
+  <div class="circle"
+       ref="circle"
+       :style="circleStyle" />
+</template>
+
+<script lang="ts">
+import Geo from '../mixins/Geo.vue';
+import Map from 'ol/Map';
+import Overlay from 'ol/Overlay';
+
+export default {
+  mixins: [Geo],
+  props: {
+    map: {
+      type: Map,
+      required: true,
+    },
+
+    latitude: {
+      type: Number,
+      required: true,
+    },
+
+    longitude: {
+      type: Number,
+      required: true,
+    },
+
+    radius: {
+      // Unit: meters
+      type: Number,
+      required: true,
+    },
+
+    color: {
+      type: String,
+      default: 'var(--color-accent)',
+    },
+
+    borderColor: {
+      type: String,
+      default: 'rgba(0, 0, 0, 0.2)',
+    },
+  },
+
+  data() {
+    return {
+      overlay: null as Overlay | null,
+    }
+  },
+
+  computed: {
+    center(): { x: number | null, y: number | null } {
+      const pixel = this.map.getPixelFromCoordinate([this.longitude, this.latitude]);
+      if (!pixel) {
+        return {
+          x: null,
+          y: null,
+        };
+      }
+
+      return {
+        x: pixel[0],
+        y: pixel[1],
+      };
+    },
+
+    circleStyle() {
+      if (!(this.center.x && this.center.y)) {
+        return {};
+      }
+
+      return {
+        width: `${this.radiusPx * 2}px`,
+        height: `${this.radiusPx * 2}px`,
+        backgroundColor: this.color,
+        border: `1px solid ${this.borderColor}`,
+      };
+    },
+
+    radiusPx() {
+      const center = this.map.getPixelFromCoordinate([this.longitude, this.latitude]);
+      const radiusLatOffset = 90 * (this.radius / this.earthRadius) * Math.cos(this.latitude * Math.PI / 180);
+      const radius = this.map.getPixelFromCoordinate([this.longitude, this.latitude + radiusLatOffset]);
+      return Math.abs(center[1] - radius[1]);
+    },
+  },
+
+  methods: {
+    bind() {
+      this.overlay = new Overlay({
+        element: this.$el,
+        positioning: 'center-center',
+      });
+
+      this.map.addOverlay(this.overlay as Overlay);
+
+      // Force show the Overlay
+      (this.overlay as Overlay).setPosition([this.longitude, this.latitude]);
+    },
+
+    unbind() {
+      if (this.overlay) {
+        this.map.removeOverlay(this.overlay as Overlay);
+      }
+    },
+  },
+
+  mounted() {
+    this.bind();
+  },
+
+  unmounted() {
+    this.unbind();
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.circle {
+  position: absolute;
+  width: 0;
+  height: 0;
+  border-radius: 50%;
+  transform: translate(-50%, -50%);
+  opacity: 0.25;
+  z-index: 1001;
+}
+</style>
diff --git a/frontend/src/components/PointInfo.vue b/frontend/src/components/PointInfo.vue
index 87f64fa..f3faedb 100644
--- a/frontend/src/components/PointInfo.vue
+++ b/frontend/src/components/PointInfo.vue
@@ -60,6 +60,12 @@
           <span>{{ point.battery }}%</span>
         </p>
 
+        <p class="accuracy" v-if="point.accuracy">
+          <font-awesome-icon icon="fas fa-ruler" />
+          <span class="title">Accuracy:</span>
+          <span class="value">{{ point.accuracy }} m</span>
+        </p>
+
         <p class="locality" v-if="point.locality">{{ point.locality }}</p>
         <p class="postal-code" v-if="point.postalCode">{{ point.postalCode }}</p>
         <p class="country" v-if="country">
@@ -353,6 +359,21 @@ export default {
     }
   }
 
+  .accuracy {
+    font-size: 0.9em;
+    font-weight: bold;
+    letter-spacing: 0.05em;
+    opacity: 0.75;
+
+    .title {
+      margin: 0 0.25em;
+    }
+
+    .value {
+      font-weight: bold;
+    }
+  }
+
   .timestamp {
     color: var(--color-heading);
     font-weight: bold;
diff --git a/frontend/src/components/Timeline.vue b/frontend/src/components/Timeline.vue
index 49cfecc..1c4dd3d 100644
--- a/frontend/src/components/Timeline.vue
+++ b/frontend/src/components/Timeline.vue
@@ -164,7 +164,7 @@ export default {
       })
     },
 
-    graphData() {
+    graphData(): { labels: Date[], datasets: any[] } {
       const datasets = []
       if (this.showMetrics.altitude) {
         datasets.push(
diff --git a/frontend/src/mixins/Geo.vue b/frontend/src/mixins/Geo.vue
index d79b556..9d9df83 100644
--- a/frontend/src/mixins/Geo.vue
+++ b/frontend/src/mixins/Geo.vue
@@ -2,9 +2,15 @@
 import GPSPoint from '../models/GPSPoint'
 
 export default {
+  data() {
+    return {
+      earthRadius: 6378e3, // metres
+    }
+  },
+
   methods: {
     latLngToDistance(p: GPSPoint, q: GPSPoint): number {
-      const R = 6371e3 // metres
+      const R = this.earthRadius // metres
       const φ1 = p.latitude * Math.PI / 180 // φ, λ in radians
       const φ2 = q.latitude * Math.PI / 180
       const Δφ = (q.latitude - p.latitude) * Math.PI / 180
diff --git a/frontend/src/models/GPSPoint.ts b/frontend/src/models/GPSPoint.ts
index 4091a8d..dc83ce1 100644
--- a/frontend/src/models/GPSPoint.ts
+++ b/frontend/src/models/GPSPoint.ts
@@ -1,64 +1,66 @@
+import type { Optional } from "./Types";
+
 class GPSPoint {
   public id: number;
   public latitude: number;
   public longitude: number;
-  public altitude: number;
+  public altitude?: Optional<number>;
   public deviceId: string;
-  public address: string;
-  public locality: string;
-  public country: string;
-  public postalCode: string;
-  public description?: string;
-  public battery?: number;
-  public speed?: number;
-  public accuracy?: number;
+  public address?: Optional<string>;
+  public locality?: Optional<string>;
+  public country?: Optional<string>;
+  public postalCode?: Optional<string>;
+  public description?: Optional<string>;
+  public battery?: Optional<number>;
+  public speed?: Optional<number>;
+  public accuracy?: Optional<number>;
   public timestamp: Date;
 
-  constructor({
-    id,
-    latitude,
-    longitude,
-    altitude,
-    deviceId,
-    address,
-    locality,
-    country,
-    postalCode,
-    description,
-    battery,
-    speed,
-    accuracy,
-    timestamp,
-  }: {
+  constructor(data: {
     id: number;
     latitude: number;
     longitude: number;
-    altitude: number;
+    altitude?: number;
     deviceId: string;
-    address: string;
-    locality: string;
-    country: string;
-    postalCode: string;
+    address?: string;
+    locality?: string;
+    country?: string;
+    postalCode?: string;
     description?: string;
     battery?: number;
     speed?: number;
     accuracy?: number;
-    timestamp: Date;
+    timestamp?: Date;
   }) {
-    this.id = id;
-    this.latitude = latitude;
-    this.longitude = longitude;
-    this.altitude = altitude;
-    this.deviceId = deviceId;
-    this.address = address;
-    this.locality = locality;
-    this.country = country;
-    this.postalCode = postalCode;
-    this.description = description;
-    this.battery = battery;
-    this.speed = speed;
-    this.accuracy = accuracy;
-    this.timestamp = timestamp;
+    this.id = data.id;
+    this.latitude = data.latitude;
+    this.longitude = data.longitude;
+    this.altitude = data.altitude;
+    this.deviceId = data.deviceId;
+    this.address = data.address;
+    this.locality = data.locality;
+    this.country = data.country;
+    this.postalCode = data.postalCode;
+    this.description = data.description;
+    this.battery = data.battery;
+    this.speed = data.speed;
+    this.accuracy = data.accuracy;
+    this.timestamp = data.timestamp || new Date();
+  }
+
+  public static fromLatLng({
+    latitude,
+    longitude,
+  }: {
+    latitude: number;
+    longitude: number;
+  }) {
+    return new GPSPoint({
+      id: 0,
+      latitude,
+      longitude,
+      deviceId: '',
+    });
   }
 }