Added accuracy info to map

This commit is contained in:
Fabio Manganiello 2025-03-31 11:04:55 +02:00
parent 3eaf721ffd
commit b517e5463d
Signed by: blacklight
GPG key ID: D90FBA7F76362774
6 changed files with 222 additions and 49 deletions

View file

@ -32,6 +32,12 @@
</div> </div>
</div> </div>
<MapCircle v-if="selectedPoint?.accuracy != null && mapObj"
:map="mapObj"
:latitude="selectedPoint.latitude"
:longitude="selectedPoint.longitude"
:radius="selectedPoint.accuracy" />
<PointInfo :point="selectedPoint" <PointInfo :point="selectedPoint"
:device="selectedPoint ? devicesById[selectedPoint?.deviceId] : null" :device="selectedPoint ? devicesById[selectedPoint?.deviceId] : null"
ref="popup" ref="popup"
@ -94,7 +100,7 @@
<script lang="ts"> <script lang="ts">
import _ from 'lodash'; import _ from 'lodash';
import Map from 'ol/Map'; import Map from 'ol/Map';
import Overlay from 'ol/Overlay'; import MapCircle from './MapCircle.vue';
import Point from 'ol/geom/Point'; import Point from 'ol/geom/Point';
import PointInfo from './PointInfo.vue'; import PointInfo from './PointInfo.vue';
import VectorLayer from 'ol/layer/Vector'; import VectorLayer from 'ol/layer/Vector';
@ -141,6 +147,7 @@ export default {
FilterButton, FilterButton,
FilterForm, FilterForm,
FloatingButton, FloatingButton,
MapCircle,
MapSelectOverlay, MapSelectOverlay,
PointInfo, PointInfo,
Timeline, Timeline,
@ -154,7 +161,6 @@ export default {
mapView: null as Optional<View>, mapView: null as Optional<View>,
pointToRemove: null as Optional<GPSPoint>, pointToRemove: null as Optional<GPSPoint>,
pointsLayer: null as Optional<VectorLayer>, pointsLayer: null as Optional<VectorLayer>,
popup: null as Optional<Overlay>,
refreshPoints: 0, refreshPoints: 0,
routesLayer: null as Optional<VectorLayer>, routesLayer: null as Optional<VectorLayer>,
selectedFeature: null as Optional<Feature>, selectedFeature: null as Optional<Feature>,
@ -197,6 +203,14 @@ export default {
return acc return acc
}, {}) }, {})
}, },
mapObj(): Map | null {
if (!this.map) {
return null
}
return this.map as Map
},
}, },
methods: { methods: {

View file

@ -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>

View file

@ -60,6 +60,12 @@
<span>{{ point.battery }}%</span> <span>{{ point.battery }}%</span>
</p> </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="locality" v-if="point.locality">{{ point.locality }}</p>
<p class="postal-code" v-if="point.postalCode">{{ point.postalCode }}</p> <p class="postal-code" v-if="point.postalCode">{{ point.postalCode }}</p>
<p class="country" v-if="country"> <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 { .timestamp {
color: var(--color-heading); color: var(--color-heading);
font-weight: bold; font-weight: bold;

View file

@ -164,7 +164,7 @@ export default {
}) })
}, },
graphData() { graphData(): { labels: Date[], datasets: any[] } {
const datasets = [] const datasets = []
if (this.showMetrics.altitude) { if (this.showMetrics.altitude) {
datasets.push( datasets.push(

View file

@ -2,9 +2,15 @@
import GPSPoint from '../models/GPSPoint' import GPSPoint from '../models/GPSPoint'
export default { export default {
data() {
return {
earthRadius: 6378e3, // metres
}
},
methods: { methods: {
latLngToDistance(p: GPSPoint, q: GPSPoint): number { 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 φ1 = p.latitude * Math.PI / 180 // φ, λ in radians
const φ2 = q.latitude * Math.PI / 180 const φ2 = q.latitude * Math.PI / 180
const Δφ = (q.latitude - p.latitude) * Math.PI / 180 const Δφ = (q.latitude - p.latitude) * Math.PI / 180

View file

@ -1,64 +1,66 @@
import type { Optional } from "./Types";
class GPSPoint { class GPSPoint {
public id: number; public id: number;
public latitude: number; public latitude: number;
public longitude: number; public longitude: number;
public altitude: number; public altitude?: Optional<number>;
public deviceId: string; public deviceId: string;
public address: string; public address?: Optional<string>;
public locality: string; public locality?: Optional<string>;
public country: string; public country?: Optional<string>;
public postalCode: string; public postalCode?: Optional<string>;
public description?: string; public description?: Optional<string>;
public battery?: number; public battery?: Optional<number>;
public speed?: number; public speed?: Optional<number>;
public accuracy?: number; public accuracy?: Optional<number>;
public timestamp: Date; public timestamp: Date;
constructor({ constructor(data: {
id,
latitude,
longitude,
altitude,
deviceId,
address,
locality,
country,
postalCode,
description,
battery,
speed,
accuracy,
timestamp,
}: {
id: number; id: number;
latitude: number; latitude: number;
longitude: number; longitude: number;
altitude: number; altitude?: number;
deviceId: string; deviceId: string;
address: string; address?: string;
locality: string; locality?: string;
country: string; country?: string;
postalCode: string; postalCode?: string;
description?: string; description?: string;
battery?: number; battery?: number;
speed?: number; speed?: number;
accuracy?: number; accuracy?: number;
timestamp: Date; timestamp?: Date;
}) { }) {
this.id = id; this.id = data.id;
this.latitude = latitude; this.latitude = data.latitude;
this.longitude = longitude; this.longitude = data.longitude;
this.altitude = altitude; this.altitude = data.altitude;
this.deviceId = deviceId; this.deviceId = data.deviceId;
this.address = address; this.address = data.address;
this.locality = locality; this.locality = data.locality;
this.country = country; this.country = data.country;
this.postalCode = postalCode; this.postalCode = data.postalCode;
this.description = description; this.description = data.description;
this.battery = battery; this.battery = data.battery;
this.speed = speed; this.speed = data.speed;
this.accuracy = accuracy; this.accuracy = data.accuracy;
this.timestamp = timestamp; this.timestamp = data.timestamp || new Date();
}
public static fromLatLng({
latitude,
longitude,
}: {
latitude: number;
longitude: number;
}) {
return new GPSPoint({
id: 0,
latitude,
longitude,
deviceId: '',
});
} }
} }