heat map for photos

Signed-off-by: Arne Hamann <git@arne.email>
pull/807/head
Arne Hamann 2023-09-06 22:26:56 +02:00
parent 50dfffc0f8
commit 6ad7c560fc
4 changed files with 138 additions and 0 deletions

11
package-lock.json generated
View File

@ -20,6 +20,7 @@
"justified-layout": "^4.1.0", "justified-layout": "^4.1.0",
"leaflet": "^1.9.4", "leaflet": "^1.9.4",
"leaflet-edgebuffer": "^1.0.6", "leaflet-edgebuffer": "^1.0.6",
"leaflet.heat": "^0.2.0",
"luxon": "^3.3.0", "luxon": "^3.3.0",
"path-posix": "^1.0.0", "path-posix": "^1.0.0",
"photoswipe": "^5.3.8", "photoswipe": "^5.3.8",
@ -6728,6 +6729,11 @@
"resolved": "https://registry.npmjs.org/leaflet-edgebuffer/-/leaflet-edgebuffer-1.0.6.tgz", "resolved": "https://registry.npmjs.org/leaflet-edgebuffer/-/leaflet-edgebuffer-1.0.6.tgz",
"integrity": "sha512-2RZsp3rta0w2zBgC40F7drhYCAPaS0/i6o9MR0gMA0cdp83rLv+1gfqPnF7lnFdqskteGyQX+wzLFVdHrQjb4A==" "integrity": "sha512-2RZsp3rta0w2zBgC40F7drhYCAPaS0/i6o9MR0gMA0cdp83rLv+1gfqPnF7lnFdqskteGyQX+wzLFVdHrQjb4A=="
}, },
"node_modules/leaflet.heat": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/leaflet.heat/-/leaflet.heat-0.2.0.tgz",
"integrity": "sha512-Cd5PbAA/rX3X3XKxfDoUGi9qp78FyhWYurFg3nsfhntcM/MCNK08pRkf4iEenO1KNqwVPKCmkyktjW3UD+h9bQ=="
},
"node_modules/leven": { "node_modules/leven": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
@ -16655,6 +16661,11 @@
"resolved": "https://registry.npmjs.org/leaflet-edgebuffer/-/leaflet-edgebuffer-1.0.6.tgz", "resolved": "https://registry.npmjs.org/leaflet-edgebuffer/-/leaflet-edgebuffer-1.0.6.tgz",
"integrity": "sha512-2RZsp3rta0w2zBgC40F7drhYCAPaS0/i6o9MR0gMA0cdp83rLv+1gfqPnF7lnFdqskteGyQX+wzLFVdHrQjb4A==" "integrity": "sha512-2RZsp3rta0w2zBgC40F7drhYCAPaS0/i6o9MR0gMA0cdp83rLv+1gfqPnF7lnFdqskteGyQX+wzLFVdHrQjb4A=="
}, },
"leaflet.heat": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/leaflet.heat/-/leaflet.heat-0.2.0.tgz",
"integrity": "sha512-Cd5PbAA/rX3X3XKxfDoUGi9qp78FyhWYurFg3nsfhntcM/MCNK08pRkf4iEenO1KNqwVPKCmkyktjW3UD+h9bQ=="
},
"leven": { "leven": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",

View File

@ -41,6 +41,7 @@
"justified-layout": "^4.1.0", "justified-layout": "^4.1.0",
"leaflet": "^1.9.4", "leaflet": "^1.9.4",
"leaflet-edgebuffer": "^1.0.6", "leaflet-edgebuffer": "^1.0.6",
"leaflet.heat": "^0.2.0",
"luxon": "^3.3.0", "luxon": "^3.3.0",
"path-posix": "^1.0.0", "path-posix": "^1.0.0",
"photoswipe": "^5.3.8", "photoswipe": "^5.3.8",

View File

@ -30,6 +30,16 @@
</div> </div>
</LIcon> </LIcon>
</LMarker> </LMarker>
<LHeatMap v-if="points.length >= 50"
ref="heatMap"
:initial-points="points"
:options="{
// minOpacity: null,
// maxZoom: null,
radius: 15,
blur: 10,
gradient: { 0.4: 'blue', 0.65: 'lime', 1: 'red' },
}" />
</LMap> </LMap>
</div> </div>
</template> </template>
@ -37,6 +47,7 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { LMap, LTileLayer, LMarker, LPopup, LIcon } from 'vue2-leaflet'; import { LMap, LTileLayer, LMarker, LPopup, LIcon } from 'vue2-leaflet';
import LHeatMap from './map/LHeatMap.vue';
import { latLngBounds, Icon } from 'leaflet'; import { latLngBounds, Icon } from 'leaflet';
import { IPhoto } from '../../types'; import { IPhoto } from '../../types';
@ -73,6 +84,7 @@ Icon.Default.mergeOptions({
export default defineComponent({ export default defineComponent({
name: 'MapSplitMatter', name: 'MapSplitMatter',
components: { components: {
LHeatMap,
LMap, LMap,
LTileLayer, LTileLayer,
LMarker, LMarker,
@ -88,6 +100,7 @@ export default defineComponent({
maxBoundsViscosity: 0.9, maxBoundsViscosity: 0.9,
}, },
clusters: [] as IMarkerCluster[], clusters: [] as IMarkerCluster[],
points: [],
animMarkers: false, animMarkers: false,
}), }),
@ -137,6 +150,7 @@ export default defineComponent({
if (!reinit) { if (!reinit) {
this.setBoundsFromQuery(); this.setBoundsFromQuery();
} }
await this.fetchPoints();
return await this.fetchClusters(); return await this.fetchClusters();
} }
@ -245,6 +259,34 @@ export default defineComponent({
this.animateMarkers(); this.animateMarkers();
}, },
async fetchPoints() {
const zoom = "18";
let minLat = -90;
let maxLat = 90;
let minLon = -180;
let maxLon = 180;
// Extend bounds by 25% beyond the map
const latDiff = Math.abs(maxLat - minLat);
const lonDiff = Math.abs(maxLon - minLon);
minLat -= latDiff * 0.25;
maxLat += latDiff * 0.25;
minLon -= lonDiff * 0.25;
maxLon += lonDiff * 0.25;
// Get bounds with expanded margins
const bounds = this.boundsToStr({ minLat, maxLat, minLon, maxLon });
// Make API call
const url = API.Q(API.MAP_CLUSTERS(), { bounds, zoom });
// Params have changed, quit
const res = await axios.get(url);
this.points = res.data.map((c) => {
return [c.center[0], c.center[1], c.count]
});
},
boundsFromQuery() { boundsFromQuery() {
const bounds = (this.$route.query.b as string).split(','); const bounds = (this.$route.query.b as string).split(',');
return { return {

View File

@ -0,0 +1,84 @@
<template>
<div style="display: none;">
<slot v-if="ready" />
</div>
</template>
<script>
import 'leaflet.heat/dist/leaflet-heat.js'
import { findRealParent, propsBinder } from 'vue2-leaflet'
import { DomEvent } from 'leaflet'
const props = {
initialPoints: {
type: Array,
required: false,
default() { return [] },
},
options: {
type: Object,
default() { return {} },
},
}
export default {
props,
data() {
return {
points: null,
ready: false,
}
},
watch: {
options: {
handler(newOptions) {
this.mapObject.setOptions(newOptions)
},
deep: true,
},
points: {
handler(newPoints) {
this.mapObject.setLatLngs(newPoints)
},
deep: true,
},
},
mounted() {
this.points = this.initialPoints
this.mapObject = L.heatLayer(this.points, this.options)
DomEvent.on(this.mapObject, this.$listeners)
propsBinder(this, this.mapObject, props)
this.ready = true
this.parentContainer = findRealParent(this.$parent)
this.parentContainer.addLayer(this)
this.$nextTick(() => {
this.$emit('ready', this.mapObject)
})
},
beforeDestroy() {
this.parentContainer.removeLayer(this)
},
methods: {
addLayer(layer, alreadyAdded) {
if (!alreadyAdded) {
this.mapObject.addLayer(layer.mapObject)
}
},
removeLayer(layer, alreadyRemoved) {
if (!alreadyRemoved) {
this.mapObject.removeLayer(layer.mapObject)
}
},
addLatLng(latlng) {
this.mapObject.addLatLng(latlng)
},
setLatLngs(latlngs) {
this.mapObject.setLatLngs(latlngs)
},
redraw() {
this.mapObject.redraw()
},
},
}
</script>