better map markers

This commit is contained in:
Will Freeman
2024-12-12 13:19:50 -07:00
parent 6dff9cb2c8
commit a0f743b432
5 changed files with 82 additions and 51 deletions

View File

@@ -17,6 +17,7 @@
"devDependencies": {
"@mdi/font": "^7.4.47",
"@tsconfig/node20": "^20.1.4",
"@types/leaflet": "^1.9.15",
"@types/node": "^20.14.5",
"@vitejs/plugin-vue": "^5.0.5",
"@vue-leaflet/vue-leaflet": "^0.10.1",
@@ -25,6 +26,7 @@
"npm-run-all2": "^6.2.0",
"typescript": "~5.4.0",
"vite": "^5.3.1",
"vue-leaflet-rotate-marker": "^0.1.0",
"vue-tsc": "^2.0.21"
}
},
@@ -669,6 +671,21 @@
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true
},
"node_modules/@types/geojson": {
"version": "7946.0.15",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.15.tgz",
"integrity": "sha512-9oSxFzDCT2Rj6DfcHF8G++jxBKS7mBqXl5xrRW+Kbvjry6Uduya2iiwqHPhVXpasAVMBYKkEPGgKhd3+/HZ6xA==",
"dev": true
},
"node_modules/@types/leaflet": {
"version": "1.9.15",
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.15.tgz",
"integrity": "sha512-7UuggAuAs+mva66gtf2OTB1nEhzU/9JED93TIaOEgvFMvG/dIGQaukHE7izHo1Zd+Ko1L4ETUw7TBc8yUxevpg==",
"dev": true,
"dependencies": {
"@types/geojson": "*"
}
},
"node_modules/@types/node": {
"version": "20.16.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.6.tgz",
@@ -1472,6 +1489,15 @@
}
}
},
"node_modules/vue-leaflet-rotate-marker": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/vue-leaflet-rotate-marker/-/vue-leaflet-rotate-marker-0.1.0.tgz",
"integrity": "sha512-qBrb/ydvl+cuQSZ3cinH2G0nmFNglh5h9qVIFRAlrcjCNqZE1RIEHxwjvxA0CCxdlNFxGeeZy0RFrkdSqNToeg==",
"dev": true,
"dependencies": {
"vue": "^3.3.4"
}
},
"node_modules/vue-router": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.4.5.tgz",
@@ -1872,6 +1898,21 @@
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true
},
"@types/geojson": {
"version": "7946.0.15",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.15.tgz",
"integrity": "sha512-9oSxFzDCT2Rj6DfcHF8G++jxBKS7mBqXl5xrRW+Kbvjry6Uduya2iiwqHPhVXpasAVMBYKkEPGgKhd3+/HZ6xA==",
"dev": true
},
"@types/leaflet": {
"version": "1.9.15",
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.15.tgz",
"integrity": "sha512-7UuggAuAs+mva66gtf2OTB1nEhzU/9JED93TIaOEgvFMvG/dIGQaukHE7izHo1Zd+Ko1L4ETUw7TBc8yUxevpg==",
"dev": true,
"requires": {
"@types/geojson": "*"
}
},
"@types/node": {
"version": "20.16.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.6.tgz",
@@ -2433,6 +2474,15 @@
"@vue/shared": "3.5.8"
}
},
"vue-leaflet-rotate-marker": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/vue-leaflet-rotate-marker/-/vue-leaflet-rotate-marker-0.1.0.tgz",
"integrity": "sha512-qBrb/ydvl+cuQSZ3cinH2G0nmFNglh5h9qVIFRAlrcjCNqZE1RIEHxwjvxA0CCxdlNFxGeeZy0RFrkdSqNToeg==",
"dev": true,
"requires": {
"vue": "^3.3.4"
}
},
"vue-router": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.4.5.tgz",

View File

