chore: automated housekeeping...

This commit is contained in:
Pieter Vander Vennet 2025-09-07 22:49:44 +02:00
parent ebc48f6b53
commit e7de94576f
232 changed files with 11430 additions and 3365 deletions

View file

@ -7,8 +7,10 @@ import { Lists } from "../../../Utils/Lists"
* The featureSourceMerger receives complete geometries from various sources.
* If multiple sources contain the same object (as determined by 'id'), only one copy of them is retained
*/
export default class FeatureSourceMerger<T extends Feature, Src extends FeatureSource<T> = FeatureSource<T>>
implements IndexedFeatureSource<T>
export default class FeatureSourceMerger<
T extends Feature,
Src extends FeatureSource<T> = FeatureSource<T>
> implements IndexedFeatureSource<T>
{
public features: UIEventSource<T[]> = new UIEventSource([])
public readonly featuresById: Store<Map<string, Feature>>
@ -117,7 +119,7 @@ export default class FeatureSourceMerger<T extends Feature, Src extends FeatureS
}
export class UpdatableFeatureSourceMerger<
T extends Feature,
T extends Feature,
Src extends UpdatableFeatureSource<T> = UpdatableFeatureSource<T>
>
extends FeatureSourceMerger<T, Src>

View file

@ -7,9 +7,15 @@ import { Feature, Geometry } from "geojson"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import { Tiles } from "../../../Models/TileRange"
export default class GeoJsonSource<T extends Feature<Geometry, {
id: string
} & Record<string, any>>> implements FeatureSource<T> {
export default class GeoJsonSource<
T extends Feature<
Geometry,
{
id: string
} & Record<string, any>
>
> implements FeatureSource<T>
{
private readonly _features: UIEventSource<T[]> = new UIEventSource(undefined)
public readonly features: Store<T[]> = this._features
private readonly seenids: Set<string>

View file

@ -5,7 +5,9 @@ import { FeatureSourceForTile, UpdatableFeatureSource } from "../FeatureSource"
import { MvtToGeojson } from "mvt-to-geojson"
import { OsmTags } from "../../../Models/OsmFeature"
export default class MvtSource<T extends Feature<Geometry, OsmTags>> implements FeatureSourceForTile<T>, UpdatableFeatureSource<T> {
export default class MvtSource<T extends Feature<Geometry, OsmTags>>
implements FeatureSourceForTile<T>, UpdatableFeatureSource<T>
{
public readonly features: Store<T[]>
public readonly x: number
public readonly y: number

View file

@ -9,13 +9,15 @@ import { BBox } from "../../BBox"
import { OsmFeature } from "../../../Models/OsmFeature"
import { Lists } from "../../../Utils/Lists"
("use strict")
;("use strict")
/**
* A wrapper around the 'Overpass'-object.
* It has more logic and will automatically fetch the data for the right bbox and the active layers
*/
export default class OverpassFeatureSource<T extends OsmFeature = OsmFeature> implements UpdatableFeatureSource<T> {
export default class OverpassFeatureSource<T extends OsmFeature = OsmFeature>
implements UpdatableFeatureSource<T>
{
/**
* The last loaded features, as geojson
*/

View file

@ -25,7 +25,6 @@ export default class StaticFeatureSource<T extends Feature = Feature> implements
this.features = feats
}
}
}
export class WritableStaticFeatureSource<T extends Feature = Feature>

View file

@ -21,7 +21,9 @@ import { IsOnline } from "../../Web/IsOnline"
*
* Note that special layers (with `source=null` will be ignored)
*/
export default class ThemeSource<T extends Feature<Geometry, Record<string, any> & {id: string}>> implements IndexedFeatureSource<T> {
export default class ThemeSource<T extends Feature<Geometry, Record<string, any> & { id: string }>>
implements IndexedFeatureSource<T>
{
/**
* Indicates if a data source is loading something
*/
@ -69,7 +71,7 @@ export default class ThemeSource<T extends Feature<Geometry, Record<string, any>
return core
})
IsOnline.isOnline.addCallback(async online => {
IsOnline.isOnline.addCallback(async (online) => {
if (online) {
// Connectivity is restored - let us try to update the data
console.log("Internet got restored - starting to download all data")
@ -165,7 +167,12 @@ class ThemeSourceCore<T extends OsmFeature> extends FeatureSourceMerger<T> {
nonMvtLayers.map((l) => l.id),
" cannot be fetched from the cache server, defaulting to overpass/OSM-api"
)
overpassSource = ThemeSourceCore.setupOverpass<T>(osmLayers, bounds, zoom, featureSwitches)
overpassSource = ThemeSourceCore.setupOverpass<T>(
osmLayers,
bounds,
zoom,
featureSwitches
)
nonMvtSources.push(overpassSource)
}

View file

@ -107,7 +107,7 @@ export class ClusteringFeatureSource<T extends Feature<Point> = Feature<Point>>
type: "Feature",
geometry: {
type: "Point",
coordinates
coordinates,
},
properties: {
id: "summary_" + this.id + "_" + tileId,
@ -121,10 +121,14 @@ export class ClusteringFeatureSource<T extends Feature<Point> = Feature<Point>>
/**
* Groups multiple summaries together
*/
export class ClusterGrouping implements FeatureSource<Feature<Point, { total_metric: string, id: string }>> {
private readonly _features: UIEventSource<Feature<Point, { total_metric: string, id: string }>[]> =
new UIEventSource([])
public readonly features: Store<Feature<Point, { total_metric: string, id: string }>[]> = this._features
export class ClusterGrouping
implements FeatureSource<Feature<Point, { total_metric: string; id: string }>>
{
private readonly _features: UIEventSource<
Feature<Point, { total_metric: string; id: string }>[]
> = new UIEventSource([])
public readonly features: Store<Feature<Point, { total_metric: string; id: string }>[]> =
this._features
public static readonly singleton = new ClusterGrouping()
@ -141,7 +145,7 @@ export class ClusterGrouping implements FeatureSource<Feature<Point, { total_met
private allSource: Store<Feature<Point, { total: number; tile_id: number }>[]>[] = []
private update() {
const countPerTile = new Map<number, { lon: number, lat: number, count: number }[]>()
const countPerTile = new Map<number, { lon: number; lat: number; count: number }[]>()
for (const source of this.allSource) {
for (const f of source.data) {
const id = f.properties.tile_id
@ -149,16 +153,20 @@ export class ClusterGrouping implements FeatureSource<Feature<Point, { total_met
countPerTile.set(id, [])
}
const ls = countPerTile.get(id)
ls.push({ lon: f.geometry.coordinates[0], lat: f.geometry.coordinates[1], count: f.properties.total })
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 now = new Date().getTime() + ""
for (const tileId of countPerTile.keys()) {
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
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({
type: "Feature",
@ -168,7 +176,7 @@ export class ClusterGrouping implements FeatureSource<Feature<Point, { total_met
},
geometry: {
type: "Point",
coordinates: [lon, lat]
coordinates: [lon, lat],
},
})
}

View file

@ -6,7 +6,9 @@ import { BBox } from "../../BBox"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import { Feature, Geometry } from "geojson"
export default class DynamicGeoJsonTileSource<T extends Feature<Geometry, Record<string, any> & {id: string} > > extends UpdatableDynamicTileSource<T> {
export default class DynamicGeoJsonTileSource<
T extends Feature<Geometry, Record<string, any> & { id: string }>
> extends UpdatableDynamicTileSource<T> {
private static whitelistCache = new Map<string, Map<number, Set<number>>>()
constructor(

View file

@ -12,7 +12,9 @@ import { PolygonSourceMerger } from "./PolygonSourceMerger"
import { OsmFeature, OsmTags } from "../../../Models/OsmFeature"
import { Feature, Point } from "geojson"
class PolygonMvtSource<P extends Record<string, any> & { id: string }> extends PolygonSourceMerger<P> {
class PolygonMvtSource<
P extends Record<string, any> & { id: string }
> extends PolygonSourceMerger<P> {
constructor(
layer: LayerConfig,
mapProperties: {
@ -114,7 +116,9 @@ class PointMvtSource<T extends Feature<Point, OsmTags>> extends UpdatableDynamic
}
}
export default class DynamicMvtileSource<T extends OsmFeature> extends UpdatableFeatureSourceMerger<T> {
export default class DynamicMvtileSource<
T extends OsmFeature
> extends UpdatableFeatureSourceMerger<T> {
constructor(
layer: LayerConfig,
mapProperties: {
@ -128,7 +132,7 @@ export default class DynamicMvtileSource<T extends OsmFeature> extends Updatable
super(
<any>new PointMvtSource(layer, mapProperties, options),
<any>new LineMvtSource(layer, mapProperties, options),
<any>new PolygonMvtSource(layer, mapProperties, options),
<any>new PolygonMvtSource(layer, mapProperties, options)
)
}
}

View file

@ -9,7 +9,8 @@ import { Feature, Geometry } from "geojson"
* 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<T extends Feature,
export default class DynamicTileSource<
T extends Feature,
Src extends FeatureSource<T> = FeatureSource<T>
> extends FeatureSourceMerger<T, Src> {
private readonly loadedTiles = new Set<number>()
@ -98,7 +99,10 @@ export default class DynamicTileSource<T extends Feature,
}
}
export class UpdatableDynamicTileSource<T extends Feature<Geometry, Record<string, any> & {id: string}>, Src extends UpdatableFeatureSource<T> = UpdatableFeatureSource<T>>
export class UpdatableDynamicTileSource<
T extends Feature<Geometry, Record<string, any> & { id: string }>,
Src extends UpdatableFeatureSource<T> = UpdatableFeatureSource<T>
>
extends DynamicTileSource<T, Src>
implements UpdatableFeatureSource<T>
{

View file

@ -10,16 +10,22 @@ import { Lists } from "../../../Utils/Lists"
* The PolygonSourceMerger receives various small pieces of bigger polygons and stitches them together.
* This is used to reconstruct polygons of vector tiles
*/
export class LineSourceMerger<P extends Record<string, any> & { id: string }> extends UpdatableDynamicTileSource<
Feature<LineString | MultiLineString, P>, FeatureSourceForTile<Feature<LineString | MultiLineString, P>> & UpdatableFeatureSource<Feature<LineString | MultiLineString, P>>
export class LineSourceMerger<
P extends Record<string, any> & { id: string }
> extends UpdatableDynamicTileSource<
Feature<LineString | MultiLineString, P>,
FeatureSourceForTile<Feature<LineString | MultiLineString, P>> &
UpdatableFeatureSource<Feature<LineString | MultiLineString, P>>
> {
private readonly _zoomlevel: Store<number>
constructor(
zoomlevel: Store<number>,
minzoom: number,
constructSource: (tileIndex: number) => FeatureSourceForTile<
Feature<LineString | MultiLineString, P>> & UpdatableFeatureSource<Feature<LineString | MultiLineString, P>>,
constructSource: (
tileIndex: number
) => FeatureSourceForTile<Feature<LineString | MultiLineString, P>> &
UpdatableFeatureSource<Feature<LineString | MultiLineString, P>>,
mapProperties: {
bounds: Store<BBox>
zoom: Store<number>
@ -32,7 +38,9 @@ export class LineSourceMerger<P extends Record<string, any> & { id: string }> ex
this._zoomlevel = zoomlevel
}
protected addDataFromSources(sources: FeatureSourceForTile<Feature<LineString | MultiLineString, P>>[]) {
protected addDataFromSources(
sources: FeatureSourceForTile<Feature<LineString | MultiLineString, P>>[]
) {
sources = Lists.noNull(sources)
const all: Map<string, Feature<MultiLineString | LineString, P>> = new Map()
const currentZoom = this._zoomlevel?.data ?? 0

View file

@ -6,7 +6,9 @@ import { Feature, Geometry } from "geojson"
import StaticFeatureSource from "../Sources/StaticFeatureSource"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
export default class LocalStorageFeatureSource<T extends Feature<Geometry, { [name: string]: any }>> extends DynamicTileSource<T> {
export default class LocalStorageFeatureSource<
T extends Feature<Geometry, { [name: string]: any }>
> extends DynamicTileSource<T> {
constructor(
backend: string,
layer: LayerConfig,
@ -31,7 +33,7 @@ export default class LocalStorageFeatureSource<T extends Feature<Geometry, { [na
layer.minzoom,
(tileIndex) =>
new StaticFeatureSource<T>(
<Store<T[]>> storage.getTileSource(tileIndex).mapD((features) => {
<Store<T[]>>storage.getTileSource(tileIndex).mapD((features) => {
if (features.length === undefined) {
console.trace("These are not features:", features)
storage.invalidate(tileIndex)

View file

@ -10,10 +10,10 @@ import { Lists } from "../../../Utils/Lists"
* 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<P extends Record<string, any> & { id: string },
F extends Feature<Polygon, P> = Feature<Polygon, P>> extends UpdatableDynamicTileSource<
F, FeatureSourceForTile<F> & UpdatableFeatureSource<F>
> {
export class PolygonSourceMerger<
P extends Record<string, any> & { id: string },
F extends Feature<Polygon, P> = Feature<Polygon, P>
> extends UpdatableDynamicTileSource<F, FeatureSourceForTile<F> & UpdatableFeatureSource<F>> {
constructor(
zoomlevel: Store<number>,
minzoom: number,
@ -61,7 +61,7 @@ export class PolygonSourceMerger<P extends Record<string, any> & { id: string },
zooms.set(id, z)
continue
}
const merged = <F> GeoOperations.union(f, oldV)
const merged = <F>GeoOperations.union(f, oldV)
merged.properties = oldV.properties
all.set(id, merged)
zooms.set(id, z)

View file

@ -53,7 +53,7 @@ export class GeoOperations {
/**
* Create a union between two features
*/
public static union<P >(
public static union<P>(
f0: Feature<Polygon | MultiPolygon>,
f1: Feature<Polygon | MultiPolygon>
): Feature<Polygon | MultiPolygon, P> | null {

View file

@ -88,11 +88,10 @@ export class ImageUploadManager {
})
IsOnline.isOnline.addCallback(async (isOnline) => {
if (isOnline) {
await this.uploadQueue()
}
},
)
if (isOnline) {
await this.uploadQueue()
}
})
}
public async canBeUploaded(file: File): Promise<true | { error: Translation }> {
@ -179,7 +178,7 @@ export class ImageUploadManager {
if (this.uploadingAll) {
return
}
if(!IsOnline.isOnline){
if (!IsOnline.isOnline) {
return
}
try {

View file

@ -197,7 +197,7 @@ export default class PanoramaxImageProvider extends ImageProvider {
public async DownloadAttribution(providedImage: { id: string }): Promise<LicenseInfo> {
const meta = await this.getInfoFor(providedImage.id)
const artists = Lists.noEmpty(meta.data.providers.map(p => p.name))
const artists = Lists.noEmpty(meta.data.providers.map((p) => p.name))
// We take the last provider, as that one probably contain the username of the uploader
const artist = artists.at(-1)

View file

@ -15,9 +15,12 @@ export class WikimediaImageProvider extends ImageProvider {
"https://commons.wikimedia.org/wiki/",
"https://upload.wikimedia.org",
]
public static readonly commonsPrefixes = [...WikimediaImageProvider.apiUrls,
public static readonly commonsPrefixes = [
...WikimediaImageProvider.apiUrls,
"http://commons.wikimedia.org/wiki/",
"http://upload.wikimedia.org", "File:"]
"http://upload.wikimedia.org",
"File:",
]
private readonly commons_key = "wikimedia_commons"
public readonly defaultKeyPrefixes = [this.commons_key, "image"]
public readonly name = "Wikimedia"

View file

@ -91,13 +91,13 @@ export default class ReplaceGeometryAction extends OsmChangeAction implements Pr
public async getPreview(): Promise<FeatureSource> {
const { closestIds, allNodesById, detachedNodes, reprojectedNodes } =
await this.GetClosestIds()
const preview: Feature<Geometry, {id: string}>[] = closestIds.map((newId, i) => {
const preview: Feature<Geometry, { id: string }>[] = closestIds.map((newId, i) => {
if (this.identicalTo[i] !== undefined) {
return undefined
}
if (newId === undefined) {
return <OsmFeature> {
return <OsmFeature>{
type: "Feature",
properties: {
newpoint: "yes",
@ -128,7 +128,7 @@ export default class ReplaceGeometryAction extends OsmChangeAction implements Pr
reprojectedNodes.forEach(({ newLat, newLon, nodeId }) => {
const origNode = allNodesById.get(nodeId)
const feature: Feature<LineString, {id: string} & Record<string, any>> = {
const feature: Feature<LineString, { id: string } & Record<string, any>> = {
type: "Feature",
properties: {
move: "yes",

View file

@ -288,7 +288,7 @@ export class Changes {
if (this.pendingChanges.data.length === 0) {
return
}
if(!IsOnline.isOnline.data){
if (!IsOnline.isOnline.data) {
// No use to upload, we aren't connected anyway
return
}

View file

@ -184,7 +184,10 @@ export class OsmConnection {
this._oauth_config.oauth_secret = import.meta.env.VITE_OSM_OAUTH_SECRET
}
this.userDetails = UIEventSource.asObject<UserDetails>(LocalStorageSource.get("user_details"), undefined)
this.userDetails = UIEventSource.asObject<UserDetails>(
LocalStorageSource.get("user_details"),
undefined
)
if (options.fakeUser) {
const ud = this.userDetails.data
ud.csCount = 5678

View file

@ -5,7 +5,7 @@ import { BBox } from "../BBox"
import osmtogeojson from "osmtogeojson"
import { Feature, FeatureCollection } from "geojson"
("use strict")
;("use strict")
/**
* Interfaces overpass to get all the latest data
*/
@ -37,7 +37,9 @@ export class Overpass {
this._includeMeta = includeMeta
}
public async queryGeoJson<T extends Feature>(bounds: BBox): Promise<[{ features: T[] } & FeatureCollection, Date]> {
public async queryGeoJson<T extends Feature>(
bounds: BBox
): Promise<[{ features: T[] } & FeatureCollection, Date]> {
const bbox =
"[bbox:" +
bounds.getSouth() +
@ -73,7 +75,7 @@ export class Overpass {
console.warn("No features for", this.buildUrl(query))
}
const geojson = <{ features: T[] } & FeatureCollection><any>osmtogeojson(json)
const geojson = <{ features: T[] } & FeatureCollection>(<any>osmtogeojson(json))
const osmTime = new Date(json.osm3s.timestamp_osm_base)
return [geojson, osmTime]
}

View file

@ -125,7 +125,7 @@ export default class PhotonSearch implements GeocodingProvider, ReverseGeocoding
if (query.length < 3) {
return []
}
if(query.indexOf("https://") >=0){
if (query.indexOf("https://") >= 0) {
// Photon gives a '403 forbidden' when this is part of the search string
return []
}

View file

@ -28,7 +28,7 @@ export class ThemeSearchIndex {
if (!themes) {
throw "No themes loaded. Did generate:layeroverview fail?"
}
this.themeWhitelist = new Set(themes.map(th => th.id))
this.themeWhitelist = new Set(themes.map((th) => th.id))
const fuseOptions: IFuseOptions<MinimalThemeInformation> = {
ignoreLocation: true,
threshold: 0.2,

View file

@ -247,7 +247,10 @@ export class TagUtils {
return tags
}
static SplitKeys(tagsFilters: UploadableTag[], currentProperties: Tags): Record<string, string[]> {
static SplitKeys(
tagsFilters: UploadableTag[],
currentProperties: Tags
): Record<string, string[]> {
return this.SplitKeysRegex(tagsFilters, false, currentProperties)
}
@ -256,8 +259,11 @@ export class TagUtils {
*
* TagUtils.SplitKeysRegex([new Tag("isced:level", "bachelor; master")], true) // => {"isced:level": ["bachelor","master"]}
*/
static SplitKeysRegex(tagsFilters: ReadonlyArray<UploadableTag>, allowRegex: false,
currentProperties: Tags): Record<string, string[]>
static SplitKeysRegex(
tagsFilters: ReadonlyArray<UploadableTag>,
allowRegex: false,
currentProperties: Tags
): Record<string, string[]>
static SplitKeysRegex(
tagsFilters: ReadonlyArray<UploadableTag>,
allowRegex: boolean,
@ -351,7 +357,10 @@ export class TagUtils {
* TagUtils.FlattenMultiAnswer(([new Tag("x","y"), new Tag("a","b")])) // => [new Tag("x","y"), new Tag("a","b")]
* TagUtils.FlattenMultiAnswer(([new Tag("x","")])) // => [new Tag("x","")]
*/
static FlattenMultiAnswer(tagsFilters: UploadableTag[], currentProperties: Tags): UploadableTag[] {
static FlattenMultiAnswer(
tagsFilters: UploadableTag[],
currentProperties: Tags
): UploadableTag[] {
if (tagsFilters === undefined) {
return []
}
@ -987,8 +996,8 @@ export class TagUtils {
public static removeEmptyParts(tag: UploadableTag): UploadableTag | true {
if (tag["and"]) {
const tags = <UploadableTag[]>tag["and"]
const cleaned = tags.map(t => TagUtils.removeEmptyParts(t))
const filtered = <UploadableTag[]>cleaned.filter(t => t !== true)
const cleaned = tags.map((t) => TagUtils.removeEmptyParts(t))
const filtered = <UploadableTag[]>cleaned.filter((t) => t !== true)
if (filtered.length === 0) {
return true
}
@ -1002,10 +1011,10 @@ export class TagUtils {
static flattenAnd(tg: UploadableTag | UploadableTag[]): (SubstitutingTag | Tag)[] {
if (Array.isArray(tg)) {
return tg.flatMap(t => TagUtils.flattenAnd(t))
return tg.flatMap((t) => TagUtils.flattenAnd(t))
}
if (tg["and"] || tg instanceof And) {
return tg["and"].flatMap(tg => TagUtils.flattenAnd(tg))
return tg["and"].flatMap((tg) => TagUtils.flattenAnd(tg))
}
return [tg]
}

View file

@ -88,8 +88,6 @@ export class Stores {
})
return newStore
}
}
export abstract class Store<T> implements Readable<T> {

View file

@ -188,6 +188,6 @@ export class AndroidPolyfill {
}
static exit() {
this.databridgePlugin.request({key: "exit"})
this.databridgePlugin.request({ key: "exit" })
}
}