Initial working genericized map implementation
This shows dynamically adding layers and sources and actually reads from them!
This commit is contained in:
parent
c6282c9f5e
commit
cad01e689e
4 changed files with 320 additions and 8 deletions
68
ts/map/Layer.vue
Normal file
68
ts/map/Layer.vue
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<template>
|
||||
<!-- Renderless component -->
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import maplibregl from "maplibre-gl";
|
||||
import { inject, onMounted, onBeforeUnmount, Ref, watch } from "vue";
|
||||
|
||||
type LayerType = maplibregl.LayerSpecification["type"];
|
||||
export interface Props {
|
||||
filter?: maplibregl.FilterSpecification;
|
||||
id: string;
|
||||
paint: Object;
|
||||
source: string;
|
||||
sourceLayer: string;
|
||||
type: LayerType;
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {});
|
||||
|
||||
type RegisterLayerFunc = (id: string, config: any) => void;
|
||||
type UnregisterLayerFunc = (id: string) => void;
|
||||
const map: Ref<maplibregl.Map | null> | undefined = inject("map");
|
||||
const registerLayer: RegisterLayerFunc | undefined = inject("registerLayer");
|
||||
const unregisterLayer: UnregisterLayerFunc | undefined =
|
||||
inject("unregisterLayer");
|
||||
|
||||
const getLayerConfig = (): maplibregl.LayerSpecification => {
|
||||
let result: maplibregl.LayerSpecification = {
|
||||
id: props.id,
|
||||
source: props.source,
|
||||
"source-layer": props.sourceLayer,
|
||||
type: props.type,
|
||||
...(props.filter && { filter: props.filter }),
|
||||
...(props.paint && { paint: props.paint }),
|
||||
} as maplibregl.LayerSpecification;
|
||||
return result;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (registerLayer) {
|
||||
registerLayer(props.id, getLayerConfig());
|
||||
} else {
|
||||
console.log("registerLayer is nully");
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (unregisterLayer) {
|
||||
unregisterLayer(props.id);
|
||||
} else {
|
||||
console.log("unregisterLayer is nully");
|
||||
}
|
||||
});
|
||||
|
||||
// Update paint/layout properties reactively
|
||||
watch(
|
||||
() => props.paint,
|
||||
(newPaint) => {
|
||||
if (map && map.value?.getLayer(props.id)) {
|
||||
Object.entries(newPaint || {}).forEach(([key, value]) => {
|
||||
if (!(map && map.value)) return;
|
||||
map.value.setPaintProperty(props.id, key, value);
|
||||
});
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
</script>
|
||||
125
ts/map/Map.vue
Normal file
125
ts/map/Map.vue
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
<style scoped>
|
||||
.map {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div ref="mapDiv" class="map" v-bind="$attrs"></div>
|
||||
<slot />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import "maplibre-gl/dist/maplibre-gl.css";
|
||||
import maplibregl from "maplibre-gl";
|
||||
import {
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
provide,
|
||||
ref,
|
||||
type Ref,
|
||||
shallowRef,
|
||||
} from "vue";
|
||||
|
||||
import type { Bounds } from "@/type/api";
|
||||
|
||||
interface Props {
|
||||
bounds?: Bounds;
|
||||
center?: maplibregl.LngLatLike;
|
||||
zoom?: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {});
|
||||
|
||||
const mapDiv = ref<HTMLElement | null>(null);
|
||||
const map: Ref<maplibregl.Map | null> = shallowRef(null);
|
||||
|
||||
// Provide the map instance to children
|
||||
provide("map", map);
|
||||
|
||||
// Registry for tracking child components
|
||||
const sources = new Map();
|
||||
const layers = new Map();
|
||||
|
||||
provide("registerSource", (id: string, config: any) => {
|
||||
console.log("register source", id, config);
|
||||
sources.set(id, config);
|
||||
if (map.value && map.value.loaded()) {
|
||||
if (!map.value.getSource(id)) {
|
||||
map.value.addSource(id, config);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
provide("unregisterSource", (id: string) => {
|
||||
console.log("unregister source", id);
|
||||
sources.delete(id);
|
||||
if (map.value && map.value?.getSource(id)) {
|
||||
map.value.removeSource(id);
|
||||
}
|
||||
});
|
||||
|
||||
provide("registerLayer", (id: string, config: any) => {
|
||||
console.log("register layer", id, config);
|
||||
layers.set(id, config);
|
||||
if (map.value && map.value.loaded()) {
|
||||
if (!map.value.getLayer(id)) {
|
||||
map.value.addLayer(config);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
provide("unregisterLayer", (id: string) => {
|
||||
console.log("unregister layer", id);
|
||||
layers.delete(id);
|
||||
if (map.value?.getLayer(id)) {
|
||||
map.value.removeLayer(id);
|
||||
}
|
||||
});
|
||||
|
||||
function initializeMap() {
|
||||
if (!mapDiv.value) return;
|
||||
|
||||
console.log("initializing map...");
|
||||
const _map = new maplibregl.Map({
|
||||
container: mapDiv.value,
|
||||
center: props.center,
|
||||
style: "https://tiles.stadiamaps.com/styles/alidade_smooth.json",
|
||||
zoom: props.zoom,
|
||||
});
|
||||
|
||||
// When map loads, add all registered sources/layers
|
||||
_map.on("load", () => {
|
||||
console.log("map loaded.");
|
||||
sources.forEach((config, id) => {
|
||||
console.log("adding source", id, config);
|
||||
if (!_map.getSource(id)) {
|
||||
_map.addSource(id, config);
|
||||
}
|
||||
});
|
||||
|
||||
layers.forEach((config, id) => {
|
||||
console.log("adding layer", id, config);
|
||||
if (!_map.getLayer(id)) {
|
||||
_map.addLayer(config);
|
||||
}
|
||||
});
|
||||
});
|
||||
map.value = _map;
|
||||
}
|
||||
onMounted(() => {
|
||||
initializeMap();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (map.value) {
|
||||
map.value.remove();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
46
ts/map/Source.vue
Normal file
46
ts/map/Source.vue
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<template>
|
||||
<!-- Renderless component -->
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { inject, onMounted, onBeforeUnmount, watch } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
id: { type: String, required: true },
|
||||
type: { type: String, required: true },
|
||||
tiles: Array,
|
||||
url: String,
|
||||
// ... other source properties
|
||||
});
|
||||
|
||||
const map = inject("map");
|
||||
const registerSource = inject("registerSource");
|
||||
const unregisterSource = inject("unregisterSource");
|
||||
|
||||
const getSourceConfig = () => {
|
||||
const { id, ...config } = props;
|
||||
return config;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
registerSource(props.id, getSourceConfig());
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
unregisterSource(props.id);
|
||||
});
|
||||
|
||||
// Watch for prop changes and update source
|
||||
watch(
|
||||
() => getSourceConfig(),
|
||||
(newConfig) => {
|
||||
if (map.value?.getSource(props.id)) {
|
||||
// MapLibre doesn't support updating sources directly
|
||||
// You'd need to remove and re-add, or handle specific updates
|
||||
unregisterSource(props.id);
|
||||
registerSource(props.id, newConfig);
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue