Refactoring: fix rendering of new roads, generated by a split

This commit is contained in:
Pieter Vander Vennet 2023-04-20 01:52:23 +02:00
parent 840990c08b
commit 8eb2c68f79
34 changed files with 443 additions and 333 deletions

View file

@ -1,6 +1,9 @@
import { Changes } from "../Osm/Changes"
import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore";
import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore"
/**
* Applies tag changes onto the featureStore
*/
export default class ChangeToElementsActor {
constructor(changes: Changes, allElements: FeaturePropertiesStore) {
changes.pendingChanges.addCallbackAndRun((changes) => {

View file

@ -55,12 +55,13 @@ class SingleTileSaver {
*/
export default class SaveFeatureSourceToLocalStorage {
constructor(
backend: string,
layername: string,
zoomlevel: number,
features: FeatureSource,
featureProperties: FeaturePropertiesStore
) {
const storage = TileLocalStorage.construct<Feature[]>(layername)
const storage = TileLocalStorage.construct<Feature[]>(backend, layername)
const singleTileSavers: Map<number, SingleTileSaver> = new Map<number, SingleTileSaver>()
features.features.addCallbackAndRunD((features) => {
const sliced = GeoOperations.slice(zoomlevel, features)

View file

@ -17,14 +17,15 @@ export default class TileLocalStorage<T> {
this._layername = layername
}
public static construct<T>(layername: string): TileLocalStorage<T> {
const cached = TileLocalStorage.perLayer[layername]
public static construct<T>(backend: string, layername: string): TileLocalStorage<T> {
const key = backend + "_" + layername
const cached = TileLocalStorage.perLayer[key]
if (cached) {
return cached
}
const tls = new TileLocalStorage<T>(layername)
TileLocalStorage.perLayer[layername] = tls
const tls = new TileLocalStorage<T>(key)
TileLocalStorage.perLayer[key] = tls
return tls
}
@ -46,7 +47,7 @@ export default class TileLocalStorage<T> {
return src
}
private async SetIdb(tileIndex: number, data): Promise<void> {
private async SetIdb(tileIndex: number, data: any): Promise<void> {
try {
await IdbLocalStorage.SetDirectly(this._layername + "_" + tileIndex, data)
} catch (e) {

View file

@ -3,22 +3,18 @@
*/
import { Changes } from "../../Osm/Changes"
import { UIEventSource } from "../../UIEventSource"
import { FeatureSourceForLayer, IndexedFeatureSource } from "../FeatureSource"
import FilteredLayer from "../../../Models/FilteredLayer"
import { FeatureSource, IndexedFeatureSource } from "../FeatureSource"
import { ChangeDescription, ChangeDescriptionTools } from "../../Osm/Actions/ChangeDescription"
import { Feature } from "geojson"
export default class ChangeGeometryApplicator implements FeatureSourceForLayer {
public readonly features: UIEventSource<Feature[]> =
new UIEventSource<Feature[]>([])
public readonly layer: FilteredLayer
export default class ChangeGeometryApplicator implements FeatureSource {
public readonly features: UIEventSource<Feature[]> = new UIEventSource<Feature[]>([])
private readonly source: IndexedFeatureSource
private readonly changes: Changes
constructor(source: IndexedFeatureSource & FeatureSourceForLayer, changes: Changes) {
constructor(source: IndexedFeatureSource, changes: Changes) {
this.source = source
this.changes = changes
this.layer = source.layer
this.features = new UIEventSource<Feature[]>(undefined)
@ -30,10 +26,10 @@ export default class ChangeGeometryApplicator implements FeatureSourceForLayer {
private update() {
const upstreamFeatures = this.source.features.data
const upstreamIds = this.source.containedIds.data
const upstreamIds = this.source.featuresById.data
const changesToApply = this.changes.allChanges.data?.filter(
(ch) =>
// Does upsteram have this element? If not, we skip
// Does upstream have this element? If not, we skip
upstreamIds.has(ch.type + "/" + ch.id) &&
// Are any (geometry) changes defined?
ch.changes !== undefined &&
@ -61,7 +57,7 @@ export default class ChangeGeometryApplicator implements FeatureSourceForLayer {
for (const feature of upstreamFeatures) {
const changesForFeature = changesPerId.get(feature.properties.id)
if (changesForFeature === undefined) {
// No changes for this element
// No changes for this element - simply pass it along to downstream
newFeatures.push(feature)
continue
}

View file

@ -1,5 +1,5 @@
import { Store, UIEventSource } from "../../UIEventSource"
import { FeatureSource , IndexedFeatureSource } from "../FeatureSource"
import { FeatureSource, IndexedFeatureSource } from "../FeatureSource"
import { Feature } from "geojson"
import { Utils } from "../../../Utils"
@ -19,6 +19,7 @@ export default class FeatureSourceMerger implements IndexedFeatureSource {
this._featuresById = new UIEventSource<Map<string, Feature>>(undefined)
this.featuresById = this._featuresById
const self = this
sources = Utils.NoNull(sources)
for (let source of sources) {
source.features.addCallback(() => {
self.addData(sources.map((s) => s.features.data))
@ -28,7 +29,7 @@ export default class FeatureSourceMerger implements IndexedFeatureSource {
this._sources = sources
}
protected addSource(source: FeatureSource) {
public addSource(source: FeatureSource) {
this._sources.push(source)
source.features.addCallbackAndRun(() => {
this.addData(this._sources.map((s) => s.features.data))

View file

@ -4,13 +4,14 @@ import { FeatureSource } from "../FeatureSource"
import { Or } from "../../Tags/Or"
import FeatureSwitchState from "../../State/FeatureSwitchState"
import OverpassFeatureSource from "./OverpassFeatureSource"
import { ImmutableStore, Store } from "../../UIEventSource"
import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource"
import OsmFeatureSource from "./OsmFeatureSource"
import FeatureSourceMerger from "./FeatureSourceMerger"
import DynamicGeoJsonTileSource from "../TiledFeatureSource/DynamicGeoJsonTileSource"
import { BBox } from "../../BBox"
import LocalStorageFeatureSource from "../TiledFeatureSource/LocalStorageFeatureSource"
import StaticFeatureSource from "./StaticFeatureSource"
import { OsmPreferences } from "../../Osm/OsmPreferences"
/**
* This source will fetch the needed data from various sources for the given layout.
@ -18,15 +19,14 @@ import StaticFeatureSource from "./StaticFeatureSource"
* Note that special layers (with `source=null` will be ignored)
*/
export default class LayoutSource extends FeatureSourceMerger {
private readonly _isLoading: UIEventSource<boolean> = new UIEventSource<boolean>(false)
/**
* Indicates if a data source is loading something
* TODO fixme
*/
public readonly isLoading: Store<boolean> = new ImmutableStore(false)
public readonly isLoading: Store<boolean> = this._isLoading
constructor(
layers: LayerConfig[],
featureSwitches: FeatureSwitchState,
newAndChangedElements: FeatureSource,
mapProperties: { bounds: Store<BBox>; zoom: Store<number> },
backend: string,
isDisplayed: (id: string) => Store<boolean>
@ -39,7 +39,7 @@ export default class LayoutSource extends FeatureSourceMerger {
const osmLayers = layers.filter((layer) => layer.source.geojsonSource === undefined)
const fromCache = osmLayers.map(
(l) =>
new LocalStorageFeatureSource(l.id, 15, mapProperties, {
new LocalStorageFeatureSource(backend, l.id, 15, mapProperties, {
isActive: isDisplayed(l.id),
})
)
@ -56,7 +56,17 @@ export default class LayoutSource extends FeatureSourceMerger {
)
const expiryInSeconds = Math.min(...(layers?.map((l) => l.maxAgeOfCache) ?? []))
super(overpassSource, osmApiSource, newAndChangedElements, ...geojsonSources, ...fromCache)
super(overpassSource, osmApiSource, ...geojsonSources, ...fromCache)
const self = this
function setIsLoading() {
const loading = overpassSource?.runningQuery?.data && osmApiSource?.isRunning?.data
self._isLoading.setData(loading)
}
overpassSource?.runningQuery?.addCallbackAndRun((_) => setIsLoading())
osmApiSource?.isRunning?.addCallbackAndRun((_) => setIsLoading())
}
private static setupGeojsonSource(
@ -83,9 +93,9 @@ export default class LayoutSource extends FeatureSourceMerger {
zoom: Store<number>,
backend: string,
featureSwitches: FeatureSwitchState
): FeatureSource {
): OsmFeatureSource | undefined {
if (osmLayers.length == 0) {
return new StaticFeatureSource(new ImmutableStore([]))
return undefined
}
const minzoom = Math.min(...osmLayers.map((layer) => layer.minzoom))
const isActive = zoom.mapD((z) => {
@ -115,9 +125,9 @@ export default class LayoutSource extends FeatureSourceMerger {
bounds: Store<BBox>,
zoom: Store<number>,
featureSwitches: FeatureSwitchState
): FeatureSource {
): OverpassFeatureSource | undefined {
if (osmLayers.length == 0) {
return new StaticFeatureSource(new ImmutableStore([]))
return undefined
}
const minzoom = Math.min(...osmLayers.map((layer) => layer.minzoom))
const isActive = zoom.mapD((z) => {

View file

@ -1,13 +1,12 @@
import { Changes } from "../../Osm/Changes"
import { OsmNode, OsmObject, OsmRelation, OsmWay } from "../../Osm/OsmObject"
import { FeatureSource } from "../FeatureSource"
import { IndexedFeatureSource, WritableFeatureSource } from "../FeatureSource"
import { UIEventSource } from "../../UIEventSource"
import { ChangeDescription } from "../../Osm/Actions/ChangeDescription"
import { ElementStorage } from "../../ElementStorage"
import { OsmId, OsmTags } from "../../../Models/OsmFeature"
import { Feature } from "geojson"
export class NewGeometryFromChangesFeatureSource implements FeatureSource {
export class NewGeometryFromChangesFeatureSource implements WritableFeatureSource {
// This class name truly puts the 'Java' into 'Javascript'
/**
@ -18,7 +17,7 @@ export class NewGeometryFromChangesFeatureSource implements FeatureSource {
*/
public readonly features: UIEventSource<Feature[]> = new UIEventSource<Feature[]>([])
constructor(changes: Changes, allElementStorage: ElementStorage, backendUrl: string) {
constructor(changes: Changes, allElementStorage: IndexedFeatureSource, backendUrl: string) {
const seenChanges = new Set<ChangeDescription>()
const features = this.features.data
const self = this
@ -53,7 +52,7 @@ export class NewGeometryFromChangesFeatureSource implements FeatureSource {
// In _most_ of the cases, this means that this _isn't_ a new object
// However, when a point is snapped to an already existing point, we have to create a representation for this point!
// For this, we introspect the change
if (allElementStorage.has(change.type + "/" + change.id)) {
if (allElementStorage.featuresById.data.has(change.type + "/" + change.id)) {
// The current point already exists, we don't have to do anything here
continue
}
@ -65,7 +64,6 @@ export class NewGeometryFromChangesFeatureSource implements FeatureSource {
feat.tags[kv.k] = kv.v
}
const geojson = feat.asGeoJson()
allElementStorage.addOrGetElement(geojson)
self.features.data.push(geojson)
self.features.ping()
})

View file

@ -41,7 +41,6 @@ export default class OsmFeatureSource extends FeatureSourceMerger {
this.isActive = options.isActive ?? new ImmutableStore(true)
this._backend = options.backend ?? "https://www.openstreetmap.org"
this._bounds.addCallbackAndRunD((bbox) => this.loadData(bbox))
console.log("Allowed tags are:", this.allowedTags)
}
private async loadData(bbox: BBox) {
@ -108,7 +107,7 @@ export default class OsmFeatureSource extends FeatureSourceMerger {
}
private async LoadTile(z, x, y): Promise<void> {
console.log("OsmFeatureSource: loading ", z, x, y)
console.log("OsmFeatureSource: loading ", z, x, y, "from", this._backend)
if (z >= 22) {
throw "This is an absurd high zoom level"
}

View file

@ -22,13 +22,18 @@ export interface SnappingOptions {
* The resulting snap coordinates will be written into this UIEventSource
*/
snapLocation?: UIEventSource<{ lon: number; lat: number }>
/**
* If the projected point is within `reusePointWithin`-meter of an already existing point
*/
reusePointWithin?: number
}
export default class SnappingFeatureSource implements FeatureSource {
public readonly features: Store<Feature<Point>[]>
private readonly _snappedTo: UIEventSource<string>
/*Contains the id of the way it snapped to*/
public readonly snappedTo: Store<string>
private readonly _snappedTo: UIEventSource<string>
constructor(
snapTo: FeatureSource,

View file

@ -7,6 +7,7 @@ import StaticFeatureSource from "../Sources/StaticFeatureSource"
export default class LocalStorageFeatureSource extends DynamicTileSource {
constructor(
backend: string,
layername: string,
zoomlevel: number,
mapProperties: {
@ -17,7 +18,7 @@ export default class LocalStorageFeatureSource extends DynamicTileSource {
isActive?: Store<boolean>
}
) {
const storage = TileLocalStorage.construct<Feature[]>(layername)
const storage = TileLocalStorage.construct<Feature[]>(backend, layername)
super(
zoomlevel,
(tileIndex) =>

View file

@ -294,6 +294,10 @@ export class GeoOperations {
* Mostly used as helper for 'nearestPoint'
* @param way
*/
public static forceLineString(way: Feature<LineString | Polygon>): Feature<LineString>
public static forceLineString(
way: Feature<MultiLineString | MultiPolygon>
): Feature<MultiLineString>
public static forceLineString(
way: Feature<LineString | MultiLineString | Polygon | MultiPolygon>
): Feature<LineString | MultiLineString> {
@ -972,4 +976,9 @@ export class GeoOperations {
},
}
}
static centerpointCoordinatesObj(geojson: Feature) {
const [lon, lat] = GeoOperations.centerpointCoordinates(geojson)
return { lon, lat }
}
}

View file

@ -4,6 +4,7 @@ import { GeoOperations } from "../../GeoOperations"
import OsmChangeAction from "./OsmChangeAction"
import { ChangeDescription } from "./ChangeDescription"
import RelationSplitHandler from "./RelationSplitHandler"
import { Feature, LineString } from "geojson"
interface SplitInfo {
originalIndex?: number // or negative for new elements
@ -14,9 +15,9 @@ interface SplitInfo {
export default class SplitAction extends OsmChangeAction {
private readonly wayId: string
private readonly _splitPointsCoordinates: [number, number][] // lon, lat
private _meta: { theme: string; changeType: "split" }
private _toleranceInMeters: number
private _withNewCoordinates: (coordinates: [number, number][]) => void
private readonly _meta: { theme: string; changeType: "split" }
private readonly _toleranceInMeters: number
private readonly _withNewCoordinates: (coordinates: [number, number][]) => void
/**
* Create a changedescription for splitting a point.
@ -197,7 +198,7 @@ export default class SplitAction extends OsmChangeAction {
* If another point is closer then ~5m, we reuse that point
*/
private CalculateSplitCoordinates(osmWay: OsmWay, toleranceInM = 5): SplitInfo[] {
const wayGeoJson = osmWay.asGeoJson()
const wayGeoJson = <Feature<LineString>>osmWay.asGeoJson()
// Should be [lon, lat][]
const originalPoints: [number, number][] = osmWay.coordinates.map((c) => [c[1], c[0]])
const allPoints: {

View file

@ -18,10 +18,6 @@ import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesSto
* Needs an authenticator via OsmConnection
*/
export class Changes {
/**
* All the newly created features as featureSource + all the modified features
*/
public readonly features = new UIEventSource<{ feature: any; freshness: Date }[]>([])
public readonly pendingChanges: UIEventSource<ChangeDescription[]> =
LocalStorageSource.GetParsed<ChangeDescription[]>("pending-changes", [])
public readonly allChanges = new UIEventSource<ChangeDescription[]>(undefined)

View file

@ -213,7 +213,7 @@ class RewriteMetaInfoTags extends SimpleMetaTagger {
move("changeset", "_last_edit:changeset")
move("timestamp", "_last_edit:timestamp")
move("version", "_version_number")
feature.properties._backend = "https://openstreetmap.org"
feature.properties._backend = feature.properties._backend ?? "https://openstreetmap.org"
return movedSomething
}
}

View file

@ -38,8 +38,9 @@ export class IdbLocalStorage {
return src
}
public static SetDirectly(key: string, value): Promise<void> {
return idb.set(key, value)
public static SetDirectly(key: string, value: any): Promise<void> {
const copy = Utils.Clone(value)
return idb.set(key, copy)
}
static GetDirectly(key: string): Promise<void> {