From 6d7bfe9b73b0ca50bedb11ab39f7bc6aa2eab80e Mon Sep 17 00:00:00 2001
From: Fabio Manganiello <fabio@manganiello.tech>
Date: Mon, 24 Feb 2025 01:22:15 +0100
Subject: [PATCH] Display min/max timestamp on the map and total distance.

---
 frontend/src/components/Map.vue | 48 +++++++++++++++++++++++++++++++++
 frontend/src/mixins/Points.vue  | 20 ++++++++++++++
 2 files changed, 68 insertions(+)

diff --git a/frontend/src/components/Map.vue b/frontend/src/components/Map.vue
index 851409c..55aa6c8 100644
--- a/frontend/src/components/Map.vue
+++ b/frontend/src/components/Map.vue
@@ -3,6 +3,22 @@
     <div class="loading" v-if="loading">Loading...</div>
     <div class="map-wrapper" v-else>
       <div id="map">
+        <div class="time-range" v-if="gpsPoints?.length">
+          <div class="row">
+            <div class="key">From</div>
+            <div class="value">{{ displayedDate(oldestPoint?.timestamp) }}</div>
+          </div>
+          <div class="row">
+            <div class="key">To</div>
+            <div class="value">{{ displayedDate(newestPoint?.timestamp) }}</div>
+          </div>
+
+          <div class="row">
+            <div class="key">Total Distance</div>
+            <div class="value">{{ displayDistance(getTotalDistance(gpsPoints)) }}</div>
+          </div>
+        </div>
+
         <PointInfo :point="selectedPoint"
                    ref="popup"
                    @close="selectedPoint = null" />
@@ -166,6 +182,14 @@ export default {
         this.locationQuery = new LocationQuery(urlQuery)
       }
     },
+
+    displayedDate(date: Date | number | string | null): string {
+      if (!date) {
+        return '-'
+      }
+
+      return new Date(date).toString().replace(/GMT.*/, '')
+    },
   },
 
   watch: {
@@ -267,6 +291,30 @@ body {
       animation: unroll 0.25s ease-out;
     }
   }
+
+  .time-range {
+    position: absolute;
+    top: 0;
+    right: 0;
+    padding: 0.5em;
+    background-color: rgba(255, 255, 255, 0.8);
+    border-radius: 0.25em;
+    z-index: 1;
+
+    .row {
+      display: flex;
+      justify-content: space-between;
+      margin-bottom: 0.25em;
+
+      .key {
+        margin-right: 1em;
+      }
+
+      .value {
+        font-weight: bold;
+      }
+    }
+  }
 }
 
 @keyframes unroll {
diff --git a/frontend/src/mixins/Points.vue b/frontend/src/mixins/Points.vue
index 2879adb..fdc2785 100644
--- a/frontend/src/mixins/Points.vue
+++ b/frontend/src/mixins/Points.vue
@@ -51,6 +51,26 @@ export default {
       return groupedPoints
     },
 
+    getTotalDistance(points: GPSPoint[]) {
+      let totalDistance = 0
+      let prevPoint: GPSPoint = points[0]
+
+      points.forEach((point: GPSPoint) => {
+        totalDistance += this.latLngToDistance(point, prevPoint)
+        prevPoint = point
+      })
+
+      return totalDistance
+    },
+
+    displayDistance(distance: number) {
+      if (distance < 1000) {
+        return `${distance.toFixed(0)} m`
+      }
+
+      return `${(distance / 1000).toFixed(2)} km`
+    },
+
     createPointsLayer(points: Point[]): VectorLayer {
       const pointFeatures = points.map((point: Point) => new Feature(point))
       return new VectorLayer({