forked from MapComplete/MapComplete
		
	Feature: more or less working version of clustering, clusters of multiple layers are joined
This commit is contained in:
		
							parent
							
								
									5bc8f11d24
								
							
						
					
					
						commit
						0048c091d0
					
				
					 9 changed files with 143 additions and 65 deletions
				
			
		|  | @ -1,25 +1,27 @@ | ||||||
| import { Store, UIEventSource } from "../UIEventSource" | import { Store, UIEventSource } from "../UIEventSource" | ||||||
| import FilteredLayer from "../../Models/FilteredLayer" | import FilteredLayer from "../../Models/FilteredLayer" | ||||||
| import { Feature } from "geojson" | import { Feature, Geometry } from "geojson" | ||||||
|  | import { OsmTags } from "../../Models/OsmFeature" | ||||||
| 
 | 
 | ||||||
| export interface FeatureSource<T extends Feature = Feature> { | export interface FeatureSource<T extends Feature = Feature<Geometry, OsmTags>> { | ||||||
|     features: Store<T[]> |     features: Store<T[]> | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export interface UpdatableFeatureSource<T extends Feature = Feature> extends FeatureSource<T> { | export interface UpdatableFeatureSource<T extends Feature = Feature<Geometry, OsmTags>> extends FeatureSource<T> { | ||||||
|     /** |     /** | ||||||
|      * Forces an update and downloads the data, even if the feature source is supposed to be active |      * Forces an update and downloads the data, even if the feature source is supposed to be active | ||||||
|      */ |      */ | ||||||
|     updateAsync() |     updateAsync(): void | ||||||
| } | } | ||||||
| export interface WritableFeatureSource<T extends Feature = Feature> extends FeatureSource<T> { | 
 | ||||||
|  | export interface WritableFeatureSource<T extends Feature = Feature<Geometry, OsmTags>> extends FeatureSource<T> { | ||||||
|     features: UIEventSource<T[]> |     features: UIEventSource<T[]> | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * A feature source which only contains features for the defined layer |  * A feature source which only contains features for the defined layer | ||||||
|  */ |  */ | ||||||
| export interface FeatureSourceForLayer<T extends Feature = Feature> extends FeatureSource<T> { | export interface FeatureSourceForLayer<T extends Feature = Feature<Geometry, OsmTags>> extends FeatureSource<T> { | ||||||
|     readonly layer: FilteredLayer |     readonly layer: FilteredLayer | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -11,17 +11,17 @@ import { UIEventSource } from "../UIEventSource" | ||||||
|  * If this is the case, multiple objects with a different _matching_layer_id are generated. |  * If this is the case, multiple objects with a different _matching_layer_id are generated. | ||||||
|  * In any case, this featureSource marks the objects with _matching_layer_id |  * In any case, this featureSource marks the objects with _matching_layer_id | ||||||
|  */ |  */ | ||||||
| export default class PerLayerFeatureSourceSplitter<T extends FeatureSource = FeatureSource> { | export default class PerLayerFeatureSourceSplitter<T extends Feature, SRC extends FeatureSource<T>> { | ||||||
|     public readonly perLayer: ReadonlyMap<string, T> |     public readonly perLayer: ReadonlyMap<string, SRC> | ||||||
|     constructor( |     constructor( | ||||||
|         layers: FilteredLayer[], |         layers: FilteredLayer[], | ||||||
|         upstream: FeatureSource, |         upstream: FeatureSource<T>, | ||||||
|         options?: { |         options?: { | ||||||
|             constructStore?: (features: UIEventSource<Feature[]>, layer: FilteredLayer) => T |             constructStore?: (features: UIEventSource<T[]>, layer: FilteredLayer) => SRC | ||||||
|             handleLeftovers?: (featuresWithoutLayer: Feature[]) => void |             handleLeftovers?: (featuresWithoutLayer: T[]) => void | ||||||
|         } |         } | ||||||
|     ) { |     ) { | ||||||
|         const knownLayers = new Map<string, T>() |         const knownLayers = new Map<string, SRC>() | ||||||
|         /** |         /** | ||||||
|          * Keeps track of the ids that are included per layer. |          * Keeps track of the ids that are included per layer. | ||||||
|          * Used to know if the downstream feature source needs to be pinged |          * Used to know if the downstream feature source needs to be pinged | ||||||
|  | @ -32,12 +32,12 @@ export default class PerLayerFeatureSourceSplitter<T extends FeatureSource = Fea | ||||||
|         const constructStore = |         const constructStore = | ||||||
|             options?.constructStore ?? ((store, layer) => new SimpleFeatureSource(layer, store)) |             options?.constructStore ?? ((store, layer) => new SimpleFeatureSource(layer, store)) | ||||||
|         for (const layer of layers) { |         for (const layer of layers) { | ||||||
|             const src = new UIEventSource<Feature[]>([]) |             const src = new UIEventSource<T[]>([]) | ||||||
|             layerSources.set(layer.layerDef.id, src) |             layerSources.set(layer.layerDef.id, src) | ||||||
|             knownLayers.set(layer.layerDef.id, <T>constructStore(src, layer)) |             knownLayers.set(layer.layerDef.id, <SRC>constructStore(src, layer)) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         upstream.features.addCallbackAndRunD((features) => { |         upstream.features.addCallbackAndRunD((features: T[]) => { | ||||||
|             if (layers === undefined) { |             if (layers === undefined) { | ||||||
|                 return |                 return | ||||||
|             } |             } | ||||||
|  | @ -51,7 +51,7 @@ export default class PerLayerFeatureSourceSplitter<T extends FeatureSource = Fea | ||||||
|              */ |              */ | ||||||
|             const hasChanged: boolean[] = layers.map(() => false) |             const hasChanged: boolean[] = layers.map(() => false) | ||||||
|             const newIndices: Set<string>[] = layers.map(() => new Set<string>()) |             const newIndices: Set<string>[] = layers.map(() => new Set<string>()) | ||||||
|             const noLayerFound: Feature[] = [] |             const noLayerFound: T[] = [] | ||||||
| 
 | 
 | ||||||
|             for (const layer of layers) { |             for (const layer of layers) { | ||||||
|                 featuresPerLayer.set(layer.layerDef.id, []) |                 featuresPerLayer.set(layer.layerDef.id, []) | ||||||
|  | @ -115,7 +115,7 @@ export default class PerLayerFeatureSourceSplitter<T extends FeatureSource = Fea | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public forEach(f: (featureSource: T) => void) { |     public forEach(f: ((src: SRC) => void)) { | ||||||
|         for (const fs of this.perLayer.values()) { |         for (const fs of this.perLayer.values()) { | ||||||
|             f(fs) |             f(fs) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -1,24 +1,19 @@ | ||||||
| import { Feature as GeojsonFeature, Geometry } from "geojson" | import { Feature, Feature as GeojsonFeature, Geometry } from "geojson" | ||||||
| 
 | 
 | ||||||
| import { Store, UIEventSource } from "../../UIEventSource" | import { Store, UIEventSource } from "../../UIEventSource" | ||||||
| import { FeatureSourceForTile, UpdatableFeatureSource } from "../FeatureSource" | import { FeatureSourceForTile, UpdatableFeatureSource } from "../FeatureSource" | ||||||
| import { MvtToGeojson } from "mvt-to-geojson" | import { MvtToGeojson } from "mvt-to-geojson" | ||||||
|  | import { OsmTags } from "../../../Models/OsmFeature" | ||||||
| 
 | 
 | ||||||
| export default class MvtSource implements FeatureSourceForTile, UpdatableFeatureSource { | export default class MvtSource implements FeatureSourceForTile, UpdatableFeatureSource { | ||||||
|     public readonly features: Store<GeojsonFeature<Geometry, { [name: string]: any }>[]> |     public readonly features: Store<GeojsonFeature<Geometry, OsmTags>[]> | ||||||
|     public readonly x: number |     public readonly x: number | ||||||
|     public readonly y: number |     public readonly y: number | ||||||
|     public readonly z: number |     public readonly z: number | ||||||
|     private readonly _url: string |     private readonly _url: string | ||||||
|     private readonly _layerName: string |  | ||||||
|     private readonly _features: UIEventSource< |     private readonly _features: UIEventSource< | ||||||
|         GeojsonFeature< |         GeojsonFeature<Geometry, OsmTags>[] | ||||||
|             Geometry, |     > = new UIEventSource<GeojsonFeature<Geometry, OsmTags>[]>([]) | ||||||
|             { |  | ||||||
|                 [name: string]: any |  | ||||||
|             } |  | ||||||
|         >[] |  | ||||||
|     > = new UIEventSource<GeojsonFeature<Geometry, { [p: string]: any }>[]>([]) |  | ||||||
|     private currentlyRunning: Promise<any> |     private currentlyRunning: Promise<any> | ||||||
| 
 | 
 | ||||||
|     constructor( |     constructor( | ||||||
|  | @ -26,11 +21,9 @@ export default class MvtSource implements FeatureSourceForTile, UpdatableFeature | ||||||
|         x: number, |         x: number, | ||||||
|         y: number, |         y: number, | ||||||
|         z: number, |         z: number, | ||||||
|         layerName?: string, |  | ||||||
|         isActive?: Store<boolean> |         isActive?: Store<boolean> | ||||||
|     ) { |     ) { | ||||||
|         this._url = url |         this._url = url | ||||||
|         this._layerName = layerName |  | ||||||
|         this.x = x |         this.x = x | ||||||
|         this.y = y |         this.y = y | ||||||
|         this.z = z |         this.z = z | ||||||
|  | @ -61,7 +54,7 @@ export default class MvtSource implements FeatureSourceForTile, UpdatableFeature | ||||||
|                 return |                 return | ||||||
|             } |             } | ||||||
|             const buffer = await result.arrayBuffer() |             const buffer = await result.arrayBuffer() | ||||||
|             const features = MvtToGeojson.fromBuffer(buffer, this.x, this.y, this.z) |             const features = <Feature<Geometry, OsmTags>[]>MvtToGeojson.fromBuffer(buffer, this.x, this.y, this.z) | ||||||
|             for (const feature of features) { |             for (const feature of features) { | ||||||
|                 const properties = feature.properties |                 const properties = feature.properties | ||||||
|                 if (!properties["osm_type"]) { |                 if (!properties["osm_type"]) { | ||||||
|  |  | ||||||
|  | @ -1,14 +1,15 @@ | ||||||
| import { UIEventSource } from "../../UIEventSource" | import { UIEventSource } from "../../UIEventSource" | ||||||
| import FilteredLayer from "../../../Models/FilteredLayer" | import FilteredLayer from "../../../Models/FilteredLayer" | ||||||
| import { FeatureSourceForLayer } from "../FeatureSource" | import { FeatureSourceForLayer } from "../FeatureSource" | ||||||
| import { Feature } from "geojson" | import { Feature, Geometry } from "geojson" | ||||||
|  | import { OsmTags } from "../../../Models/OsmFeature" | ||||||
| 
 | 
 | ||||||
| export default class SimpleFeatureSource implements FeatureSourceForLayer { | export default class SimpleFeatureSource<T extends Feature = Feature<Geometry, OsmTags>> implements FeatureSourceForLayer<T> { | ||||||
|     public readonly features: UIEventSource<Feature[]> |     public readonly features: UIEventSource<T[]> | ||||||
|     public readonly layer: FilteredLayer |     public readonly layer: FilteredLayer | ||||||
| 
 | 
 | ||||||
|     constructor(layer: FilteredLayer, featureSource?: UIEventSource<Feature[]>) { |     constructor(layer: FilteredLayer, featureSource?: UIEventSource<T[]>) { | ||||||
|         this.layer = layer |         this.layer = layer | ||||||
|         this.features = featureSource ?? new UIEventSource<Feature[]>([]) |         this.features = featureSource ?? new UIEventSource<T[]>([]) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -2,7 +2,6 @@ import { FeatureSource } from "../FeatureSource" | ||||||
| import { Feature, Point } from "geojson" | import { Feature, Point } from "geojson" | ||||||
| import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource" | import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource" | ||||||
| import { GeoOperations } from "../../GeoOperations" | import { GeoOperations } from "../../GeoOperations" | ||||||
| import StaticFeatureSource from "../Sources/StaticFeatureSource" |  | ||||||
| import { Tiles } from "../../../Models/TileRange" | import { Tiles } from "../../../Models/TileRange" | ||||||
| 
 | 
 | ||||||
| export interface ClusteringOptions { | export interface ClusteringOptions { | ||||||
|  | @ -19,9 +18,14 @@ export interface ClusteringOptions { | ||||||
|     showSummaryAt?: "tilecenter" | "average" |     showSummaryAt?: "tilecenter" | "average" | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | interface SummaryProperties { | ||||||
|  |     id: string, | ||||||
|  |     total: number, | ||||||
|  |     tile_id: number | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export class ClusteringFeatureSource<T extends Feature<Point> = Feature<Point>> implements FeatureSource<T> { | export class ClusteringFeatureSource<T extends Feature<Point> = Feature<Point>> implements FeatureSource<T> { | ||||||
| 
 | 
 | ||||||
|     public readonly summaryPoints: FeatureSource |  | ||||||
|     private readonly id: string |     private readonly id: string | ||||||
|     private readonly showSummaryAt: "tilecenter" | "average" |     private readonly showSummaryAt: "tilecenter" | "average" | ||||||
|     features: Store<T[]> |     features: Store<T[]> | ||||||
|  | @ -42,11 +46,9 @@ export class ClusteringFeatureSource<T extends Feature<Point> = Feature<Point>> | ||||||
|         const clusterCutoff = options?.dontClusterAboveZoom ?? 17 |         const clusterCutoff = options?.dontClusterAboveZoom ?? 17 | ||||||
|         const doCluster = options?.dontClusterAboveZoom === undefined ? new ImmutableStore(true) : currentZoomlevel.map(zoom => zoom <= clusterCutoff) |         const doCluster = options?.dontClusterAboveZoom === undefined ? new ImmutableStore(true) : currentZoomlevel.map(zoom => zoom <= clusterCutoff) | ||||||
|         const cutoff = options?.cutoff ?? 20 |         const cutoff = options?.cutoff ?? 20 | ||||||
|         const summaryPoints = new UIEventSource<Feature<Point>[]>([]) |         const summaryPoints = new UIEventSource<Feature<Point, SummaryProperties>[]>([]) | ||||||
|         currentZoomlevel = currentZoomlevel.stabilized(500) |         currentZoomlevel = currentZoomlevel.stabilized(500).map(z => Math.floor(z)) | ||||||
|         this.summaryPoints = new StaticFeatureSource(summaryPoints) |  | ||||||
|         this.features = (upstream.features.map(features => { |         this.features = (upstream.features.map(features => { | ||||||
|             console.log(">>> Updating features in clusters ", this.id, ":", features) |  | ||||||
|             if (!doCluster.data) { |             if (!doCluster.data) { | ||||||
|                 summaryPoints.set([]) |                 summaryPoints.set([]) | ||||||
|                 return features |                 return features | ||||||
|  | @ -55,7 +57,7 @@ export class ClusteringFeatureSource<T extends Feature<Point> = Feature<Point>> | ||||||
|             const z = currentZoomlevel.data |             const z = currentZoomlevel.data | ||||||
|             const perTile = GeoOperations.spreadIntoBboxes(features, z) |             const perTile = GeoOperations.spreadIntoBboxes(features, z) | ||||||
|             const resultingFeatures = [] |             const resultingFeatures = [] | ||||||
|             const summary: Feature<Point>[] = [] |             const summary: Feature<Point, SummaryProperties>[] = [] | ||||||
|             for (const tileIndex of perTile.keys()) { |             for (const tileIndex of perTile.keys()) { | ||||||
|                 const tileFeatures: Feature<Point>[] = perTile.get(tileIndex) |                 const tileFeatures: Feature<Point>[] = perTile.get(tileIndex) | ||||||
|                 if (tileFeatures.length > cutoff) { |                 if (tileFeatures.length > cutoff) { | ||||||
|  | @ -69,11 +71,12 @@ export class ClusteringFeatureSource<T extends Feature<Point> = Feature<Point>> | ||||||
| 
 | 
 | ||||||
|         }, [doCluster, currentZoomlevel])) |         }, [doCluster, currentZoomlevel])) | ||||||
| 
 | 
 | ||||||
|  |         ClusterGrouping.singleton.registerSource(summaryPoints) | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     private createSummaryFeature(features: Feature<Point>[], tileId: number): Feature<Point> { |     private createSummaryFeature(features: Feature<Point>[], tileId: number): Feature<Point, SummaryProperties> { | ||||||
| 
 | 
 | ||||||
|         let lon: number |         let lon: number | ||||||
|         let lat: number |         let lat: number | ||||||
|  | @ -91,7 +94,7 @@ export class ClusteringFeatureSource<T extends Feature<Point> = Feature<Point>> | ||||||
|             lon = lonSum / features.length |             lon = lonSum / features.length | ||||||
|             lat = latSum / features.length |             lat = latSum / features.length | ||||||
|         } |         } | ||||||
|         return <Feature<Point>>{ |         return { | ||||||
|             type: "Feature", |             type: "Feature", | ||||||
|             geometry: { |             geometry: { | ||||||
|                 type: "Point", |                 type: "Point", | ||||||
|  | @ -99,9 +102,68 @@ export class ClusteringFeatureSource<T extends Feature<Point> = Feature<Point>> | ||||||
|             }, |             }, | ||||||
|             properties: { |             properties: { | ||||||
|                 id: "summary_" + this.id + "_" + tileId, |                 id: "summary_" + this.id + "_" + tileId, | ||||||
|                 z, |                 tile_id: tileId, | ||||||
|                 total_metric: "" + features.length |                 total: features.length | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Groups multiple summaries together | ||||||
|  |  */ | ||||||
|  | export class ClusterGrouping implements FeatureSource<Feature<Point, { total_metric: string }>> { | ||||||
|  |     private readonly _features: UIEventSource<Feature<Point, { total_metric: string }>[]> = new UIEventSource([]) | ||||||
|  |     public readonly features: Store<Feature<Point, { total_metric: string }>[]> = this._features | ||||||
|  | 
 | ||||||
|  |     public static readonly singleton = new ClusterGrouping() | ||||||
|  | 
 | ||||||
|  |     public readonly isDirty = new UIEventSource(false) | ||||||
|  | 
 | ||||||
|  |     private constructor() { | ||||||
|  |         this.isDirty.stabilized(200).addCallback(dirty => { | ||||||
|  |             if (dirty) { | ||||||
|  |                 this.update() | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private allSource: Store<Feature<Point, { total: number, tile_id: number }>[]>[] = [] | ||||||
|  | 
 | ||||||
|  |     private update() { | ||||||
|  |         const countPerTile = new Map<number, number>() | ||||||
|  |         for (const source of this.allSource) { | ||||||
|  |             for (const f of source.data) { | ||||||
|  |                 const id = f.properties.tile_id | ||||||
|  |                 const count = f.properties.total + (countPerTile.get(id) ?? 0) | ||||||
|  |                 countPerTile.set(id, count) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         const features: Feature<Point, { total_metric: string, id: string }>[] = [] | ||||||
|  |         for (const tileId of countPerTile.keys()) { | ||||||
|  |             const coordinates = Tiles.centerPointOf(tileId) | ||||||
|  |             features.push({ | ||||||
|  |                 type: "Feature", | ||||||
|  |                 properties: { | ||||||
|  |                     total_metric: "" + countPerTile.get(tileId), | ||||||
|  |                     id: "clustered_all_" + tileId | ||||||
|  |                 }, | ||||||
|  |                 geometry: { | ||||||
|  |                     type: "Point", | ||||||
|  |                     coordinates | ||||||
|  |                 } | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |         this._features.set(features) | ||||||
|  |         this.isDirty.set(false) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public registerSource(features: Store<Feature<Point, SummaryProperties>[]>) { | ||||||
|  |         this.allSource.push(features) | ||||||
|  |         features.addCallbackAndRun(() => { | ||||||
|  |             //this.isDirty.set(true)
 | ||||||
|  |             this.update() | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -18,9 +18,10 @@ import { Store, UIEventSource } from "../../Logic/UIEventSource" | ||||||
| import NearbyFeatureSource from "../../Logic/FeatureSource/Sources/NearbyFeatureSource" | import NearbyFeatureSource from "../../Logic/FeatureSource/Sources/NearbyFeatureSource" | ||||||
| import { | import { | ||||||
|     SummaryTileSource, |     SummaryTileSource, | ||||||
|     SummaryTileSourceRewriter, |     SummaryTileSourceRewriter | ||||||
| } from "../../Logic/FeatureSource/TiledFeatureSource/SummaryTileSource" | } from "../../Logic/FeatureSource/TiledFeatureSource/SummaryTileSource" | ||||||
| import { ShowDataLayerOptions } from "../../UI/Map/ShowDataLayerOptions" | import { ShowDataLayerOptions } from "../../UI/Map/ShowDataLayerOptions" | ||||||
|  | import { ClusterGrouping } from "../../Logic/FeatureSource/TiledFeatureSource/ClusteringFeatureSource" | ||||||
| 
 | 
 | ||||||
| export class WithSpecialLayers extends WithChangesState { | export class WithSpecialLayers extends WithChangesState { | ||||||
|     readonly favourites: FavouritesFeatureSource |     readonly favourites: FavouritesFeatureSource | ||||||
|  | @ -61,6 +62,7 @@ export class WithSpecialLayers extends WithChangesState { | ||||||
|         this.closestFeatures.registerSource(this.favourites, "favourite") |         this.closestFeatures.registerSource(this.favourites, "favourite") | ||||||
| 
 | 
 | ||||||
|         this.featureSummary = this.setupSummaryLayer() |         this.featureSummary = this.setupSummaryLayer() | ||||||
|  |         this.setupClusterLayer() | ||||||
|         this.initActorsSpecialLayers() |         this.initActorsSpecialLayers() | ||||||
|         this.drawSelectedElement() |         this.drawSelectedElement() | ||||||
|         this.drawSpecialLayers() |         this.drawSpecialLayers() | ||||||
|  | @ -128,6 +130,18 @@ export class WithSpecialLayers extends WithChangesState { | ||||||
|         return source |         return source | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * On high zoom levels, the clusters will be gathered in the GroupClustering. | ||||||
|  |      * This method is responsible for drawing it | ||||||
|  |      * @private | ||||||
|  |      */ | ||||||
|  |     private setupClusterLayer(): void { | ||||||
|  |         new ShowDataLayer(this.map, { | ||||||
|  |             features: ClusterGrouping.singleton, | ||||||
|  |             layer: new LayerConfig(<LayerConfigJson>(<unknown>summaryLayer), "summaryLayer") | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     protected registerSpecialLayer(flayer: FilteredLayer, source: FeatureSource) { |     protected registerSpecialLayer(flayer: FilteredLayer, source: FeatureSource) { | ||||||
|         if (!source?.features) { |         if (!source?.features) { | ||||||
|             return |             return | ||||||
|  |  | ||||||
|  | @ -53,11 +53,20 @@ export class Tiles { | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Returns the centerpoint [lon, lat] of the specified tile |      * Returns the centerpoint [lon, lat] of the specified tile | ||||||
|      * @param z |      * @param z OR tileId | ||||||
|      * @param x |      * @param x | ||||||
|      * @param y |      * @param y | ||||||
|      */ |      */ | ||||||
|     static centerPointOf(z: number, x: number, y: number): [number, number] { |     static centerPointOf(z: number, x: number, y: number): [number, number] ; | ||||||
|  |     static centerPointOf(tileId: number): [number, number] ; | ||||||
|  | 
 | ||||||
|  |     static centerPointOf(zOrId: number, x?: number, y?: number): [number, number] { | ||||||
|  |         let z: number | ||||||
|  |         if (x === undefined) { | ||||||
|  |             [z, x, y] = Tiles.tile_from_index(zOrId) | ||||||
|  |         } else { | ||||||
|  |             z = zOrId | ||||||
|  |         } | ||||||
|         return [ |         return [ | ||||||
|             (Tiles.tile2long(x, z) + Tiles.tile2long(x + 1, z)) / 2, |             (Tiles.tile2long(x, z) + Tiles.tile2long(x + 1, z)) / 2, | ||||||
|             (Tiles.tile2lat(y, z) + Tiles.tile2lat(y + 1, z)) / 2, |             (Tiles.tile2lat(y, z) + Tiles.tile2lat(y + 1, z)) / 2, | ||||||
|  |  | ||||||
|  | @ -3,9 +3,9 @@ import type { AddLayerObject, Map as MlMap } from "maplibre-gl" | ||||||
| import { GeoJSONSource } from "maplibre-gl" | import { GeoJSONSource } from "maplibre-gl" | ||||||
| import { ShowDataLayerOptions } from "./ShowDataLayerOptions" | import { ShowDataLayerOptions } from "./ShowDataLayerOptions" | ||||||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | ||||||
| import { FeatureSource, FeatureSourceForLayer } from "../../Logic/FeatureSource/FeatureSource" | import { FeatureSource } from "../../Logic/FeatureSource/FeatureSource" | ||||||
| import { BBox } from "../../Logic/BBox" | import { BBox } from "../../Logic/BBox" | ||||||
| import { Feature } from "geojson" | import { Feature, Geometry } from "geojson" | ||||||
| import LineRenderingConfig from "../../Models/ThemeConfig/LineRenderingConfig" | import LineRenderingConfig from "../../Models/ThemeConfig/LineRenderingConfig" | ||||||
| import { Utils } from "../../Utils" | import { Utils } from "../../Utils" | ||||||
| import * as range_layer from "../../../assets/layers/range/range.json" | import * as range_layer from "../../../assets/layers/range/range.json" | ||||||
|  | @ -16,7 +16,7 @@ import { TagsFilter } from "../../Logic/Tags/TagsFilter" | ||||||
| import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" | import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" | ||||||
| import { PointRenderingLayer } from "./PointRenderingLayer" | import { PointRenderingLayer } from "./PointRenderingLayer" | ||||||
| import { ClusteringFeatureSource } from "../../Logic/FeatureSource/TiledFeatureSource/ClusteringFeatureSource" | import { ClusteringFeatureSource } from "../../Logic/FeatureSource/TiledFeatureSource/ClusteringFeatureSource" | ||||||
| import summaryLayer from "../../../public/assets/generated/layers/summary.json" | import { OsmTags } from "../../Models/OsmFeature" | ||||||
| 
 | 
 | ||||||
| class LineRenderingLayer { | class LineRenderingLayer { | ||||||
|     /** |     /** | ||||||
|  | @ -361,7 +361,7 @@ export default class ShowDataLayer { | ||||||
|         layers: LayerConfig[], |         layers: LayerConfig[], | ||||||
|         options?: Partial<Omit<ShowDataLayerOptions, "features" | "layer">> |         options?: Partial<Omit<ShowDataLayerOptions, "features" | "layer">> | ||||||
|     ) { |     ) { | ||||||
|         const perLayer: PerLayerFeatureSourceSplitter<FeatureSourceForLayer> = |         const perLayer = | ||||||
|             new PerLayerFeatureSourceSplitter( |             new PerLayerFeatureSourceSplitter( | ||||||
|                 layers.filter((l) => l.source !== null).map((l) => new FilteredLayer(l)), |                 layers.filter((l) => l.source !== null).map((l) => new FilteredLayer(l)), | ||||||
|                 features, |                 features, | ||||||
|  | @ -379,10 +379,10 @@ export default class ShowDataLayer { | ||||||
|             }) |             }) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         perLayer.forEach((fs) => { |         perLayer.forEach((features) => { | ||||||
|             new ShowDataLayer(mlmap, { |             new ShowDataLayer(mlmap, { | ||||||
|                 layer: fs.layer.layerDef, |                 layer: features.layer.layerDef, | ||||||
|                 features: fs, |                 features, | ||||||
|                 ...(options ?? {}), |                 ...(options ?? {}), | ||||||
|             }) |             }) | ||||||
|         }) |         }) | ||||||
|  | @ -396,14 +396,10 @@ export default class ShowDataLayer { | ||||||
|             const clustering = new ClusteringFeatureSource(feats, state.mapProperties.zoom.map(z => z + 2), |             const clustering = new ClusteringFeatureSource(feats, state.mapProperties.zoom.map(z => z + 2), | ||||||
|                 options.layer.id, |                 options.layer.id, | ||||||
|                 { |                 { | ||||||
|                     cutoff: 2, |                     cutoff: 7, | ||||||
|                     showSummaryAt: "tilecenter" |                     showSummaryAt: "tilecenter" | ||||||
|                 }) |                 }) | ||||||
|             new ShowDataLayer(mlmap, { | 
 | ||||||
|                 features: clustering.summaryPoints, |  | ||||||
|                 layer: new LayerConfig(<LayerConfigJson>(<unknown>summaryLayer), "summaryLayer") |  | ||||||
|                 // doShowLayer: this.mapProperties.zoom.map((z) => z < maxzoom),
 |  | ||||||
|             }) |  | ||||||
|             return clustering |             return clustering | ||||||
|         } |         } | ||||||
|         new ShowDataLayer(mlmap, options) |         new ShowDataLayer(mlmap, options) | ||||||
|  | @ -411,7 +407,7 @@ export default class ShowDataLayer { | ||||||
| 
 | 
 | ||||||
|     public static showRange( |     public static showRange( | ||||||
|         map: Store<MlMap>, |         map: Store<MlMap>, | ||||||
|         features: FeatureSource, |         features: FeatureSource<Feature<Geometry, OsmTags>>, | ||||||
|         doShowLayer?: Store<boolean> |         doShowLayer?: Store<boolean> | ||||||
|     ): ShowDataLayer { |     ): ShowDataLayer { | ||||||
|         return new ShowDataLayer(map, { |         return new ShowDataLayer(map, { | ||||||
|  |  | ||||||
|  | @ -1,12 +1,13 @@ | ||||||
| import { FeatureSource } from "../../Logic/FeatureSource/FeatureSource" | import { FeatureSource } from "../../Logic/FeatureSource/FeatureSource" | ||||||
| import { Store, UIEventSource } from "../../Logic/UIEventSource" | import { Store, UIEventSource } from "../../Logic/UIEventSource" | ||||||
| import { Feature, Point } from "geojson" | import { Feature, Geometry, Point } from "geojson" | ||||||
|  | import { OsmTags } from "../../Models/OsmFeature" | ||||||
| 
 | 
 | ||||||
| export interface ShowDataLayerOptions { | export interface ShowDataLayerOptions { | ||||||
|     /** |     /** | ||||||
|      * Features to show |      * Features to show | ||||||
|      */ |      */ | ||||||
|     features: FeatureSource |     features: FeatureSource<Feature<Geometry, OsmTags>> | ||||||
|     /** |     /** | ||||||
|      * Indication of the current selected element; overrides some filters. |      * Indication of the current selected element; overrides some filters. | ||||||
|      * When a feature is tapped, the feature will be put in there |      * When a feature is tapped, the feature will be put in there | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue