Merge pull request 'Add local clustering' (#2468) from feature/local-clustering into develop

Reviewed-on: MapComplete/MapComplete#2468
This commit is contained in:
Pieter Vander Vennet 2025-07-25 18:19:56 +00:00
commit 17ce67bf3f
18 changed files with 727 additions and 321 deletions

View file

@ -31,8 +31,11 @@ export class IconConfig extends WithContextLoader {
}
}
export const allowed_location_codes = ["point", "centroid", "start", "end", "projected_centerpoint", "polygon_centroid", "waypoints"] as const
export type PointRenderingLocation = typeof allowed_location_codes[number]
export default class PointRenderingConfig extends WithContextLoader {
static readonly allowed_location_codes: ReadonlySet<string> = new Set<string>([
static readonly allowed_location_codes_set: ReadonlySet<PointRenderingLocation> = new Set<PointRenderingLocation>([
"point",
"centroid",
"start",
@ -41,16 +44,7 @@ export default class PointRenderingConfig extends WithContextLoader {
"polygon_centroid",
"waypoints",
])
public readonly location: Set<
| "point"
| "centroid"
| "start"
| "end"
| "projected_centerpoint"
| "polygon_centroid"
| "waypoints"
| string
>
public readonly location: Set<PointRenderingLocation>
public readonly marker: IconConfig[]
public readonly iconBadges: { if: TagsFilter; then: TagRenderingConfig }[]
@ -77,10 +71,10 @@ export default class PointRenderingConfig extends WithContextLoader {
json.location = [json.location]
}
this.location = new Set(json.location)
this.location = new Set(<PointRenderingLocation[]>json.location)
this.location.forEach((l) => {
const allowed = PointRenderingConfig.allowed_location_codes
const allowed = PointRenderingConfig.allowed_location_codes_set
if (!allowed.has(l)) {
throw `A point rendering has an invalid location: '${l}' is not one of ${Array.from(
allowed
@ -313,10 +307,9 @@ export default class PointRenderingConfig extends WithContextLoader {
}
const cssLabel = this.labelCss?.GetRenderValue(tags.data)?.txt
const cssClassesLabel = this.labelCssClasses?.GetRenderValue(tags.data)?.txt
const self = this
return new VariableUiElement(
tags.map((tags) => {
const label = self.label
const label = this.label
?.GetRenderValue(tags)
?.Subs(tags)
?.SetClass("flex items-center justify-center absolute marker-label")

View file

@ -255,27 +255,4 @@ export class UserMapFeatureswitchState extends WithUserRelatedState {
}
)
}
/**
* Shows the current GPS-location marker on the given map.
* This is used to show the location on _other_ maps, e.g. on the map to add a new feature.
*
* This is _NOT_ to be used on the main map!
*/
public showCurrentLocationOn(map: Store<MlMap>) {
const id = "gps_location"
const layer = this.theme.getLayer(id)
if (layer === undefined) {
return
}
if (map === this.map) {
throw "Invalid use of showCurrentLocationOn"
}
const features = this.geolocation.currentUserLocation
return new ShowDataLayer(map, {
features,
layer,
metaTags: this.userRelatedState.preferencesAsTags,
})
}
}

View file

@ -1,5 +1,7 @@
import { Changes } from "../../Logic/Osm/Changes"
import { NewGeometryFromChangesFeatureSource } from "../../Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource"
import {
NewGeometryFromChangesFeatureSource
} from "../../Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource"
import { WithLayoutSourceState } from "./WithLayoutSourceState"
import ThemeConfig from "../ThemeConfig/ThemeConfig"
import { Utils } from "../../Utils"
@ -18,9 +20,7 @@ import { Map as MlMap } from "maplibre-gl"
import FilteringFeatureSource from "../../Logic/FeatureSource/Sources/FilteringFeatureSource"
import ShowDataLayer from "../../UI/Map/ShowDataLayer"
import SelectedElementTagsUpdater from "../../Logic/Actors/SelectedElementTagsUpdater"
import NoElementsInViewDetector, {
FeatureViewState,
} from "../../Logic/Actors/NoElementsInViewDetector"
import NoElementsInViewDetector, { FeatureViewState } from "../../Logic/Actors/NoElementsInViewDetector"
export class WithChangesState extends WithLayoutSourceState {
readonly changes: Changes
@ -219,14 +219,24 @@ export class WithChangesState extends WithLayoutSourceState {
)
filteringFeatureSource.set(layerName, filtered)
new ShowDataLayer(map, {
ShowDataLayer.showLayerClustered(map,
this,
{
layer: fs.layer.layerDef,
features: filtered,
doShowLayer,
metaTags: this.userRelatedState.preferencesAsTags,
selectedElement: this.selectedElement,
fetchStore: (id) => this.featureProperties.getStore(id)
})
/*new ShowDataLayer(map, {
layer: fs.layer.layerDef,
features: filtered,
doShowLayer,
metaTags: this.userRelatedState.preferencesAsTags,
selectedElement: this.selectedElement,
fetchStore: (id) => this.featureProperties.getStore(id),
})
})*/
})
return filteringFeatureSource
}

View file

@ -18,9 +18,10 @@ import { Store, UIEventSource } from "../../Logic/UIEventSource"
import NearbyFeatureSource from "../../Logic/FeatureSource/Sources/NearbyFeatureSource"
import {
SummaryTileSource,
SummaryTileSourceRewriter,
SummaryTileSourceRewriter
} from "../../Logic/FeatureSource/TiledFeatureSource/SummaryTileSource"
import { ShowDataLayerOptions } from "../../UI/Map/ShowDataLayerOptions"
import { ClusterGrouping } from "../../Logic/FeatureSource/TiledFeatureSource/ClusteringFeatureSource"
export class WithSpecialLayers extends WithChangesState {
readonly favourites: FavouritesFeatureSource
@ -63,6 +64,7 @@ export class WithSpecialLayers extends WithChangesState {
this.closestFeatures.registerSource(this.favourites, "favourite")
this.featureSummary = this.setupSummaryLayer()
this.setupClusterLayer()
this.initActorsSpecialLayers()
this.drawSelectedElement()
this.drawSpecialLayers()
@ -131,6 +133,18 @@ export class WithSpecialLayers extends WithChangesState {
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) {
if (!source?.features) {
return

View file

@ -53,11 +53,20 @@ export class Tiles {
/**
* Returns the centerpoint [lon, lat] of the specified tile
* @param z
* @param z OR tileId
* @param x
* @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 [
(Tiles.tile2long(x, z) + Tiles.tile2long(x + 1, z)) / 2,
(Tiles.tile2lat(y, z) + Tiles.tile2lat(y + 1, z)) / 2,