memories/src/components/top-matter/MapSplitMatter.vue

178 lines
4.0 KiB
Vue
Raw Normal View History

<template>
2023-02-08 22:13:13 +00:00
<div class="map-matter">
2023-02-09 05:55:12 +00:00
<LMap
2023-02-08 21:35:42 +00:00
class="map"
2023-01-26 04:51:42 +00:00
ref="map"
2023-02-08 21:35:42 +00:00
:zoom="zoom"
:minZoom="2"
@moveend="refresh"
@zoomend="refresh"
2023-01-26 04:51:42 +00:00
>
2023-02-09 05:55:12 +00:00
<LTileLayer :url="url" :attribution="attribution" />
<LMarker
v-for="cluster in clusters"
:key="cluster.center.toString()"
:lat-lng="cluster.center"
2023-02-09 07:15:48 +00:00
@click="zoomTo(cluster.center)"
>
2023-02-09 05:55:12 +00:00
<LIcon v-if="cluster.id" :icon-anchor="[24, 24]">
<div class="preview">
<div class="count">{{ cluster.count }}</div>
<img :src="clusterPreviewUrl(cluster)" />
</div>
</LIcon>
</LMarker>
</LMap>
2023-01-26 04:51:42 +00:00
</div>
</template>
<script lang="ts">
2023-02-08 21:35:42 +00:00
import { defineComponent } from "vue";
2023-02-09 05:55:12 +00:00
import { LMap, LTileLayer, LMarker, LPopup, LIcon } from "vue2-leaflet";
2023-01-26 04:51:42 +00:00
import { Icon } from "leaflet";
2023-02-08 21:35:42 +00:00
import { API } from "../../services/API";
2023-02-08 21:35:42 +00:00
import axios from "axios";
2023-02-08 21:35:42 +00:00
import "leaflet/dist/leaflet.css";
2023-02-08 21:35:42 +00:00
const TILE_URL = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
const ATTRIBUTION =
'&copy; <a target="_blank" href="http://osm.org/copyright">OpenStreetMap</a> contributors';
2023-02-08 21:53:38 +00:00
type IMarkerCluster = {
2023-02-09 05:55:12 +00:00
id?: number;
2023-02-08 21:53:38 +00:00
center: [number, number];
count: number;
};
Icon.Default.mergeOptions({
2023-01-26 04:51:42 +00:00
iconRetinaUrl: require("leaflet/dist/images/marker-icon-2x.png"),
iconUrl: require("leaflet/dist/images/marker-icon.png"),
shadowUrl: require("leaflet/dist/images/marker-shadow.png"),
});
export default defineComponent({
2023-02-08 22:13:13 +00:00
name: "MapSplitMatter",
2023-01-26 04:51:42 +00:00
components: {
LMap,
LTileLayer,
LMarker,
LPopup,
2023-02-09 05:55:12 +00:00
LIcon,
2023-01-26 04:51:42 +00:00
},
data: () => ({
2023-02-08 21:35:42 +00:00
url: TILE_URL,
attribution: ATTRIBUTION,
zoom: 2,
clusters: [] as IMarkerCluster[],
2023-01-26 04:51:42 +00:00
}),
mounted() {
2023-02-08 21:35:42 +00:00
const map = this.$refs.map as LMap;
2023-02-08 21:35:42 +00:00
// Make sure the zoom control doesn't overlap with the navbar
map.mapObject.zoomControl.setPosition("topright");
// Initialize
this.refresh();
2023-01-26 04:51:42 +00:00
},
2023-01-26 04:51:42 +00:00
methods: {
2023-02-08 21:35:42 +00:00
async refresh() {
const map = this.$refs.map as LMap;
// Get boundaries of the map
const boundary = map.mapObject.getBounds();
const minLat = boundary.getSouth();
const maxLat = boundary.getNorth();
const minLon = boundary.getWest();
const maxLon = boundary.getEast();
// Set query parameters to route if required
const s = (x: number) => x.toFixed(6);
const bounds = `${s(minLat)},${s(maxLat)},${s(minLon)},${s(maxLon)}`;
const zoom = map.mapObject.getZoom().toString();
if (this.$route.query.b === bounds && this.$route.query.z === zoom) {
return;
}
this.$router.replace({ query: { b: bounds, z: zoom } });
2023-02-08 18:52:53 +00:00
// Show clusters correctly while draging the map
2023-02-08 21:35:42 +00:00
const query = new URLSearchParams();
2023-02-08 21:53:38 +00:00
query.set("bounds", bounds);
2023-02-08 21:35:42 +00:00
query.set("zoom", zoom);
2023-02-08 21:35:42 +00:00
// Make API call
2023-02-08 22:13:13 +00:00
const url = API.Q(API.MAP_CLUSTERS(), query);
const res = await axios.get(url);
this.clusters = res.data;
},
2023-02-09 05:55:12 +00:00
clusterPreviewUrl(cluster: IMarkerCluster) {
return API.MAP_CLUSTER_PREVIEW(cluster.id);
},
2023-02-09 07:15:48 +00:00
zoomTo(center: [number, number]) {
const map = this.$refs.map as LMap;
const zoom = Math.max(map.mapObject.getZoom() + 2, 14);
map.mapObject.setView(center, zoom, { animate: true });
},
2023-01-26 04:51:42 +00:00
},
});
</script>
<style lang="scss" scoped>
2023-02-08 22:13:13 +00:00
.map-matter {
2023-02-08 21:35:42 +00:00
height: 100%;
width: 100%;
}
.map {
height: 100%;
width: 100%;
margin: 0;
z-index: 0;
}
2023-02-09 05:55:12 +00:00
.preview {
width: 48px;
height: 48px;
background-color: #fff;
border-radius: 5px;
position: relative;
transition: transform 0.2s;
&:hover {
transform: scale(1.8);
}
img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 5px;
2023-02-09 07:15:48 +00:00
cursor: pointer;
2023-02-09 05:55:12 +00:00
}
.count {
position: absolute;
top: 0;
right: 0;
background-color: var(--color-primary-default);
color: var(--color-primary-text);
padding: 0 4px;
border-radius: 5px;
font-size: 0.8em;
}
}
</style>
<style lang="scss">
// Show leaflet marker on top on hover
.leaflet-marker-icon:hover {
z-index: 100000 !important;
}
2023-01-26 04:51:42 +00:00
</style>