mirror of
https://github.com/FoggedLens/deflock.git
synced 2026-02-27 06:13:28 +00:00
rotate icons, transparent area not clickable
This commit is contained in:
93
webapp/package-lock.json
generated
93
webapp/package-lock.json
generated
@@ -8,8 +8,10 @@
|
||||
"name": "deflock",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@types/leaflet.markercluster": "^1.5.5",
|
||||
"axios": "^1.7.7",
|
||||
"countup.js": "^2.8.0",
|
||||
"leaflet.markercluster": "^1.5.3",
|
||||
"vue": "^3.4.29",
|
||||
"vue-router": "^4.3.3",
|
||||
"vuetify": "^3.7.2"
|
||||
@@ -20,13 +22,11 @@
|
||||
"@types/leaflet": "^1.9.15",
|
||||
"@types/node": "^20.14.5",
|
||||
"@vitejs/plugin-vue": "^5.0.5",
|
||||
"@vue-leaflet/vue-leaflet": "^0.10.1",
|
||||
"@vue/tsconfig": "^0.5.1",
|
||||
"leaflet": "^1.9.4",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
@@ -674,18 +674,24 @@
|
||||
"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
|
||||
"integrity": "sha512-9oSxFzDCT2Rj6DfcHF8G++jxBKS7mBqXl5xrRW+Kbvjry6Uduya2iiwqHPhVXpasAVMBYKkEPGgKhd3+/HZ6xA=="
|
||||
},
|
||||
"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/leaflet.markercluster": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/leaflet.markercluster/-/leaflet.markercluster-1.5.5.tgz",
|
||||
"integrity": "sha512-TkWOhSHDM1ANxmLi+uK0PjsVcjIKBr8CLV2WoF16dIdeFmC0Cj5P5axkI3C1Xsi4+ht6EU8+BfEbbqEF9icPrg==",
|
||||
"dependencies": {
|
||||
"@types/leaflet": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.16.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.6.tgz",
|
||||
@@ -734,24 +740,6 @@
|
||||
"vscode-uri": "^3.0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue-leaflet/vue-leaflet": {
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@vue-leaflet/vue-leaflet/-/vue-leaflet-0.10.1.tgz",
|
||||
"integrity": "sha512-RNEDk8TbnwrJl8ujdbKgZRFygLCxd0aBcWLQ05q/pGv4+d0jamE3KXQgQBqGAteE1mbQsk3xoNcqqUgaIGfWVg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"vue": "^3.2.25"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/leaflet": "^1.5.7",
|
||||
"leaflet": "^1.6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/leaflet": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-core": {
|
||||
"version": "3.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.8.tgz",
|
||||
@@ -1112,8 +1100,15 @@
|
||||
"node_modules/leaflet": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
|
||||
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA=="
|
||||
},
|
||||
"node_modules/leaflet.markercluster": {
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/leaflet.markercluster/-/leaflet.markercluster-1.5.3.tgz",
|
||||
"integrity": "sha512-vPTw/Bndq7eQHjLBVlWpnGeLa3t+3zGiuM7fJwCkiMFq+nmRuG3RI3f7f4N4TDX7T4NpbAXpR2+NTRSEGfCSeA==",
|
||||
"peerDependencies": {
|
||||
"leaflet": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.11",
|
||||
@@ -1489,15 +1484,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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",
|
||||
@@ -1901,18 +1887,24 @@
|
||||
"@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
|
||||
"integrity": "sha512-9oSxFzDCT2Rj6DfcHF8G++jxBKS7mBqXl5xrRW+Kbvjry6Uduya2iiwqHPhVXpasAVMBYKkEPGgKhd3+/HZ6xA=="
|
||||
},
|
||||
"@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/leaflet.markercluster": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/leaflet.markercluster/-/leaflet.markercluster-1.5.5.tgz",
|
||||
"integrity": "sha512-TkWOhSHDM1ANxmLi+uK0PjsVcjIKBr8CLV2WoF16dIdeFmC0Cj5P5axkI3C1Xsi4+ht6EU8+BfEbbqEF9icPrg==",
|
||||
"requires": {
|
||||
"@types/leaflet": "*"
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "20.16.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.6.tgz",
|
||||
@@ -1955,15 +1947,6 @@
|
||||
"vscode-uri": "^3.0.8"
|
||||
}
|
||||
},
|
||||
"@vue-leaflet/vue-leaflet": {
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@vue-leaflet/vue-leaflet/-/vue-leaflet-0.10.1.tgz",
|
||||
"integrity": "sha512-RNEDk8TbnwrJl8ujdbKgZRFygLCxd0aBcWLQ05q/pGv4+d0jamE3KXQgQBqGAteE1mbQsk3xoNcqqUgaIGfWVg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"vue": "^3.2.25"
|
||||
}
|
||||
},
|
||||
"@vue/compiler-core": {
|
||||
"version": "3.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.8.tgz",
|
||||
@@ -2255,8 +2238,13 @@
|
||||
"leaflet": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
|
||||
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA=="
|
||||
},
|
||||
"leaflet.markercluster": {
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/leaflet.markercluster/-/leaflet.markercluster-1.5.3.tgz",
|
||||
"integrity": "sha512-vPTw/Bndq7eQHjLBVlWpnGeLa3t+3zGiuM7fJwCkiMFq+nmRuG3RI3f7f4N4TDX7T4NpbAXpR2+NTRSEGfCSeA==",
|
||||
"requires": {}
|
||||
},
|
||||
"magic-string": {
|
||||
"version": "0.30.11",
|
||||
@@ -2474,15 +2462,6 @@
|
||||
"@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",
|
||||
|
||||
@@ -11,8 +11,10 @@
|
||||
"type-check": "vue-tsc --build --force"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/leaflet.markercluster": "^1.5.5",
|
||||
"axios": "^1.7.7",
|
||||
"countup.js": "^2.8.0",
|
||||
"leaflet.markercluster": "^1.5.3",
|
||||
"vue": "^3.4.29",
|
||||
"vue-router": "^4.3.3",
|
||||
"vuetify": "^3.7.2"
|
||||
@@ -23,13 +25,11 @@
|
||||
"@types/leaflet": "^1.9.15",
|
||||
"@types/node": "^20.14.5",
|
||||
"@vitejs/plugin-vue": "^5.0.5",
|
||||
"@vue-leaflet/vue-leaflet": "^0.10.1",
|
||||
"@vue/tsconfig": "^0.5.1",
|
||||
"leaflet": "^1.9.4",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
<template>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
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: {
|
||||
type: Object as PropType<ALPR>,
|
||||
required: true
|
||||
},
|
||||
showFov: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
const iconSize: PointExpression = [60, 60];
|
||||
const iconAnchor: PointExpression = [30, 30];
|
||||
const hasDirection = computed(() => props.alpr.tags.direction !== undefined);
|
||||
const rotationAngle = computed(() => parseInt(props.alpr.tags.direction) || undefined);
|
||||
const iconUrl = computed(() => props.showFov ? `/map-icon.svg` : '/map-icon-simple.svg');
|
||||
</script>
|
||||
@@ -52,6 +52,7 @@
|
||||
import { defineProps, computed } from 'vue';
|
||||
import type { PropType } from 'vue';
|
||||
import type { ALPR } from '@/types';
|
||||
import { VIcon, VList, VSheet, VListItem } from 'vuetify/components';
|
||||
|
||||
const props = defineProps({
|
||||
alpr: {
|
||||
|
||||
211
webapp/src/components/LeafletMap.vue
Normal file
211
webapp/src/components/LeafletMap.vue
Normal file
@@ -0,0 +1,211 @@
|
||||
<template>
|
||||
<div id="map"></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, h, createApp, watch, type PropType } from 'vue';
|
||||
import L from 'leaflet';
|
||||
|
||||
import DFMapPopup from './DFMapPopup.vue';
|
||||
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import 'leaflet.markercluster';
|
||||
import 'leaflet.markercluster/dist/MarkerCluster.Default.css';
|
||||
import 'leaflet.markercluster/dist/MarkerCluster.css';
|
||||
import { createVuetify } from 'vuetify'
|
||||
|
||||
const props = defineProps({
|
||||
center: {
|
||||
required: true,
|
||||
},
|
||||
zoom: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
alprs: Array
|
||||
});
|
||||
|
||||
let map: L.Map;
|
||||
let circlesLayer: L.FeatureGroup;
|
||||
let clusterLayer: L.MarkerClusterGroup;
|
||||
|
||||
onMounted(() => {
|
||||
initializeMap();
|
||||
});
|
||||
|
||||
function initializeMap() {
|
||||
map = L.map('map', { zoomControl: false }).setView(props.center, props.zoom);
|
||||
|
||||
registerEvents();
|
||||
registerWatchers();
|
||||
|
||||
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
||||
}).addTo(map);
|
||||
|
||||
populateMap();
|
||||
}
|
||||
|
||||
function populateMap() {
|
||||
const showFov = props.zoom >= 16;
|
||||
|
||||
if (clusterLayer) {
|
||||
map.removeLayer(clusterLayer);
|
||||
}
|
||||
|
||||
clusterLayer = L.markerClusterGroup();
|
||||
circlesLayer = L.featureGroup();
|
||||
|
||||
for (let i = 0; i < 200; i++) {
|
||||
const lat = 51.505 + (Math.random() - 0.5) * 0.1;
|
||||
const lng = -0.09 + (Math.random() - 0.5) * 0.1;
|
||||
const orientationDegrees = Math.random() * 360;
|
||||
|
||||
let marker: L.CircleMarker | L.Marker;
|
||||
|
||||
// content of @/public/map-icon.svg; TODO: import it here if possible
|
||||
const svgMarker = `
|
||||
<svg style="transform:rotate(${orientationDegrees}deg)" class="svgMarker" 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 class="someSVGpath" 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 class="someSVGpath" 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 class="someSVGpath" cx="256" cy="256" r="57.821" style="fill:rgb(63,84,243);fill-opacity:0.41;"/>
|
||||
<path class="someSVGpath" 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>
|
||||
`;
|
||||
|
||||
const el = document.createElement('div');
|
||||
el.innerHTML = svgMarker;
|
||||
el.style.width = '50px';
|
||||
el.style.height = '50px';
|
||||
|
||||
if (showFov) {
|
||||
const icon = L.divIcon({
|
||||
className: 'leaflet-data-marker',
|
||||
html: svgMarker,
|
||||
iconSize: [60, 60],
|
||||
iconAnchor: [30, 30],
|
||||
popupAnchor: [0, -5],
|
||||
});
|
||||
|
||||
marker = L.marker([lat, lng], { icon: icon });
|
||||
} else {
|
||||
marker = L.circleMarker([lat, lng], {
|
||||
fill: true,
|
||||
fillColor: '#3f54f3',
|
||||
fillOpacity: 0.6,
|
||||
stroke: true,
|
||||
color: '#3f54f3',
|
||||
opacity: 1,
|
||||
radius: 8,
|
||||
weight: 3,
|
||||
});
|
||||
}
|
||||
|
||||
// Bind an empty popup to the circle
|
||||
marker.bindPopup('');
|
||||
|
||||
// Add an event listener to create the popup content on demand
|
||||
marker.on('popupopen', (e: any) => {
|
||||
const popupContent = document.createElement('div');
|
||||
createApp({
|
||||
render() {
|
||||
return h(DFMapPopup, { alpr: {
|
||||
id: `id-${i}`,
|
||||
lat: lat,
|
||||
lon: lng,
|
||||
tags: { type: 'car' },
|
||||
type: "node",
|
||||
} });
|
||||
}
|
||||
}).use(createVuetify()).mount(popupContent);
|
||||
|
||||
// Set the popup content
|
||||
e.popup.setContent(popupContent);
|
||||
});
|
||||
|
||||
circlesLayer.addLayer(marker);
|
||||
}
|
||||
|
||||
clusterLayer.addLayer(circlesLayer);
|
||||
map.addLayer(clusterLayer);
|
||||
}
|
||||
|
||||
const emit = defineEmits(['update:center', 'update:zoom', 'update:bounds']);
|
||||
|
||||
function registerEvents() {
|
||||
map.on('moveend', () => {
|
||||
emit('update:center', map.getCenter());
|
||||
emit('update:zoom', map.getZoom());
|
||||
emit('update:bounds', map.getBounds());
|
||||
});
|
||||
|
||||
map.on('load', () => {
|
||||
emit('update:bounds', map.getBounds());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the zoom level has crossed the threshold for switching map markers.
|
||||
* Standard circles are shown until a zoom level of 16 is reached, at which point
|
||||
* the map switches to showing the field of view for each camera.
|
||||
* @param oldZoom old zoom level
|
||||
* @param newZoom new zoom level
|
||||
* @param threshold the zoom level threshold
|
||||
*/
|
||||
function hasCrossedZoomThreshold(oldZoom: number, newZoom: number, threshold: number) {
|
||||
return (oldZoom < threshold && newZoom >= threshold) || (oldZoom >= threshold && newZoom < threshold);
|
||||
}
|
||||
|
||||
function registerWatchers() {
|
||||
watch(() => props.center, (newCenter) => {
|
||||
if (newCenter !== props.center) // TODO: is this necessary?
|
||||
map.setView(newCenter);
|
||||
});
|
||||
|
||||
watch(() => props.zoom, (newZoom, oldZoom) => {
|
||||
if (newZoom !== oldZoom) {
|
||||
map.setZoom(newZoom);
|
||||
if (hasCrossedZoomThreshold(oldZoom, newZoom, 16)) {
|
||||
populateMap();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import 'leaflet/dist/leaflet.css';
|
||||
@import 'leaflet.markercluster/dist/MarkerCluster.Default.css';
|
||||
@import 'leaflet.markercluster/dist/MarkerCluster.css';
|
||||
|
||||
#map {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style> /* (Global) */
|
||||
/* Disables clicks on the main wrappers */
|
||||
.leaflet-marker-icon.leaflet-interactive:not(.marker-cluster), .svgMarker {
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
}
|
||||
.svgMarker {
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Enables clicks only on actual SVG path */
|
||||
.someSVGpath {
|
||||
pointer-events: all;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@@ -7,59 +7,13 @@
|
||||
</v-card>
|
||||
|
||||
<!-- use-global-leaflet=false is a workaround for a bug in current version of vue-leaflet -->
|
||||
<l-map
|
||||
<leaflet-map
|
||||
v-if="center"
|
||||
ref="map"
|
||||
v-model:zoom="zoom"
|
||||
v-model:center="center"
|
||||
:use-global-leaflet="false"
|
||||
v-model:zoom="zoom"
|
||||
@update:bounds="updateBounds"
|
||||
@ready="mapLoaded"
|
||||
:options="{ zoomControl: false }"
|
||||
>
|
||||
<l-control position="topleft">
|
||||
<form @submit.prevent="onSearch">
|
||||
<v-text-field
|
||||
:rounded="xs || undefined"
|
||||
:density="xs ? 'compact' : 'default'"
|
||||
class="map-search"
|
||||
ref="searchField"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
placeholder="Search for a location"
|
||||
single-line
|
||||
variant="solo"
|
||||
clearable
|
||||
hide-details
|
||||
v-model="searchQuery"
|
||||
type="search"
|
||||
>
|
||||
<template v-slot:append-inner>
|
||||
<v-btn :disabled="!searchQuery" variant="text" flat color="#0080BC" @click="onSearch">
|
||||
Go<v-icon end>mdi-chevron-right</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</form>
|
||||
</l-control>
|
||||
<!-- url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" -->
|
||||
|
||||
<l-tile-layer
|
||||
:url="mapTileUrl"
|
||||
layer-type="base"
|
||||
name="OpenStreetMap"
|
||||
attribution="© <a target="_blank" href="http://osm.org/copyright">OpenStreetMap</a> contributors"
|
||||
/>
|
||||
|
||||
<l-control position="bottomright">
|
||||
<v-btn @click="goToUserLocation" icon class="mt-2">
|
||||
<v-icon x-large>mdi-crosshairs-gps</v-icon>
|
||||
</v-btn>
|
||||
</l-control>
|
||||
|
||||
<DFMarkerCluster v-if="showClusters" v-for="cluster in clusters" :key="cluster.id" :lat="cluster.lat" :lon="cluster.lon" />
|
||||
<DFMapMarker v-else v-for="alpr in visibleALPRs" :key="alpr.id" :alpr :show-fov="zoom >= 16" />
|
||||
</l-map>
|
||||
<div class="loader" v-else>
|
||||
/>
|
||||
<div v-else class="loader">
|
||||
<span class="mb-4 text-grey">Loading Map</span>
|
||||
<v-progress-circular indeterminate color="primary" />
|
||||
</div>
|
||||
@@ -77,9 +31,12 @@ import type { Cluster } from '@/services/apiService';
|
||||
import { getALPRs, geocodeQuery, getClusters } from '@/services/apiService';
|
||||
import { useDisplay, useTheme } from 'vuetify';
|
||||
import DFMapMarker from '@/components/DFMapMarker.vue';
|
||||
import DFMarkerCluster from '@/components/DFMarkerCluster.vue';
|
||||
import NewVisitor from '@/components/NewVisitor.vue';
|
||||
import type { ALPR } from '@/types';
|
||||
import L from 'leaflet';
|
||||
globalThis.L = L;
|
||||
import 'leaflet/dist/leaflet.css'
|
||||
import 'vue-leaflet-markercluster/dist/style.css'
|
||||
import LeafletMap from '@/components/LeafletMap.vue';
|
||||
|
||||
const DEFAULT_ZOOM = 12;
|
||||
const MIN_ZOOM_FOR_REFRESH = 4;
|
||||
@@ -167,10 +124,6 @@ function getUserLocation(): Promise<[number, number]> {
|
||||
});
|
||||
};
|
||||
|
||||
function mapLoaded(map: any) {
|
||||
updateBounds(map.getBounds());
|
||||
}
|
||||
|
||||
function updateBounds(newBounds: any) {
|
||||
updateURL();
|
||||
|
||||
@@ -187,7 +140,7 @@ function updateBounds(newBounds: any) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateMarkers();
|
||||
// updateMarkers();
|
||||
}
|
||||
|
||||
function updateURL() {
|
||||
|
||||
Reference in New Issue
Block a user