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>
<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: {

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>
</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;

View file

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

View file

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

View file

@ -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: '',
});
}
}