diff --git a/src/components/map/Map.tsx b/src/components/map/Map.tsx index 0d5b006..e0f9a10 100644 --- a/src/components/map/Map.tsx +++ b/src/components/map/Map.tsx @@ -51,6 +51,7 @@ import style from '../gpx/styles'; import { DepartureBoard } from '@suid/icons-material'; import deTileVectorSource from '../../lib/de-tile-vector-source'; import ClusterableVectorTileSource from '../../lib/ClusterableVectorTileSource'; +import clusterableVectorTileSourceProxy from '../../lib/ClusterableVectorTileSourceProxy'; const [getState, setState] = createSignal({ lon: 0, @@ -240,22 +241,11 @@ const Map: Component = () => { zIndex: Infinity, }); - const clusterableVectorTileSource = new ClusterableVectorTileSource({ - url: 'https://geo.dyomedea.com/services/spain/tiles/{z}/{x}/{y}.pbf', - maxZoom: 14, - }); - - console.log({ - caller: 'Map / projections', - vector: vectorLayer.getSource()?.getProjection(), - vectorTile: clusterableVectorTileSource.getProjection(), - }); - - const vectorTileLayer = new VectorTileLayer({ - source: clusterableVectorTileSource, - style: [], - declutter: false, - }); + // console.log({ + // caller: 'Map / projections', + // vector: vectorLayer.getSource()?.getProjection(), + // vectorTile: clusterableVectorTileSource.getProjection(), + // }); const olMap = new OlMap({ view: new View({ @@ -263,7 +253,7 @@ const Map: Component = () => { zoom: +getState().zoom, rotation: +getState().rotation, }), - layers: [tileLayer, vectorLayer, vectorTileLayer], + layers: [tileLayer, vectorLayer], target: target, controls: new Collection([ new Attribution({ collapsible: true }), @@ -376,6 +366,26 @@ const Map: Component = () => { // } // }); + const vectorTileSource = new VectorTileSource({ + url: 'https://geo.dyomedea.com/services/spain/tiles/{z}/{x}/{y}.pbf', + format: new MVT({ featureClass: Feature }), + maxZoom: 14, + }); + + const vectorTileLayer = new VectorTileLayer({ + source: vectorTileSource, + style: [], + declutter: false, + }); + + const clusterableVectorTileSource = + clusterableVectorTileSourceProxy(vectorTileSource); + + clusterableVectorTileSource.init({ + featureFilter: (feature: Feature, resolution?: number) => + feature.get('type') === 'poi', + }); + let clusterLayer = new VectorLayer({ // source: vectorTileMirrorSource, source: new Cluster({ @@ -419,6 +429,7 @@ const Map: Component = () => { olMap.getView().getProjection() ) ), + // point, features: features, type: 'cluster', }); @@ -447,25 +458,25 @@ const Map: Component = () => { // }), // }), }); - olMap.addLayer(clusterLayer); - + olMap.addLayer(vectorTileLayer); + olMap.addLayer(clusterLayer); setMap(olMap); - console.log({ - caller: 'Map / projections', - olMap, - projections: { - map: olMap.getView().getProjection(), - vectorSource: vectorLayer.getSource()?.getProjection(), - clusterSource: clusterLayer.getSource().getProjection(), - clusterSourceSource: clusterLayer - .getSource() - ?.getSource() - .getProjection(), - }, - }); + // console.log({ + // caller: 'Map / projections', + // olMap, + // projections: { + // map: olMap.getView().getProjection(), + // vectorSource: vectorLayer.getSource()?.getProjection(), + // clusterSource: clusterLayer.getSource().getProjection(), + // clusterSourceSource: clusterLayer + // .getSource() + // ?.getSource() + // .getProjection(), + // }, + // }); }); return ( diff --git a/src/lib/ClusterableVectorTileSourceProxy.ts b/src/lib/ClusterableVectorTileSourceProxy.ts new file mode 100644 index 0000000..870d7d3 --- /dev/null +++ b/src/lib/ClusterableVectorTileSourceProxy.ts @@ -0,0 +1,142 @@ +import { cloneDeep } from 'lodash'; +import { Feature } from 'ol'; +import { equals, Extent } from 'ol/extent'; +import { fromLonLat, Projection } from 'ol/proj'; +import VectorTileSource from 'ol/source/VectorTile.js'; + +const CLUSTER_PROPS = '[cluster props]'; + +type FeatureFilter = ( + feature: Feature, + resolution: number | undefined +) => boolean; + +const passAllFeatureFilter = (feature: Feature, resolution?: number) => true; + +interface Props { + extent: Extent | null; + resolution?: number; + extentProjected: Extent | null; + featuresSent: boolean; + featureFilter: FeatureFilter; +} + +interface Options { + featureFilter?: FeatureFilter; +} + +const initialProps = { + extent: null, + extentProjected: null, + featuresSent: false, + featureFilter: passAllFeatureFilter, +}; + +const getProps = (vectorTileSource: VectorTileSource) => + vectorTileSource.get(CLUSTER_PROPS) as Props; + +const setProps = (vectorTileSource: VectorTileSource, props: Props) => + vectorTileSource.set(CLUSTER_PROPS, props); + +const overloads = { + init: (target: VectorTileSource) => { + return (options?: Options) => { + let props = cloneDeep(initialProps); + if (options) { + props = { ...props, ...options }; + } + setProps(target, props); + }; + }, + + loadFeatures: (target: VectorTileSource) => { + return (extent: Extent, resolution: number, projection: Projection) => { + let props = getProps(target); + props.resolution = resolution; + if (props.extent === null || !equals(props.extent, extent)) { + props.extent = extent; + if (target.getProjection() != null) { + props.extentProjected = fromLonLat( + extent.slice(0, 2), + target.getProjection() + ).concat(fromLonLat(extent.slice(2), props.projection)); + } + } + if (!props.featuresSent) { + target.dispatchEvent('change'); + } + props.featuresSent = false; + setProps(target, props); + console.log({ + caller: 'clusterableVectorTileSourceProxy', + overload: 'loadFeatures', + extent, + resolution, + projection, + target: target, + props, + }); + }; + }, + + getFeatures: (target: VectorTileSource) => { + return () => { + let props = getProps(target); + const result = ( + props.extentProjected !== null + ? target.getFeaturesInExtent(props.extentProjected) + : [] + ).filter((feature) => props.featureFilter(feature, props.resolution)); + //console.trace(); + props.featuresSent = true; + setProps(target, props); + console.log({ + caller: 'clusterableVectorTileSourceProxy', + overload: 'getFeatures', + result, + props, + target, + }); + return result; + }; + }, + + addEventListener: (target: VectorTileSource) => { + return (type: string, listener: any) => { + console.log({ + caller: 'clusterableVectorTileSourceProxy', + overload: 'addEventListener', + type, + listener, + target, + }); + target.addEventListener(type, listener); + if (type === 'change') { + target.addEventListener('tileloadend', listener); + } + }; + }, +}; + +const clusterableVectorTileSourceProxy = (vectorTileSource: VectorTileSource) => + new Proxy(vectorTileSource, { + get: (target, prop, receiver) => { + let result; + if (prop in overloads) { + result = overloads[prop as keyof typeof overloads](target); + } else { + result = Reflect.get(target, prop, receiver); + } + // console.log({ + // caller: 'clusterableVectorTileSourceProxy', + // trap: 'get', + // target, + // prop, + // receiver, + // result, + // }); + return result; + }, + }); + +export default clusterableVectorTileSourceProxy;