diff --git a/frontend/src/components/Map.vue b/frontend/src/components/Map.vue
index 33acddb..1310bb4 100644
--- a/frontend/src/components/Map.vue
+++ b/frontend/src/components/Map.vue
@@ -29,6 +29,7 @@
 
         <PointInfo :point="selectedPoint"
                    ref="popup"
+                   @remove="onRemove"
                    @close="selectedPoint = null" />
 
         <div class="controls">
@@ -57,6 +58,19 @@
                   @show-metrics="setShowMetrics" />
       </div>
     </div>
+
+    <ConfirmDialog
+      :visible="true"
+      :disabled="loading"
+      v-if="pointToRemove"
+      @close="pointToRemove = null"
+      @confirm="removePoint">
+      <template #title>
+        Remove point
+      </template>
+
+      Are you sure you want to remove this point?
+    </ConfirmDialog>
   </main>
 </template>
 
@@ -72,7 +86,9 @@ import { useGeographic } from 'ol/proj';
 
 import type { Optional } from '../models/Types';
 import Api from '../mixins/Api.vue';
+import ConfirmDialog from '../elements/ConfirmDialog.vue';
 import Dates from '../mixins/Dates.vue';
+import Feature from 'ol/Feature';
 import FilterButton from './filter/ToggleButton.vue';
 import FilterForm from './filter/Form.vue';
 import GPSPoint from '../models/GPSPoint';
@@ -99,6 +115,7 @@ export default {
   ],
 
   components: {
+    ConfirmDialog,
     FilterButton,
     FilterForm,
     PointInfo,
@@ -110,10 +127,13 @@ export default {
       loading: false,
       map: null as Optional<Map>,
       mapView: null as Optional<View>,
+      pointToRemove: null as Optional<GPSPoint>,
       pointsLayer: null as Optional<VectorLayer>,
       popup: null as Optional<Overlay>,
       queryInitialized: false,
+      refreshPoints: 0,
       routesLayer: null as Optional<VectorLayer>,
+      selectedFeature: null as Optional<Point>,
       selectedPoint: null as Optional<GPSPoint>,
       showControls: false,
       showMetrics: new TimelineMetricsConfiguration(),
@@ -122,10 +142,14 @@ export default {
 
   computed: {
     groupedGPSPoints(): GPSPoint[] {
+      // Reference refreshPoints to force reactivity
+      this.refreshPoints;
       return this.groupPoints(this.gpsPoints)
     },
 
     mappedPoints(): Record<string, Point> {
+      // Reference refreshPoints to force reactivity
+      this.refreshPoints;
       return this.toMappedPoints(this.groupedGPSPoints)
         .reduce((acc: Record<string, Point>, point: Point) => {
           // @ts-expect-error
@@ -148,6 +172,50 @@ export default {
       }
     },
 
+    async removePoint() {
+      if (!this.pointToRemove) {
+        return
+      }
+
+      this.loading = true
+      try {
+        this.deletePoints([this.pointToRemove])
+        if (this.selectedFeature) {
+          const routeFeatures = this.routesLayer?.getSource().getFeatures().filter((f: Feature) => {
+            const [start, end] = (f.getGeometry() as any).getCoordinates()
+            return (
+              (
+                this.pointToRemove?.longitude === start[0] &&
+                this.pointToRemove?.latitude === start[1]
+              ) || (
+                this.pointToRemove?.longitude === end[0] &&
+                this.pointToRemove?.latitude === end[1]
+              )
+            )
+          })
+
+          if (routeFeatures.length) {
+            this.routesLayer?.getSource().removeFeatures(routeFeatures)
+          }
+
+          this.pointsLayer?.getSource().removeFeature(this.selectedFeature)
+        }
+
+        this.gpsPoints = this.gpsPoints.filter((p: GPSPoint) => p.id !== this.pointToRemove?.id)
+        this.refreshPoints++
+      } finally {
+        this.loading = false
+        this.pointToRemove = null
+        this.selectedPoint = null
+        this.selectedFeature = null
+      }
+    },
+
+    onRemove(point: GPSPoint) {
+      this.pointToRemove = point
+      this.$emit('remove', this.point)
+    },
+
     fetchNextPage() {
       const nextPageQuery = this.nextPageQuery()
       if (!nextPageQuery) {
@@ -190,12 +258,22 @@ export default {
       return map
     },
 
+    refreshMap() {
+      // @ts-ignore
+      this.refreshMapView(this.mapView, this.gpsPoints)
+      // @ts-ignore
+      this.refreshPointsLayer(this.pointsLayer, Object.values(this.mappedPoints))
+      // @ts-ignore
+      this.refreshRoutesLayer(this.routesLayer, Object.values(this.mappedPoints))
+    },
+
     bindClick(map: Map) {
       map.on('click', (event) => {
         this.showControls = false
         const feature = map.forEachFeatureAtPixel(event.pixel, (feature) => feature)
 
         if (feature) {
+          this.selectedFeature = feature
           const point = this.gpsPoints.find((gps: GPSPoint) => {
             const [longitude, latitude] = (feature.getGeometry() as any).getCoordinates()
             return gps.longitude === longitude && gps.latitude === latitude
@@ -210,6 +288,7 @@ export default {
           }
         } else {
           this.selectedPoint = null
+          this.selectedFeature = null
         }
       })
     },
@@ -318,12 +397,7 @@ export default {
         }
 
         if (this.mapView) {
-          // @ts-ignore
-          this.refreshMapView(this.mapView, this.gpsPoints)
-          // @ts-ignore
-          this.refreshPointsLayer(this.pointsLayer, Object.values(this.mappedPoints))
-          // @ts-ignore
-          this.refreshRoutesLayer(this.routesLayer, Object.values(this.mappedPoints))
+          this.refreshMap()
         }
       },
       deep: true,
diff --git a/frontend/src/components/PointInfo.vue b/frontend/src/components/PointInfo.vue
index 4c535d7..6d426b0 100644
--- a/frontend/src/components/PointInfo.vue
+++ b/frontend/src/components/PointInfo.vue
@@ -30,6 +30,12 @@
           <span class="continent">{{ country.continent }}</span>
         </p>
         <p class="timestamp" v-if="timeString">{{ timeString }}</p>
+
+        <div class="remove">
+          <button title="Remove" @click="$emit('remove', point)">
+            <font-awesome-icon icon="fas fa-trash-alt" />&nbsp; Remove
+          </button>
+        </div>
       </div>
     </div>
   </div>
@@ -45,7 +51,7 @@ import Dates from '../mixins/Dates.vue';
 import GPSPoint from '../models/GPSPoint';
 
 export default {
-  emit: ['close'],
+  emit: ['close', 'remove'],
   mixins: [Dates],
   props: {
     point: {
@@ -157,5 +163,22 @@ export default {
     font-weight: bold;
     font-size: 0.9em;
   }
+
+  .remove {
+    font-size: 0.85em;
+    margin-top: 0.5em;
+
+    button {
+      width: 100%;
+      background: none;
+      border: none;
+      color: var(--vt-c-red-fg-light);
+      margin-left: -0.5em;
+
+      &:hover {
+        color: var(--vt-c-red-fg-dark);
+      }
+    }
+  }
 }
 </style>
diff --git a/frontend/src/mixins/api/GPSData.vue b/frontend/src/mixins/api/GPSData.vue
index 05f2b3f..29081fd 100644
--- a/frontend/src/mixins/api/GPSData.vue
+++ b/frontend/src/mixins/api/GPSData.vue
@@ -20,6 +20,13 @@ export default {
         )
         .sort((a: GPSPoint, b: GPSPoint) => a.timestamp.getTime() - b.timestamp.getTime())
     },
+
+    async deletePoints(points: GPSPoint[]) {
+      await this.request('/gpsdata', {
+        method: 'DELETE',
+        body: points.map((point: GPSPoint) => point.id),
+      })
+    },
   },
 }
 </script>