@@ -20,6 +20,7 @@
"devDependencies": {
"@mdi/font": "^7.4.47",
"@tsconfig/node20": "^20.1.4",
"@types/leaflet": "^1.9.15",
"@types/node": "^20.14.5",
"@vitejs/plugin-vue": "^5.0.5",
"@vue-leaflet/vue-leaflet": "^0.10.1",
@@ -28,6 +29,7 @@
"npm-run-all2": "^6.2.0",
"typescript": "~5.4.0",
"vite": "^5.3.1",
"vue-leaflet-rotate-marker": "^0.1.0",
"vue-tsc": "^2.0.21"
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M215.248,221.461L99.696,43.732C144.935,16.031 198.536,0 256,0C313.464,0 367.065,16.031 412.304,43.732L296.752,221.461C287.138,209.593 272.448,202.001 256,202.001C239.552,202.001 224.862,209.593 215.248,221.461Z" style="fill:rgb(87,87,87);fill-opacity:0.46;"/>
<path d="M215.248,221.461L99.696,43.732C144.935,16.031 198.536,0 256,0C313.464,0 367.065,16.031 412.304,43.732L296.752,221.461C287.138,209.593 272.448,202.001 256,202.001C239.552,202.001 224.862,209.593 215.248,221.461ZM217.92,200.242C228.694,192.652 241.831,188.195 256,188.195C270.169,188.195 283.306,192.652 294.08,200.242C294.08,200.242 392.803,48.4 392.803,48.4C352.363,26.364 305.694,13.806 256,13.806C206.306,13.806 159.637,26.364 119.197,48.4L217.92,200.242Z" style="fill:rgb(137,135,135);"/>
<g transform="matrix(0.906623,0,0,0.906623,23.9045,22.3271)">
<circle cx="256" cy="256" r="57.821" style="fill:rgb(63,84,243);fill-opacity:0.41;"/>
<path d="M256,174.25C301.119,174.25 337.75,210.881 337.75,256C337.75,301.119 301.119,337.75 256,337.75C210.881,337.75 174.25,301.119 174.25,256C174.25,210.881 210.881,174.25 256,174.25ZM256,198.179C224.088,198.179 198.179,224.088 198.179,256C198.179,287.912 224.088,313.821 256,313.821C287.912,313.821 313.821,287.912 313.821,256C313.821,224.088 287.912,198.179 256,198.179Z" style="fill:rgb(63,84,243);"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -1,28 +1,25 @@
<template>
<l-circle-marker :lat-lng="[alpr.lat, alpr.lon]" :radius="7" :color="markerColor">
<l-marker-rotate v-if="showFov && hasDirection" :lat-lng="[alpr.lat, alpr.lon]" rotationOrigin="center center" :rotationAngle>
<l-popup>
<DFMapPopup :alpr="alpr" />
</l-popup>
<l-icon :icon-url :icon-size :icon-anchor />
</l-marker-rotate>
<l-circle-marker v-else :lat-lng="[alpr.lat, alpr.lon]" :radius="7" color="#3f54f3">
<l-popup>
<DFMapPopup :alpr="alpr" />
</l-popup>
</l-circle-marker>
<l-polygon
:lat-lngs="directionIndicatorPolygonCoordinates"
:options="{ color: 'red' }"
v-if="showFov && hasDirection"
>
<!-- TODO: use the same popup -->
<l-popup>
<DFMapPopup :alpr="alpr" />
</l-popup>
</l-polygon>
</template>
<script setup lang="ts">
import { LCircleMarker, LPolygon, LPopup } from '@vue-leaflet/vue-leaflet';
import { LIcon, LPopup, LCircleMarker } from '@vue-leaflet/vue-leaflet';
import { LMarkerRotate } from 'vue-leaflet-rotate-marker';
import DFMapPopup from '@/components/DFMapPopup.vue';
import type { ALPR } from '@/types';
import type { PropType } from 'vue';
import { computed, defineProps } from 'vue';
import { type PointExpression } from 'leaflet';
const props = defineProps({
alpr: {
@@ -35,41 +32,9 @@ const props = defineProps({
}
});
const markerColor = computed(() => {
if (props.alpr.tags.brand === 'Avigilon') {
return '#ff5722';
}
return '#3f54f3';
});
const iconSize: PointExpression = [60, 60];
const iconAnchor: PointExpression = [30, 30];
const hasDirection = computed(() => props.alpr.tags.direction !== undefined);
const directionIndicatorPolygonCoordinates = computed(() => {
if (!hasDirection.value) {
console.warn('ALPR does not have direction tag');
return [];
}
const { lat, lon } = props.alpr;
const direction = parseInt(props.alpr.tags.direction);
const fov = 30; // Field of view in degrees
const distance = 0.0004; // Distance for the triangle points
const toRadians = (degrees: number) => degrees * (Math.PI / 180);
const pointL = {
lat: lat + distance * Math.cos(toRadians(direction - fov / 2)),
lon: lon + distance * Math.sin(toRadians(direction - fov / 2))
};
const pointR = {
lat: lat + distance * Math.cos(toRadians(direction + fov / 2)),
lon: lon + distance * Math.sin(toRadians(direction + fov / 2))
};
return [
[lat, lon],
[pointL.lat, pointL.lon],
[pointR.lat, pointR.lon]
];
});
const rotationAngle = computed(() => parseInt(props.alpr.tags.direction) || undefined);
const iconUrl = computed(() => props.showFov ? `/map-icon.svg` : '/map-icon-simple.svg');
</script>

View File

@@ -5,13 +5,13 @@
<v-icon start>mdi-face-recognition</v-icon> <b>Face Recognition</b>
</v-list-item>
<v-list-item>
<v-icon start>mdi-car</v-icon> <b>License Plate</b>
<v-icon start>mdi-cctv</v-icon> <b>License Plate Reader</b>
</v-list-item>
<v-list-item v-if="isFaceRecognition">
<v-icon start>mdi-adjust</v-icon> <b>Omnidirectional</b>
</v-list-item>
<v-list-item v-else>
<v-icon start>mdi-cctv</v-icon> <b>Directional {{ alpr.tags.direction ? `(${degreesToCardinal(parseInt(alpr.tags.direction))})` : '' }}</b>
<v-icon start>mdi-compass-outline</v-icon> <b>{{ cardinalDirection }}</b>
</v-list-item>
<v-list-item>
<v-icon start>mdi-domain</v-icon> <b>
@@ -75,6 +75,10 @@ const kvTags = computed(() => {
.map(([key, value]) => ({ key, value: valueTransformations[key]?.(value) ?? value }));
});
const cardinalDirection = computed(() =>
props.alpr.tags.direction === undefined ? 'Unknown' : degreesToCardinal(parseInt(props.alpr.tags.direction))
);
function degreesToCardinal(degrees: number): string {
const cardinals = ['North', 'Northeast', 'East', 'Southeast', 'South', 'Southwest', 'West', 'Northwest'];
return cardinals[Math.round(degrees / 45) % 8];