UX: center summary tiles, small style tweaks

This commit is contained in:
Pieter Vander Vennet 2025-08-26 04:23:56 +02:00
parent 020a1f8c3b
commit 38191309ca
4 changed files with 32 additions and 21 deletions

View file

@ -29,8 +29,8 @@
} }
] ]
}, },
"labelCss": "background: #ffffffbb", "labelCss": "background: #ffffffcc; min-width: 2rem",
"labelCssClasses": "w-12 text-lg rounded-xl p-1 px-2" "labelCssClasses": "text-lg rounded p-1 px-2 border-2 border-gray font-bold"
} }
], ],
"tagRenderings": [ "tagRenderings": [

View file

@ -3,6 +3,7 @@ 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 { Tiles } from "../../../Models/TileRange" import { Tiles } from "../../../Models/TileRange"
import { Lists } from "../../../Utils/Lists"
export interface ClusteringOptions { export interface ClusteringOptions {
/** /**
@ -86,11 +87,10 @@ export class ClusteringFeatureSource<T extends Feature<Point> = Feature<Point>>
features: Feature<Point>[], features: Feature<Point>[],
tileId: number tileId: number
): Feature<Point, SummaryProperties> { ): Feature<Point, SummaryProperties> {
let lon: number let coordinates: [number, number]
let lat: number
const [z, x, y] = Tiles.tile_from_index(tileId)
if (this.showSummaryAt === "tilecenter") { if (this.showSummaryAt === "tilecenter") {
;[lon, lat] = Tiles.centerPointOf(z, x, y) const [z, x, y] = Tiles.tile_from_index(tileId)
coordinates = Tiles.centerPointOf(z, x, y)
} else { } else {
let lonSum = 0 let lonSum = 0
let latSum = 0 let latSum = 0
@ -99,14 +99,15 @@ export class ClusteringFeatureSource<T extends Feature<Point> = Feature<Point>>
lonSum += lon lonSum += lon
latSum += lat latSum += lat
} }
lon = lonSum / features.length const lon = lonSum / features.length
lat = latSum / features.length const lat = latSum / features.length
coordinates = [lon, lat]
} }
return { return {
type: "Feature", type: "Feature",
geometry: { geometry: {
type: "Point", type: "Point",
coordinates: [lon, lat], coordinates
}, },
properties: { properties: {
id: "summary_" + this.id + "_" + tileId, id: "summary_" + this.id + "_" + tileId,
@ -120,10 +121,10 @@ export class ClusteringFeatureSource<T extends Feature<Point> = Feature<Point>>
/** /**
* Groups multiple summaries together * Groups multiple summaries together
*/ */
export class ClusterGrouping implements FeatureSource<Feature<Point, { total_metric: string }>> { export class ClusterGrouping implements FeatureSource<Feature<Point, { total_metric: string, id: string }>> {
private readonly _features: UIEventSource<Feature<Point, { total_metric: string }>[]> = private readonly _features: UIEventSource<Feature<Point, { total_metric: string, id: string }>[]> =
new UIEventSource([]) new UIEventSource([])
public readonly features: Store<Feature<Point, { total_metric: string }>[]> = this._features public readonly features: Store<Feature<Point, { total_metric: string, id: string }>[]> = this._features
public static readonly singleton = new ClusterGrouping() public static readonly singleton = new ClusterGrouping()
@ -140,27 +141,34 @@ export class ClusterGrouping implements FeatureSource<Feature<Point, { total_met
private allSource: Store<Feature<Point, { total: number; tile_id: number }>[]>[] = [] private allSource: Store<Feature<Point, { total: number; tile_id: number }>[]>[] = []
private update() { private update() {
const countPerTile = new Map<number, number>() const countPerTile = new Map<number, { lon: number, lat: number, count: number }[]>()
for (const source of this.allSource) { for (const source of this.allSource) {
for (const f of source.data) { for (const f of source.data) {
const id = f.properties.tile_id const id = f.properties.tile_id
const count = f.properties.total + (countPerTile.get(id) ?? 0) if (!countPerTile.has(id)) {
countPerTile.set(id, count) countPerTile.set(id, [])
}
const ls = countPerTile.get(id)
ls.push({ lon: f.geometry.coordinates[0], lat: f.geometry.coordinates[1], count: f.properties.total })
} }
} }
const features: Feature<Point, { total_metric: string; id: string }>[] = [] const features: Feature<Point, { total_metric: string; id: string }>[] = []
const now = new Date().getTime() + "" const now = new Date().getTime() + ""
for (const tileId of countPerTile.keys()) { for (const tileId of countPerTile.keys()) {
const coordinates = Tiles.centerPointOf(tileId) const data = countPerTile.get(tileId)
const total = Lists.sum(data.map(d => d.count))
const lon = Lists.sum(data.map(d => d.lon * d.count)) / total
const lat = Lists.sum(data.map(d => d.lat * d.count)) / total
features.push({ features.push({
type: "Feature", type: "Feature",
properties: { properties: {
total_metric: "" + countPerTile.get(tileId), total_metric: "" + total,
id: "clustered_all_" + tileId + "_" + now, // We add the date to force a fresh ID every time, this makes sure values are updated id: "clustered_all_" + tileId + "_" + now, // We add the date to force a fresh ID every time, this makes sure values are updated
}, },
geometry: { geometry: {
type: "Point", type: "Point",
coordinates, coordinates: [lon, lat]
}, },
}) })
} }
@ -169,6 +177,10 @@ export class ClusterGrouping implements FeatureSource<Feature<Point, { total_met
} }
public registerSource(features: Store<Feature<Point, SummaryProperties>[]>) { public registerSource(features: Store<Feature<Point, SummaryProperties>[]>) {
if (this.allSource.indexOf(features) >= 0) {
console.error("This source has already been registered")
return
}
this.allSource.push(features) this.allSource.push(features)
features.addCallbackAndRun(() => { features.addCallbackAndRun(() => {
//this.isDirty.set(true) //this.isDirty.set(true)

View file

@ -407,8 +407,7 @@ export default class ShowDataLayer {
state.mapProperties.zoom.map((z) => z + 2), state.mapProperties.zoom.map((z) => z + 2),
options.layer.id, options.layer.id,
{ {
cutoff: 7, cutoff: 7
showSummaryAt: "tilecenter",
} }
) )
new ShowDataLayer(mlmap, options) new ShowDataLayer(mlmap, options)

View file

@ -346,7 +346,7 @@
<InsetSpacer height={AndroidPolyfill.getInsetSizes().top} /> <InsetSpacer height={AndroidPolyfill.getInsetSizes().top} />
</div> </div>
<!-- Top bar with tools --> <!-- Top bar with tools -->
<div class="flex items-center"> <div class="flex items-center" style="z-index: 10">
<MapControlButton <MapControlButton
cls="m-0.5 p-0.5 sm:p-1" cls="m-0.5 p-0.5 sm:p-1"
arialabel={Translations.t.general.labels.menu} arialabel={Translations.t.general.labels.menu}