import { Store, Stores } from "../../UIEventSource" import { Tiles } from "../../../Models/TileRange" import { BBox } from "../../BBox" import { FeatureSource, FeatureSourceForTile } from "../FeatureSource" import FeatureSourceMerger from "../Sources/FeatureSourceMerger" import { Feature } from "geojson" import { Utils } from "../../../Utils" import { GeoOperations } from "../../GeoOperations" /*** * A tiled source which dynamically loads the required tiles at a fixed zoom level. * A single featureSource will be initialized for every tile in view; which will later be merged into this featureSource */ export default class DynamicTileSource extends FeatureSourceMerger { /** * * @param zoomlevel If {z} is specified in the source, the 'zoomlevel' will be used as zoomlevel to download from * @param minzoom Only activate this feature source if zoomed in further then this * @param constructSource * @param mapProperties * @param options */ constructor( zoomlevel: Store, minzoom: number, constructSource: (tileIndex: number) => Src, mapProperties: { bounds: Store zoom: Store }, options?: { isActive?: Store }, ) { super() const loadedTiles = new Set() const neededTiles: Store = Stores.ListStabilized( mapProperties.bounds .mapD( (bounds) => { if (options?.isActive && !options?.isActive.data) { return undefined } if (mapProperties.zoom.data < minzoom) { return undefined } const z = Math.floor(zoomlevel.data) const tileRange = Tiles.TileRangeBetween( z, bounds.getNorth(), bounds.getEast(), bounds.getSouth(), bounds.getWest(), ) if (tileRange.total > 500) { console.warn( "Got a really big tilerange, bounds and location might be out of sync", ) return undefined } const needed = Tiles.MapRange(tileRange, (x, y) => Tiles.tile_index(z, x, y), ).filter((i) => !loadedTiles.has(i)) if (needed.length === 0) { return undefined } return needed }, [options?.isActive, mapProperties.zoom], ) .stabilized(250), ) neededTiles.addCallbackAndRunD((neededIndexes) => { for (const neededIndex of neededIndexes) { loadedTiles.add(neededIndex) super.addSource(constructSource(neededIndex)) } }) } } /** * The PolygonSourceMerger receives various small pieces of bigger polygons and stitches them together. * This is used to reconstruct polygons of vector tiles */ export class PolygonSourceMerger extends DynamicTileSource { constructor( zoomlevel: Store, minzoom: number, constructSource: (tileIndex: number) => FeatureSourceForTile, mapProperties: { bounds: Store zoom: Store }, options?: { isActive?: Store }, ) { super(zoomlevel, minzoom, constructSource, mapProperties, options) } protected addDataFromSources(sources: FeatureSourceForTile[]) { sources = Utils.NoNull(sources) const all: Map = new Map() const zooms: Map = new Map() for (const source of sources) { let z = source.z for (const f of source.features.data) { const id = f.properties.id if(id.endsWith("146616907")){ console.log("Horeca totaal") } if (!all.has(id)) { // No other parts of this polygon have been seen before, simply add it all.set(id, f) zooms.set(id, z) continue } // A part of this object has been seen before, eventually from a different zoom level const oldV = all.get(id) const oldZ = zooms.get(id) if (oldZ > z) { // The store contains more detailed information, so we ignore this part which has a lower accuraccy continue } if (oldZ < z) { // The old value has worse accuracy then what we receive now, we throw it away all.set(id, f) zooms.set(id, z) continue } const merged = GeoOperations.union(f, oldV) merged.properties = oldV.properties all.set(id, merged) zooms.set(id, z) } } const newList = Array.from(all.values()) this.features.setData(newList) this._featuresById.setData(all) } }