Still working on clustering.

This commit is contained in:
Eric van der Vlist 2023-01-18 22:10:23 +01:00
parent fce0b54574
commit 66fc287e5c
3 changed files with 262 additions and 120 deletions

View File

@ -1,3 +1,11 @@
{ {
"overrides": [{ "filename": "**/fr.*", "language": "fr" }] "overrides": [
{
"filename": "**/fr.*",
"language": "fr"
}
],
"words": [
"Clusterable"
]
} }

View File

@ -49,6 +49,8 @@ import VectorTileSource from 'ol/source/VectorTile.js';
import MVT from 'ol/format/MVT.js'; import MVT from 'ol/format/MVT.js';
import style from '../gpx/styles'; import style from '../gpx/styles';
import { DepartureBoard } from '@suid/icons-material'; import { DepartureBoard } from '@suid/icons-material';
import deTileVectorSource from '../../lib/de-tile-vector-source';
import ClusterableVectorTileSource from '../../lib/ClusterableVectorTileSource';
const [getState, setState] = createSignal({ const [getState, setState] = createSignal({
lon: 0, lon: 0,
@ -238,8 +240,7 @@ const Map: Component = () => {
zIndex: Infinity, zIndex: Infinity,
}); });
const vectorTileSource = new VectorTileSource({ const clusterableVectorTileSource = new ClusterableVectorTileSource({
format: new MVT({ featureClass: Feature }),
url: 'https://geo.dyomedea.com/services/spain/tiles/{z}/{x}/{y}.pbf', url: 'https://geo.dyomedea.com/services/spain/tiles/{z}/{x}/{y}.pbf',
maxZoom: 14, maxZoom: 14,
}); });
@ -247,11 +248,11 @@ const Map: Component = () => {
console.log({ console.log({
caller: 'Map / projections', caller: 'Map / projections',
vector: vectorLayer.getSource()?.getProjection(), vector: vectorLayer.getSource()?.getProjection(),
vectorTile: vectorTileSource.getProjection(), vectorTile: clusterableVectorTileSource.getProjection(),
}); });
const vectorTileLayer = new VectorTileLayer({ const vectorTileLayer = new VectorTileLayer({
source: vectorTileSource, source: clusterableVectorTileSource,
style: [], style: [],
declutter: false, declutter: false,
}); });
@ -262,7 +263,7 @@ const Map: Component = () => {
zoom: +getState().zoom, zoom: +getState().zoom,
rotation: +getState().rotation, rotation: +getState().rotation,
}), }),
layers: [tileLayer, vectorTileLayer, vectorLayer], layers: [tileLayer, vectorLayer, vectorTileLayer],
target: target, target: target,
controls: new Collection<Control>([ controls: new Collection<Control>([
new Attribution({ collapsible: true }), new Attribution({ collapsible: true }),
@ -285,114 +286,123 @@ const Map: Component = () => {
olMap.on(['moveend'], changeListener); olMap.on(['moveend'], changeListener);
olMap.on(['singleclick'], clickHandler); olMap.on(['singleclick'], clickHandler);
// cf https://stackoverflow.com/questions/55161380/openlayers-cluster-with-mvt-vectortilesource-impossible // // cf https://stackoverflow.com/questions/55161380/openlayers-cluster-with-mvt-vectortilesource-impossible
const vectorTileMirrorSource = new VectorSource(); // const vectorTileMirrorSource = new VectorSource();
let featuresForZ: Feature[] = []; // let featuresForZ: Feature[] = [];
let viewZ = vectorTileLayer // let viewZ = vectorTileLayer
.getSource() // .getSource()
.getTileGrid() // .getTileGrid()
.getZForResolution(olMap.getView().getResolution()); // .getZForResolution(olMap.getView().getResolution());
const vectorMirrorRefresh = () => { // const vectorMirrorRefresh = () => {
console.log({
caller: 'Map / Cluster / vectorMirrorRefresh',
olMap,
vectorMirrorLayer,
viewZ,
featuresForZ,
});
vectorTileMirrorSource.clear();
if (featuresForZ[viewZ]) {
vectorTileMirrorSource.addFeatures(featuresForZ[viewZ]);
// vectorMirrorLayer.getSource()?.refresh();
}
//vectorMirrorLayer.changed();
};
vectorTileLayer.getSource()?.on('tileloadend', (evt) => {
const z = evt.tile.getTileCoord()[0];
// const features = evt.tile.getFeatures();
// features.forEach((feature: Feature) => {
// feature.setId(undefined);
// });
const features = evt.tile
.getFeatures()
.filter((feature: Feature) => feature.get('type') === 'poi')
.map((feature: Feature) => {
const center = olExtent.getCenter(feature.getGeometry().getExtent());
const centerLonLat = toLonLat(
center,
olMap.getView().getProjection()
);
const newFeature = feature.clone();
newFeature.setGeometry(new Point(centerLonLat));
// console.log({ // console.log({
// caller: 'Map / Cluster / on tileloadend / new feature', // caller: 'Map / Cluster / vectorMirrorRefresh',
// feature, // olMap,
// center, // vectorMirrorLayer,
// centerLonLat, // viewZ,
// newFeature, // featuresForZ,
// }); // });
return newFeature; // vectorTileMirrorSource.clear();
}); // if (featuresForZ[viewZ]) {
if (!Array.isArray(featuresForZ[z])) { // vectorTileMirrorSource.addFeatures(featuresForZ[viewZ]);
featuresForZ[z] = []; // // vectorMirrorLayer.getSource()?.refresh();
} // }
featuresForZ[z] = featuresForZ[z].concat(features); // //vectorMirrorLayer.changed();
// evt.tile.setFeatures([]); // };
if (z === viewZ) {
vectorMirrorRefresh();
}
console.log({
caller: 'Map / Cluster / on tileloadend',
olMap,
z,
viewZ,
features,
vectorMirrorLayer,
featuresForZ,
});
});
olMap.getView().on('change:resolution', function () { // vectorTileLayer.getSource()?.on('tileloadend', (evt) => {
// use VT features from the tile z level corresponding to view resolution // const z = evt.tile.getTileCoord()[0];
const newZ = vectorTileLayer // // const features = evt.tile.getFeatures();
.getSource() // // features.forEach((feature: Feature) => {
.getTileGrid() // // feature.setId(undefined);
.getZForResolution(olMap.getView().getResolution()); // // });
console.log({ // const features = evt.tile
caller: 'Map / Cluster / on change:resolution', // .getFeatures()
olMap, // .filter((feature: Feature) => feature.get('type') === 'poi')
newZ, // .map((feature: Feature) => {
viewZ, // const center = olExtent.getCenter(feature.getGeometry().getExtent());
vectorMirrorLayer, // const centerLonLat = toLonLat(
featuresForZ, // center,
}); // olMap.getView().getProjection()
if (newZ !== viewZ) { // );
viewZ = newZ; // const newFeature = feature.clone();
vectorMirrorRefresh(); // newFeature.setGeometry(new Point(centerLonLat));
} // // console.log({
}); // // caller: 'Map / Cluster / on tileloadend / new feature',
// // feature,
// // center,
// // centerLonLat,
// // newFeature,
// // });
// return newFeature;
// });
// if (!Array.isArray(featuresForZ[z])) {
// featuresForZ[z] = [];
// }
// featuresForZ[z] = featuresForZ[z].concat(features);
// // evt.tile.setFeatures([]);
// if (z === viewZ) {
// vectorMirrorRefresh();
// }
// console.log({
// caller: 'Map / Cluster / on tileloadend',
// olMap,
// z,
// viewZ,
// features,
// vectorMirrorLayer,
// featuresForZ,
// });
// });
let vectorMirrorLayer = new VectorLayer({ // olMap.getView().on('change:resolution', function () {
// // use VT features from the tile z level corresponding to view resolution
// const newZ = vectorTileLayer
// .getSource()
// .getTileGrid()
// .getZForResolution(olMap.getView().getResolution());
// console.log({
// caller: 'Map / Cluster / on change:resolution',
// olMap,
// newZ,
// viewZ,
// vectorMirrorLayer,
// featuresForZ,
// });
// if (newZ !== viewZ) {
// viewZ = newZ;
// vectorMirrorRefresh();
// }
// });
let clusterLayer = new VectorLayer({
// source: vectorTileMirrorSource, // source: vectorTileMirrorSource,
source: new Cluster({ source: new Cluster({
source: vectorTileMirrorSource, // source: deTileVectorSource(vectorLayer.getSource()),
// geometryFunction: (feature: Feature) => { source: clusterableVectorTileSource,
// // console.log({ geometryFunction: (feature: Feature) => {
// // caller: 'Map / Cluster / geometryFunction', // console.log({
// // feature, // caller: 'Map / Cluster / geometryFunction',
// // }); // feature,
// // test data is linestrings // });
// // return new Point( // test data is linestrings
// // olExtent.getCenter(feature.getGeometry().getExtent()) // return new Point(
// // ); // olExtent.getCenter(feature.getGeometry().getExtent())
// if (feature.get('type') === 'poi') { // );
// return feature.getGeometry(); if (feature.get('type') === 'poi') {
// } return new Point(
// return null; olExtent.getCenter(feature.getGeometry().getExtent())
// }, );
// return new Point(
// toLonLat(
// olExtent.getCenter(feature.getGeometry().getExtent()),
// olMap.getView().getProjection()
// )
// );
}
return null;
},
createCluster: (point: Point, features: Feature[]) => { createCluster: (point: Point, features: Feature[]) => {
// console.log({ // console.log({
// caller: 'Map / Cluster / createCluster', // caller: 'Map / Cluster / createCluster',
@ -403,15 +413,21 @@ const Map: Component = () => {
//return features[0]; //return features[0];
return new Feature({ return new Feature({
geometry: point, geometry: new Point(
toLonLat(
olExtent.getCenter(point.getExtent()),
olMap.getView().getProjection()
)
),
features: features, features: features,
type: 'cluster',
}); });
}, },
distance: 100, distance: 100000,
minDistance: 10, minDistance: 0,
}), }),
zIndex: Infinity, zIndex: Infinity,
style, // style,
style: function (feature) { style: function (feature) {
// console.log({ // console.log({
// caller: 'Map / Cluster / style', // caller: 'Map / Cluster / style',
@ -431,19 +447,25 @@ const Map: Component = () => {
// }), // }),
// }), // }),
}); });
olMap.addLayer(vectorMirrorLayer); olMap.addLayer(clusterLayer);
setMap(olMap);
console.log({ console.log({
caller: 'Map / projections', caller: 'Map / projections',
olMap, olMap,
projections: {
map: olMap.getView().getProjection(), map: olMap.getView().getProjection(),
vectorSource: vectorLayer.getSource()?.getProjection(), vectorSource: vectorLayer.getSource()?.getProjection(),
vectorTileMirrorSource: vectorTileMirrorSource.getProjection(), clusterSource: clusterLayer.getSource().getProjection(),
vectorTileSource: vectorTileSource.getProjection(), clusterSourceSource: clusterLayer
clusterSource: vectorMirrorLayer.getSource()?.getProjection(), .getSource()
?.getSource()
.getProjection(),
},
}); });
setMap(olMap);
}); });
return ( return (

View File

@ -0,0 +1,112 @@
import { Feature } from 'ol';
import { equals, Extent } from 'ol/extent';
import MVT from 'ol/format/MVT';
import { fromLonLat, Projection } from 'ol/proj';
import VectorTileSource from 'ol/source/VectorTile.js';
class ClusterableVectorTileSource extends VectorTileSource {
extent: Extent | null = null;
extentProjected: Extent | null = null;
featuresSent: boolean = false;
changeHandler = () => {
this.featuresSent = false;
console.log({
caller: 'ClusterableVectorTileSource',
method: 'changeHandler',
this: this,
});
};
constructor(properties: any) {
properties.format = new MVT({ featureClass: Feature });
super(properties);
this.addEventListener('change', this.changeHandler);
console.log({
caller: 'ClusterableVectorTileSource',
method: 'constructor',
this: this,
});
}
loadFeatures = (
extent: Extent,
resolution: number,
projection: Projection
) => {
console.log({
caller: 'ClusterableVectorTileSource',
method: 'loadFeatures',
extent,
resolution,
projection,
this: this,
});
if (this.extent === null || !equals(this.extent, extent)) {
this.extent = extent;
if (this.projection != null) {
this.extentProjected = fromLonLat(
extent.slice(0, 2),
this.projection
).concat(fromLonLat(extent.slice(2), this.projection));
}
}
if (!this.featuresSent) {
super.dispatchEvent('change');
}
this.featuresSent = false;
};
getFeatures = () => {
const result =
this.extentProjected !== null
? super.getFeaturesInExtent(this.extentProjected)
: [];
console.log({
caller: 'ClusterableVectorTileSource',
method: 'getFeatures',
result,
this: this,
});
//console.trace();
this.featuresSent = true;
return result;
};
getFeaturesInExtent = (extent: Extent) => {
const features = super.getFeaturesInExtent(extent);
console.log({
caller: 'ClusterableVectorTileSource',
method: 'getFeaturesInExtent',
extent,
features,
this: this,
});
return features;
// if (this.projection === null) {
// return super.getFeaturesInExtent(extent);
// }
// return super.getFeaturesInExtent(
// (this.extentProjected = fromLonLat(
// extent.slice(0, 2),
// this.projection
// ).concat(fromLonLat(extent.slice(2), this.projection)))
// );
};
addEventListener = (type: string, listener: any) => {
console.log({
caller: 'ClusterableVectorTileSource',
method: 'addEventListener',
type,
listener,
this: this,
});
super.addEventListener(type, listener);
if (type === 'change') {
super.addEventListener('tileloadend', listener);
}
};
}
export default ClusterableVectorTileSource;