nidus-sync/ts/components/MapAggregate.vue
Eli Ribble f88ca57d97
Migrate existing ts types from the API into the API module
This makes it possible to start hydrating the types into valid data
types like Dates which means I can get type safety guarantees when
displaying information.
2026-04-09 00:25:21 +00:00

197 lines
4.2 KiB
Vue

<style scoped>
.map-container {
background-color: #e9ecef;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
height: 500px;
margin-top: 20px;
position: relative;
}
.map {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
height: 100%;
width: 100%;
}
</style>
<template>
<div class="map-container">
<div ref="mapContainer" class="map"></div>
</div>
</template>
<script setup lang="ts">
import "maplibre-gl/dist/maplibre-gl.css";
import maplibregl from "maplibre-gl";
import type { LngLatBoundsLike, Map as MapLibreMap } from "maplibre-gl";
import { onMounted, onUnmounted, ref, shallowRef, type Ref } from "vue";
import { Bounds, Marker } from "@/types";
interface Emits {
(e: "cell-click", cell: number): void;
}
interface Props {
bounds?: Bounds;
markers?: Marker[];
organizationId: number;
tegola: string;
}
const emit = defineEmits<Emits>();
const props = withDefaults(defineProps<Props>(), {
// default bounds cover a bunch of the continental US
bounds: (): Bounds => {
return {
max: { longitude: -70, latitude: 50 },
min: { longitude: -125, latitude: 25 },
};
},
});
const boundsSafe = props.bounds as Bounds;
const mapContainer = ref<HTMLElement | null>(null);
const map: Ref<MapLibreMap | null> = shallowRef(null);
function _bounds(): LngLatBoundsLike {
return new maplibregl.LngLatBounds(
new maplibregl.LngLat(boundsSafe.min.longitude, boundsSafe.min.latitude),
new maplibregl.LngLat(boundsSafe.max.longitude, boundsSafe.max.latitude),
);
}
const initializeMap = () => {
if (!mapContainer.value) return;
const bounds = _bounds();
map.value = new maplibregl.Map({
bounds: bounds,
container: mapContainer.value,
style: "https://tiles.stadiamaps.com/styles/alidade_smooth.json",
});
console.log("Initializing map to bounds", bounds);
const mapInstance = map.value;
if (mapInstance) {
map.value.on("load", () => {
if (!map.value) return;
map.value.addSource("tegola", {
type: "vector",
tiles: [
`${props.tegola}maps/nidus/{z}/{x}/{y}?id=${props.organizationId}&organization_id=${props.organizationId}`,
],
});
map.value.addLayer({
id: "mosquito_source",
type: "fill",
filter: [
"==",
["zoom"],
["+", 2, ["to-number", ["get", "resolution"]]],
],
source: "tegola",
"source-layer": "mosquito_source",
paint: {
"fill-opacity": 0.4,
"fill-color": "#dc3545",
},
});
map.value.addLayer({
id: "service_request",
type: "fill",
filter: [
"==",
["zoom"],
["+", 2, ["to-number", ["get", "resolution"]]],
],
source: "tegola",
"source-layer": "service_request",
paint: {
"fill-opacity": 0.4,
"fill-color": "#ffc107",
},
});
map.value.addLayer({
id: "trap",
type: "fill",
filter: [
"==",
["zoom"],
["+", 2, ["to-number", ["get", "resolution"]]],
],
source: "tegola",
"source-layer": "trap",
paint: {
"fill-opacity": 0.4,
"fill-color": "#0dcaf0",
},
});
map.value.addLayer({
id: "service-area",
source: "tegola",
"source-layer": "service-area-bounds",
type: "line",
paint: {
"line-color": "#f00",
},
});
map.value.on("mouseenter", "mosquito_source", () => {
if (map.value) {
map.value.getCanvas().style.cursor = "pointer";
}
});
map.value.on("mouseleave", "mosquito_source", () => {
if (map.value) {
map.value.getCanvas().style.cursor = "";
}
});
const handleClick = (e: maplibregl.MapLayerMouseEvent) => {
if (!e.features || e.features.length === 0) return;
const feature = e.features[0];
const properties = feature.properties;
emit("cell-click", properties.cell);
};
map.value.on("click", "mosquito_source", handleClick);
map.value.on("click", "service_request", handleClick);
map.value.on("click", "trap", handleClick);
});
}
};
const jumpTo = (args: maplibregl.JumpToOptions) => {
if (map.value) {
map.value.jumpTo(args);
}
};
onMounted(() => {
setTimeout(() => initializeMap(), 0);
});
onUnmounted(() => {
if (map.value) {
map.value.remove();
map.value = null;
}
});
// Expose public methods
defineExpose({
jumpTo,
});
</script>