better map markers

This commit is contained in:
Will Freeman committed 2024-12-12 13:19:50 -07:00
1 parent 6dff9cb2c8
commit a0f743b432
5 files changed
+82 -51

No files matched your search

+50
View File
@@ -17,6 +17,7 @@
"devDependencies": { "devDependencies": {
"@mdi/font": "^7.4.47", "@mdi/font": "^7.4.47",
"@tsconfig/node20": "^20.1.4", "@tsconfig/node20": "^20.1.4",
"@types/leaflet": "^1.9.15",
"@types/node": "^20.14.5", "@types/node": "^20.14.5",
"@vitejs/plugin-vue": "^5.0.5", "@vitejs/plugin-vue": "^5.0.5",
"@vue-leaflet/vue-leaflet": "^0.10.1", "@vue-leaflet/vue-leaflet": "^0.10.1",
@@ -25,6 +26,7 @@
"npm-run-all2": "^6.2.0", "npm-run-all2": "^6.2.0",
"typescript": "~5.4.0", "typescript": "~5.4.0",
"vite": "^5.3.1", "vite": "^5.3.1",
"vue-leaflet-rotate-marker": "^0.1.0",
"vue-tsc": "^2.0.21" "vue-tsc": "^2.0.21"
} }
}, },
@@ -669,6 +671,21 @@
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true "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": { "node_modules/@types/node": {
"version": "20.16.6", "version": "20.16.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.6.tgz", "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": { "node_modules/vue-router": {
"version": "4.4.5", "version": "4.4.5",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.4.5.tgz", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.4.5.tgz",
@@ -1872,6 +1898,21 @@
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true "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": { "@types/node": {
"version": "20.16.6", "version": "20.16.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.6.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.6.tgz",
@@ -2433,6 +2474,15 @@
"@vue/shared": "3.5.8" "@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": { "vue-router": {
"version": "4.4.5", "version": "4.4.5",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.4.5.tgz", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.4.5.tgz",
+2
View File
@@ -20,6 +20,7 @@
"devDependencies": { "devDependencies": {
"@mdi/font": "^7.4.47", "@mdi/font": "^7.4.47",
"@tsconfig/node20": "^20.1.4", "@tsconfig/node20": "^20.1.4",
"@types/leaflet": "^1.9.15",
"@types/node": "^20.14.5", "@types/node": "^20.14.5",
"@vitejs/plugin-vue": "^5.0.5", "@vitejs/plugin-vue": "^5.0.5",
"@vue-leaflet/vue-leaflet": "^0.10.1", "@vue-leaflet/vue-leaflet": "^0.10.1",
@@ -28,6 +29,7 @@
"npm-run-all2": "^6.2.0", "npm-run-all2": "^6.2.0",
"typescript": "~5.4.0", "typescript": "~5.4.0",
"vite": "^5.3.1", "vite": "^5.3.1",
"vue-leaflet-rotate-marker": "^0.1.0",
"vue-tsc": "^2.0.21" "vue-tsc": "^2.0.21"
} }
} }
+10
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

+14 -49
View File
@@ -1,28 +1,25 @@
<template> <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> <l-popup>
<DFMapPopup :alpr="alpr" /> <DFMapPopup :alpr="alpr" />
</l-popup> </l-popup>
</l-circle-marker> </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> </template>
<script setup lang="ts"> <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 DFMapPopup from '@/components/DFMapPopup.vue';
import type { ALPR } from '@/types'; import type { ALPR } from '@/types';
import type { PropType } from 'vue'; import type { PropType } from 'vue';
import { computed, defineProps } from 'vue'; import { computed, defineProps } from 'vue';
import { type PointExpression } from 'leaflet';
const props = defineProps({ const props = defineProps({
alpr: { alpr: {
@@ -35,41 +32,9 @@ const props = defineProps({
} }
}); });
const markerColor = computed(() => { const iconSize: PointExpression = [60, 60];
if (props.alpr.tags.brand === 'Avigilon') { const iconAnchor: PointExpression = [30, 30];
return '#ff5722';
}
return '#3f54f3';
});
const hasDirection = computed(() => props.alpr.tags.direction !== undefined); const hasDirection = computed(() => props.alpr.tags.direction !== undefined);
const rotationAngle = computed(() => parseInt(props.alpr.tags.direction) || undefined);
const directionIndicatorPolygonCoordinates = computed(() => { const iconUrl = computed(() => props.showFov ? `/map-icon.svg` : '/map-icon-simple.svg');
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]
];
});
</script> </script>
+6 -2
View File
@@ -5,13 +5,13 @@
<v-icon start>mdi-face-recognition</v-icon> <b>Face Recognition</b> <v-icon start>mdi-face-recognition</v-icon> <b>Face Recognition</b>
</v-list-item> </v-list-item>
<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-list-item v-if="isFaceRecognition"> <v-list-item v-if="isFaceRecognition">
<v-icon start>mdi-adjust</v-icon> <b>Omnidirectional</b> <v-icon start>mdi-adjust</v-icon> <b>Omnidirectional</b>
</v-list-item> </v-list-item>
<v-list-item v-else> <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-list-item> <v-list-item>
<v-icon start>mdi-domain</v-icon> <b> <v-icon start>mdi-domain</v-icon> <b>
@@ -75,6 +75,10 @@ const kvTags = computed(() => {
.map(([key, value]) => ({ key, value: valueTransformations[key]?.(value) ?? value })); .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 { function degreesToCardinal(degrees: number): string {
const cardinals = ['North', 'Northeast', 'East', 'Southeast', 'South', 'Southwest', 'West', 'Northwest']; const cardinals = ['North', 'Northeast', 'East', 'Southeast', 'South', 'Southwest', 'West', 'Northwest'];
return cardinals[Math.round(degrees / 45) % 8]; return cardinals[Math.round(degrees / 45) % 8];