Reformat all files with prettier

This commit is contained in:
Pieter Vander Vennet 2022-09-08 21:40:48 +02:00
parent e22d189376
commit b541d3eab4
382 changed files with 50893 additions and 35566 deletions

View file

@ -1,52 +1,52 @@
/**
* Applies geometry changes from 'Changes' onto every feature of a featureSource
*/
import {Changes} from "../../Osm/Changes";
import {UIEventSource} from "../../UIEventSource";
import {FeatureSourceForLayer, IndexedFeatureSource} from "../FeatureSource";
import FilteredLayer from "../../../Models/FilteredLayer";
import {ChangeDescription, ChangeDescriptionTools} from "../../Osm/Actions/ChangeDescription";
import { Changes } from "../../Osm/Changes"
import { UIEventSource } from "../../UIEventSource"
import { FeatureSourceForLayer, IndexedFeatureSource } from "../FeatureSource"
import FilteredLayer from "../../../Models/FilteredLayer"
import { ChangeDescription, ChangeDescriptionTools } from "../../Osm/Actions/ChangeDescription"
export default class ChangeGeometryApplicator implements FeatureSourceForLayer {
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<{ feature: any; freshness: Date }[]>([]);
public readonly name: string;
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]> =
new UIEventSource<{ feature: any; freshness: Date }[]>([])
public readonly name: string
public readonly layer: FilteredLayer
private readonly source: IndexedFeatureSource;
private readonly changes: Changes;
private readonly source: IndexedFeatureSource
private readonly changes: Changes
constructor(source: (IndexedFeatureSource & FeatureSourceForLayer), changes: Changes) {
this.source = source;
this.changes = changes;
constructor(source: IndexedFeatureSource & FeatureSourceForLayer, changes: Changes) {
this.source = source
this.changes = changes
this.layer = source.layer
this.name = "ChangesApplied(" + source.name + ")"
this.features = new UIEventSource<{ feature: any; freshness: Date }[]>(undefined)
const self = this;
source.features.addCallbackAndRunD(_ => self.update())
changes.allChanges.addCallbackAndRunD(_ => self.update())
const self = this
source.features.addCallbackAndRunD((_) => self.update())
changes.allChanges.addCallbackAndRunD((_) => self.update())
}
private update() {
const upstreamFeatures = this.source.features.data
const upstreamIds = this.source.containedIds.data
const changesToApply = this.changes.allChanges.data
?.filter(ch =>
const changesToApply = this.changes.allChanges.data?.filter(
(ch) =>
// Does upsteram have this element? If not, we skip
upstreamIds.has(ch.type + "/" + ch.id) &&
// Are any (geometry) changes defined?
ch.changes !== undefined &&
// Ignore new elements, they are handled by the NewGeometryFromChangesFeatureSource
ch.id > 0)
ch.id > 0
)
if (changesToApply === undefined || changesToApply.length === 0) {
// No changes to apply!
// Pass the original feature and lets continue our day
this.features.setData(upstreamFeatures);
return;
this.features.setData(upstreamFeatures)
return
}
const changesPerId = new Map<string, ChangeDescription[]>()
@ -58,27 +58,32 @@ export default class ChangeGeometryApplicator implements FeatureSourceForLayer {
changesPerId.set(key, [ch])
}
}
const newFeatures: { feature: any, freshness: Date }[] = []
const newFeatures: { feature: any; freshness: Date }[] = []
for (const feature of upstreamFeatures) {
const changesForFeature = changesPerId.get(feature.feature.properties.id)
if (changesForFeature === undefined) {
// No changes for this element
newFeatures.push(feature)
continue;
continue
}
// Allright! We have a feature to rewrite!
const copy = {
...feature
...feature,
}
// We only apply the last change as that one'll have the latest geometry
const change = changesForFeature[changesForFeature.length - 1]
copy.feature.geometry = ChangeDescriptionTools.getGeojsonGeometry(change)
console.log("Applying a geometry change onto:", feature,"The change is:", change,"which becomes:", copy)
console.log(
"Applying a geometry change onto:",
feature,
"The change is:",
change,
"which becomes:",
copy
)
newFeatures.push(copy)
}
this.features.setData(newFeatures)
}
}
}

View file

@ -1,99 +1,112 @@
import {UIEventSource} from "../../UIEventSource";
import FeatureSource, {FeatureSourceForLayer, IndexedFeatureSource, Tiled} from "../FeatureSource";
import FilteredLayer from "../../../Models/FilteredLayer";
import {Tiles} from "../../../Models/TileRange";
import {BBox} from "../../BBox";
import { UIEventSource } from "../../UIEventSource"
import FeatureSource, { FeatureSourceForLayer, IndexedFeatureSource, Tiled } from "../FeatureSource"
import FilteredLayer from "../../../Models/FilteredLayer"
import { Tiles } from "../../../Models/TileRange"
import { BBox } from "../../BBox"
export default class FeatureSourceMerger implements FeatureSourceForLayer, Tiled, IndexedFeatureSource {
public features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<{ feature: any; freshness: Date }[]>([]);
public readonly name;
export default class FeatureSourceMerger
implements FeatureSourceForLayer, Tiled, IndexedFeatureSource
{
public features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<
{ feature: any; freshness: Date }[]
>([])
public readonly name
public readonly layer: FilteredLayer
public readonly tileIndex: number;
public readonly bbox: BBox;
public readonly containedIds: UIEventSource<Set<string>> = new UIEventSource<Set<string>>(new Set())
private readonly _sources: UIEventSource<FeatureSource[]>;
public readonly tileIndex: number
public readonly bbox: BBox
public readonly containedIds: UIEventSource<Set<string>> = new UIEventSource<Set<string>>(
new Set()
)
private readonly _sources: UIEventSource<FeatureSource[]>
/**
* Merges features from different featureSources for a single layer
* Uses the freshest feature available in the case multiple sources offer data with the same identifier
*/
constructor(layer: FilteredLayer, tileIndex: number, bbox: BBox, sources: UIEventSource<FeatureSource[]>) {
this.tileIndex = tileIndex;
this.bbox = bbox;
this._sources = sources;
this.layer = layer;
this.name = "FeatureSourceMerger(" + layer.layerDef.id + ", " + Tiles.tile_from_index(tileIndex).join(",") + ")"
const self = this;
constructor(
layer: FilteredLayer,
tileIndex: number,
bbox: BBox,
sources: UIEventSource<FeatureSource[]>
) {
this.tileIndex = tileIndex
this.bbox = bbox
this._sources = sources
this.layer = layer
this.name =
"FeatureSourceMerger(" +
layer.layerDef.id +
", " +
Tiles.tile_from_index(tileIndex).join(",") +
")"
const self = this
const handledSources = new Set<FeatureSource>();
const handledSources = new Set<FeatureSource>()
sources.addCallbackAndRunD(sources => {
let newSourceRegistered = false;
sources.addCallbackAndRunD((sources) => {
let newSourceRegistered = false
for (let i = 0; i < sources.length; i++) {
let source = sources[i];
let source = sources[i]
if (handledSources.has(source)) {
continue
}
handledSources.add(source)
newSourceRegistered = true
source.features.addCallback(() => {
self.Update();
});
self.Update()
})
if (newSourceRegistered) {
self.Update();
self.Update()
}
}
})
}
private Update() {
let somethingChanged = false;
const all: Map<string, { feature: any, freshness: Date }> = new Map<string, { feature: any; freshness: Date }>();
let somethingChanged = false
const all: Map<string, { feature: any; freshness: Date }> = new Map<
string,
{ feature: any; freshness: Date }
>()
// We seed the dictionary with the previously loaded features
const oldValues = this.features.data ?? [];
const oldValues = this.features.data ?? []
for (const oldValue of oldValues) {
all.set(oldValue.feature.id, oldValue)
}
for (const source of this._sources.data) {
if (source?.features?.data === undefined) {
continue;
continue
}
for (const f of source.features.data) {
const id = f.feature.properties.id;
const id = f.feature.properties.id
if (!all.has(id)) {
// This is a new feature
somethingChanged = true;
all.set(id, f);
continue;
somethingChanged = true
all.set(id, f)
continue
}
// This value has been seen already, either in a previous run or by a previous datasource
// Let's figure out if something changed
const oldV = all.get(id);
const oldV = all.get(id)
if (oldV.freshness < f.freshness) {
// Jup, this feature is fresher
all.set(id, f);
somethingChanged = true;
all.set(id, f)
somethingChanged = true
}
}
}
if (!somethingChanged) {
// We don't bother triggering an update
return;
return
}
const newList = [];
const newList = []
all.forEach((value, _) => {
newList.push(value)
})
this.containedIds.setData(new Set(all.keys()))
this.features.setData(newList);
this.features.setData(newList)
}
}
}

View file

@ -1,34 +1,35 @@
import {Store, UIEventSource} from "../../UIEventSource";
import FilteredLayer, {FilterState} from "../../../Models/FilteredLayer";
import {FeatureSourceForLayer, Tiled} from "../FeatureSource";
import {BBox} from "../../BBox";
import {ElementStorage} from "../../ElementStorage";
import {TagsFilter} from "../../Tags/TagsFilter";
import {OsmFeature} from "../../../Models/OsmFeature";
import { Store, UIEventSource } from "../../UIEventSource"
import FilteredLayer, { FilterState } from "../../../Models/FilteredLayer"
import { FeatureSourceForLayer, Tiled } from "../FeatureSource"
import { BBox } from "../../BBox"
import { ElementStorage } from "../../ElementStorage"
import { TagsFilter } from "../../Tags/TagsFilter"
import { OsmFeature } from "../../../Models/OsmFeature"
export default class FilteringFeatureSource implements FeatureSourceForLayer, Tiled {
public features: UIEventSource<{ feature: any; freshness: Date }[]> =
new UIEventSource<{ feature: any; freshness: Date }[]>([]);
public readonly name;
public readonly layer: FilteredLayer;
public features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<
{ feature: any; freshness: Date }[]
>([])
public readonly name
public readonly layer: FilteredLayer
public readonly tileIndex: number
public readonly bbox: BBox
private readonly upstream: FeatureSourceForLayer;
private readonly upstream: FeatureSourceForLayer
private readonly state: {
locationControl: Store<{ zoom: number }>;
selectedElement: Store<any>,
globalFilters: Store<{ filter: FilterState }[]>,
locationControl: Store<{ zoom: number }>
selectedElement: Store<any>
globalFilters: Store<{ filter: FilterState }[]>
allElements: ElementStorage
};
private readonly _alreadyRegistered = new Set<UIEventSource<any>>();
}
private readonly _alreadyRegistered = new Set<UIEventSource<any>>()
private readonly _is_dirty = new UIEventSource(false)
private previousFeatureSet: Set<any> = undefined;
private previousFeatureSet: Set<any> = undefined
constructor(
state: {
locationControl: Store<{ zoom: number }>,
selectedElement: Store<any>,
allElements: ElementStorage,
locationControl: Store<{ zoom: number }>
selectedElement: Store<any>
allElements: ElementStorage
globalFilters: Store<{ filter: FilterState }[]>
},
tileIndex,
@ -41,92 +42,95 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti
this.upstream = upstream
this.state = state
this.layer = upstream.layer;
const layer = upstream.layer;
const self = this;
this.layer = upstream.layer
const layer = upstream.layer
const self = this
upstream.features.addCallback(() => {
self.update();
});
layer.appliedFilters.addCallback(_ => {
self.update()
})
this._is_dirty.stabilized(1000).addCallbackAndRunD(dirty => {
layer.appliedFilters.addCallback((_) => {
self.update()
})
this._is_dirty.stabilized(1000).addCallbackAndRunD((dirty) => {
if (dirty) {
self.update()
}
})
metataggingUpdated?.addCallback(_ => {
metataggingUpdated?.addCallback((_) => {
self._is_dirty.setData(true)
})
state.globalFilters.addCallback(_ => {
state.globalFilters.addCallback((_) => {
self.update()
})
this.update();
this.update()
}
private update() {
const self = this;
const layer = this.upstream.layer;
const features: { feature: OsmFeature; freshness: Date }[] = (this.upstream.features.data ?? []);
const includedFeatureIds = new Set<string>();
const globalFilters = self.state.globalFilters.data.map(f => f.filter);
const self = this
const layer = this.upstream.layer
const features: { feature: OsmFeature; freshness: Date }[] =
this.upstream.features.data ?? []
const includedFeatureIds = new Set<string>()
const globalFilters = self.state.globalFilters.data.map((f) => f.filter)
const newFeatures = (features ?? []).filter((f) => {
self.registerCallback(f.feature)
const isShown: TagsFilter = layer.layerDef.isShown;
const tags = f.feature.properties;
if (isShown !== undefined && !isShown.matchesProperties(tags) ) {
return false;
const isShown: TagsFilter = layer.layerDef.isShown
const tags = f.feature.properties
if (isShown !== undefined && !isShown.matchesProperties(tags)) {
return false
}
const tagsFilter = Array.from(layer.appliedFilters?.data?.values() ?? [])
for (const filter of tagsFilter) {
const neededTags: TagsFilter = filter?.currentFilter
if (neededTags !== undefined && !neededTags.matchesProperties(f.feature.properties)) {
if (
neededTags !== undefined &&
!neededTags.matchesProperties(f.feature.properties)
) {
// Hidden by the filter on the layer itself - we want to hide it no matter what
return false;
return false
}
}
for (const filter of globalFilters) {
const neededTags: TagsFilter = filter?.currentFilter
if (neededTags !== undefined && !neededTags.matchesProperties(f.feature.properties)) {
if (
neededTags !== undefined &&
!neededTags.matchesProperties(f.feature.properties)
) {
// Hidden by the filter on the layer itself - we want to hide it no matter what
return false;
return false
}
}
includedFeatureIds.add(f.feature.properties.id)
return true;
});
return true
})
const previousSet = this.previousFeatureSet;
const previousSet = this.previousFeatureSet
this._is_dirty.setData(false)
// Is there any difference between the two sets?
if (previousSet !== undefined && previousSet.size === includedFeatureIds.size) {
// The size of the sets is the same - they _might_ be identical
const newItemFound = Array.from(includedFeatureIds).some(id => !previousSet.has(id))
const newItemFound = Array.from(includedFeatureIds).some((id) => !previousSet.has(id))
if (!newItemFound) {
// We know that:
// We know that:
// - The sets have the same size
// - Every item from the new set has been found in the old set
// which means they are identical!
return;
return
}
}
// Something new has been found!
this.features.setData(newFeatures);
this.features.setData(newFeatures)
}
private registerCallback(feature: any) {
@ -139,11 +143,10 @@ export default class FilteringFeatureSource implements FeatureSourceForLayer, Ti
}
this._alreadyRegistered.add(src)
const self = this;
const self = this
// Add a callback as a changed tag migh change the filter
src.addCallbackAndRunD(_ => {
src.addCallbackAndRunD((_) => {
self._is_dirty.setData(true)
})
}
}

View file

@ -1,168 +1,163 @@
/**
* Fetches a geojson file somewhere and passes it along
*/
import {UIEventSource} from "../../UIEventSource";
import FilteredLayer from "../../../Models/FilteredLayer";
import {Utils} from "../../../Utils";
import {FeatureSourceForLayer, Tiled} from "../FeatureSource";
import {Tiles} from "../../../Models/TileRange";
import {BBox} from "../../BBox";
import {GeoOperations} from "../../GeoOperations";
import { UIEventSource } from "../../UIEventSource"
import FilteredLayer from "../../../Models/FilteredLayer"
import { Utils } from "../../../Utils"
import { FeatureSourceForLayer, Tiled } from "../FeatureSource"
import { Tiles } from "../../../Models/TileRange"
import { BBox } from "../../BBox"
import { GeoOperations } from "../../GeoOperations"
export default class GeoJsonSource implements FeatureSourceForLayer, Tiled {
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>;
public readonly state = new UIEventSource<undefined | {error: string} | "loaded">(undefined)
public readonly name;
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>
public readonly state = new UIEventSource<undefined | { error: string } | "loaded">(undefined)
public readonly name
public readonly isOsmCache: boolean
public readonly layer: FilteredLayer;
public readonly layer: FilteredLayer
public readonly tileIndex
public readonly bbox;
private readonly seenids: Set<string>;
private readonly idKey ?: string;
public constructor(flayer: FilteredLayer,
zxy?: [number, number, number] | BBox,
options?: {
featureIdBlacklist?: Set<string>
}) {
public readonly bbox
private readonly seenids: Set<string>
private readonly idKey?: string
public constructor(
flayer: FilteredLayer,
zxy?: [number, number, number] | BBox,
options?: {
featureIdBlacklist?: Set<string>
}
) {
if (flayer.layerDef.source.geojsonZoomLevel !== undefined && zxy === undefined) {
throw "Dynamic layers are not supported. Use 'DynamicGeoJsonTileSource instead"
}
this.layer = flayer;
this.layer = flayer
this.idKey = flayer.layerDef.source.idKey
this.seenids = options?.featureIdBlacklist ?? new Set<string>()
let url = flayer.layerDef.source.geojsonSource.replace("{layer}", flayer.layerDef.id);
let url = flayer.layerDef.source.geojsonSource.replace("{layer}", flayer.layerDef.id)
if (zxy !== undefined) {
let tile_bbox: BBox;
let tile_bbox: BBox
if (zxy instanceof BBox) {
tile_bbox = zxy;
tile_bbox = zxy
} else {
const [z, x, y] = zxy;
tile_bbox = BBox.fromTile(z, x, y);
const [z, x, y] = zxy
tile_bbox = BBox.fromTile(z, x, y)
this.tileIndex = Tiles.tile_index(z, x, y)
this.bbox = BBox.fromTile(z, x, y)
url = url
.replace('{z}', "" + z)
.replace('{x}', "" + x)
.replace('{y}', "" + y)
.replace("{z}", "" + z)
.replace("{x}", "" + x)
.replace("{y}", "" + y)
}
let bounds: { minLat: number, maxLat: number, minLon: number, maxLon: number } = tile_bbox
let bounds: { minLat: number; maxLat: number; minLon: number; maxLon: number } =
tile_bbox
if (this.layer.layerDef.source.mercatorCrs) {
bounds = tile_bbox.toMercator()
}
url = url
.replace('{y_min}', "" + bounds.minLat)
.replace('{y_max}', "" + bounds.maxLat)
.replace('{x_min}', "" + bounds.minLon)
.replace('{x_max}', "" + bounds.maxLon)
.replace("{y_min}", "" + bounds.minLat)
.replace("{y_max}", "" + bounds.maxLat)
.replace("{x_min}", "" + bounds.minLon)
.replace("{x_max}", "" + bounds.maxLon)
} else {
this.tileIndex = Tiles.tile_index(0, 0, 0)
this.bbox = BBox.global;
this.bbox = BBox.global
}
this.name = "GeoJsonSource of " + url;
this.name = "GeoJsonSource of " + url
this.isOsmCache = flayer.layerDef.source.isOsmCacheLayer;
this.isOsmCache = flayer.layerDef.source.isOsmCacheLayer
this.features = new UIEventSource<{ feature: any; freshness: Date }[]>([])
this.LoadJSONFrom(url)
}
private LoadJSONFrom(url: string) {
const eventSource = this.features;
const self = this;
const eventSource = this.features
const self = this
Utils.downloadJsonCached(url, 60 * 60)
.then(json => {
.then((json) => {
self.state.setData("loaded")
// TODO: move somewhere else, just for testing
// Check for maproulette data
if (url.startsWith("https://maproulette.org/api/v2/tasks/box/")) {
console.log("MapRoulette data detected")
const data = json;
let maprouletteFeatures: any[] = [];
data.forEach(element => {
const data = json
let maprouletteFeatures: any[] = []
data.forEach((element) => {
maprouletteFeatures.push({
type: "Feature",
geometry: {
type: "Point",
coordinates: [element.point.lng, element.point.lat]
coordinates: [element.point.lng, element.point.lat],
},
properties: {
// Map all properties to the feature
...element,
}
});
});
json.features = maprouletteFeatures;
},
})
})
json.features = maprouletteFeatures
}
if (json.features === undefined || json.features === null) {
return;
return
}
if (self.layer.layerDef.source.mercatorCrs) {
json = GeoOperations.GeoJsonToWGS84(json)
}
const time = new Date();
const newFeatures: { feature: any, freshness: Date } [] = []
let i = 0;
let skipped = 0;
const time = new Date()
const newFeatures: { feature: any; freshness: Date }[] = []
let i = 0
let skipped = 0
for (const feature of json.features) {
const props = feature.properties
for (const key in props) {
if(props[key] === null){
if (props[key] === null) {
delete props[key]
}
if (typeof props[key] !== "string") {
// Make sure all the values are string, it crashes stuff otherwise
props[key] = JSON.stringify(props[key])
}
}
if(self.idKey !== undefined){
if (self.idKey !== undefined) {
props.id = props[self.idKey]
}
if (props.id === undefined) {
props.id = url + "/" + i;
feature.id = url + "/" + i;
i++;
props.id = url + "/" + i
feature.id = url + "/" + i
i++
}
if (self.seenids.has(props.id)) {
skipped++;
continue;
skipped++
continue
}
self.seenids.add(props.id)
let freshness: Date = time;
let freshness: Date = time
if (feature.properties["_last_edit:timestamp"] !== undefined) {
freshness = new Date(props["_last_edit:timestamp"])
}
newFeatures.push({feature: feature, freshness: freshness})
newFeatures.push({ feature: feature, freshness: freshness })
}
if (newFeatures.length == 0) {
return;
return
}
eventSource.setData(eventSource.data.concat(newFeatures))
}).catch(msg => {
console.debug("Could not load geojson layer", url, "due to", msg);
self.state.setData({error: msg})
})
})
.catch((msg) => {
console.debug("Could not load geojson layer", url, "due to", msg)
self.state.setData({ error: msg })
})
}
}

View file

@ -1,50 +1,50 @@
import {Changes} from "../../Osm/Changes";
import {OsmNode, OsmObject, OsmRelation, OsmWay} from "../../Osm/OsmObject";
import FeatureSource from "../FeatureSource";
import {UIEventSource} from "../../UIEventSource";
import {ChangeDescription} from "../../Osm/Actions/ChangeDescription";
import {ElementStorage} from "../../ElementStorage";
import {OsmId, OsmTags} from "../../../Models/OsmFeature";
import { Changes } from "../../Osm/Changes"
import { OsmNode, OsmObject, OsmRelation, OsmWay } from "../../Osm/OsmObject"
import FeatureSource from "../FeatureSource"
import { UIEventSource } from "../../UIEventSource"
import { ChangeDescription } from "../../Osm/Actions/ChangeDescription"
import { ElementStorage } from "../../ElementStorage"
import { OsmId, OsmTags } from "../../../Models/OsmFeature"
export class NewGeometryFromChangesFeatureSource implements FeatureSource {
// This class name truly puts the 'Java' into 'Javascript'
/**
* A feature source containing exclusively new elements.
*
*
* These elements are probably created by the 'SimpleAddUi' which generates a new point, but the import functionality might create a line or polygon too.
* Other sources of new points are e.g. imports from nodes
*/
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<{ feature: any; freshness: Date }[]>([]);
public readonly name: string = "newFeatures";
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]> =
new UIEventSource<{ feature: any; freshness: Date }[]>([])
public readonly name: string = "newFeatures"
constructor(changes: Changes, allElementStorage: ElementStorage, backendUrl: string) {
const seenChanges = new Set<ChangeDescription>()
const features = this.features.data
const self = this
const seenChanges = new Set<ChangeDescription>();
const features = this.features.data;
const self = this;
changes.pendingChanges.stabilized(100).addCallbackAndRunD(changes => {
changes.pendingChanges.stabilized(100).addCallbackAndRunD((changes) => {
if (changes.length === 0) {
return;
return
}
const now = new Date();
let somethingChanged = false;
const now = new Date()
let somethingChanged = false
function add(feature) {
feature.id = feature.properties.id
features.push({
feature: feature,
freshness: now
freshness: now,
})
somethingChanged = true;
somethingChanged = true
}
for (const change of changes) {
if (seenChanges.has(change)) {
// Already handled
continue;
continue
}
seenChanges.add(change)
@ -60,35 +60,32 @@ export class NewGeometryFromChangesFeatureSource implements FeatureSource {
// For this, we introspect the change
if (allElementStorage.has(change.type + "/" + change.id)) {
// The current point already exists, we don't have to do anything here
continue;
continue
}
console.debug("Detected a reused point")
// The 'allElementsStore' does _not_ have this point yet, so we have to create it
OsmObject.DownloadObjectAsync(change.type + "/" + change.id).then(feat => {
OsmObject.DownloadObjectAsync(change.type + "/" + change.id).then((feat) => {
console.log("Got the reused point:", feat)
for (const kv of change.tags) {
feat.tags[kv.k] = kv.v
}
const geojson = feat.asGeoJson();
const geojson = feat.asGeoJson()
allElementStorage.addOrGetElement(geojson)
self.features.data.push({feature: geojson, freshness: new Date()})
self.features.data.push({ feature: geojson, freshness: new Date() })
self.features.ping()
})
continue
} else if (change.id < 0 && change.changes === undefined) {
// The geometry is not described - not a new point
if (change.id < 0) {
console.error("WARNING: got a new point without geometry!")
}
continue;
continue
}
try {
const tags: OsmTags = {
id: <OsmId> (change.type + "/" + change.id)
id: <OsmId>(change.type + "/" + change.id),
}
for (const kv of change.tags) {
tags[kv.k] = kv.v
@ -104,30 +101,31 @@ export class NewGeometryFromChangesFeatureSource implements FeatureSource {
n.lon = change.changes["lon"]
const geojson = n.asGeoJson()
add(geojson)
break;
break
case "way":
const w = new OsmWay(change.id)
w.tags = tags
w.nodes = change.changes["nodes"]
w.coordinates = change.changes["coordinates"].map(([lon, lat]) => [lat, lon])
w.coordinates = change.changes["coordinates"].map(([lon, lat]) => [
lat,
lon,
])
add(w.asGeoJson())
break;
break
case "relation":
const r = new OsmRelation(change.id)
r.tags = tags
r.members = change.changes["members"]
add(r.asGeoJson())
break;
break
}
} catch (e) {
console.error("Could not generate a new geometry to render on screen for:", e)
}
}
if (somethingChanged) {
self.features.ping()
}
})
}
}
}

View file

@ -2,34 +2,36 @@
* Every previously added point is remembered, but new points are added.
* Data coming from upstream will always overwrite a previous value
*/
import FeatureSource, {Tiled} from "../FeatureSource";
import {Store, UIEventSource} from "../../UIEventSource";
import {BBox} from "../../BBox";
import FeatureSource, { Tiled } from "../FeatureSource"
import { Store, UIEventSource } from "../../UIEventSource"
import { BBox } from "../../BBox"
export default class RememberingSource implements FeatureSource, Tiled {
public readonly features: Store<{ feature: any, freshness: Date }[]>;
public readonly name;
public readonly features: Store<{ feature: any; freshness: Date }[]>
public readonly name
public readonly tileIndex: number
public readonly bbox: BBox
constructor(source: FeatureSource & Tiled) {
const self = this;
this.name = "RememberingSource of " + source.name;
const self = this
this.name = "RememberingSource of " + source.name
this.tileIndex = source.tileIndex
this.bbox = source.bbox;
this.bbox = source.bbox
const empty = [];
const featureSource = new UIEventSource<{feature: any, freshness: Date}[]>(empty)
const empty = []
const featureSource = new UIEventSource<{ feature: any; freshness: Date }[]>(empty)
this.features = featureSource
source.features.addCallbackAndRunD(features => {
const oldFeatures = self.features?.data ?? empty;
source.features.addCallbackAndRunD((features) => {
const oldFeatures = self.features?.data ?? empty
// Then new ids
const ids = new Set<string>(features.map(f => f.feature.properties.id + f.feature.geometry.type));
const ids = new Set<string>(
features.map((f) => f.feature.properties.id + f.feature.geometry.type)
)
// the old data
const oldData = oldFeatures.filter(old => !ids.has(old.feature.properties.id + old.feature.geometry.type))
const oldData = oldFeatures.filter(
(old) => !ids.has(old.feature.properties.id + old.feature.geometry.type)
)
featureSource.setData([...features, ...oldData])
})
}
}
}

View file

@ -1,50 +1,57 @@
/**
* This feature source helps the ShowDataLayer class: it introduces the necessary extra features and indicates with what renderConfig it should be rendered.
*/
import {Store} from "../../UIEventSource";
import {GeoOperations} from "../../GeoOperations";
import FeatureSource from "../FeatureSource";
import PointRenderingConfig from "../../../Models/ThemeConfig/PointRenderingConfig";
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
import LineRenderingConfig from "../../../Models/ThemeConfig/LineRenderingConfig";
import { Store } from "../../UIEventSource"
import { GeoOperations } from "../../GeoOperations"
import FeatureSource from "../FeatureSource"
import PointRenderingConfig from "../../../Models/ThemeConfig/PointRenderingConfig"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import LineRenderingConfig from "../../../Models/ThemeConfig/LineRenderingConfig"
export default class RenderingMultiPlexerFeatureSource {
public readonly features: Store<(any & { pointRenderingIndex: number | undefined, lineRenderingIndex: number | undefined })[]>;
private readonly pointRenderings: { rendering: PointRenderingConfig; index: number }[];
private centroidRenderings: { rendering: PointRenderingConfig; index: number }[];
private projectedCentroidRenderings: { rendering: PointRenderingConfig; index: number }[];
private startRenderings: { rendering: PointRenderingConfig; index: number }[];
private endRenderings: { rendering: PointRenderingConfig; index: number }[];
private hasCentroid: boolean;
private lineRenderObjects: LineRenderingConfig[];
public readonly features: Store<
(any & {
pointRenderingIndex: number | undefined
lineRenderingIndex: number | undefined
})[]
>
private readonly pointRenderings: { rendering: PointRenderingConfig; index: number }[]
private centroidRenderings: { rendering: PointRenderingConfig; index: number }[]
private projectedCentroidRenderings: { rendering: PointRenderingConfig; index: number }[]
private startRenderings: { rendering: PointRenderingConfig; index: number }[]
private endRenderings: { rendering: PointRenderingConfig; index: number }[]
private hasCentroid: boolean
private lineRenderObjects: LineRenderingConfig[]
private inspectFeature(feat, addAsPoint: (feat, rendering, centerpoint: [number, number]) => void, withIndex: any[]){
private inspectFeature(
feat,
addAsPoint: (feat, rendering, centerpoint: [number, number]) => void,
withIndex: any[]
) {
if (feat.geometry.type === "Point") {
for (const rendering of this.pointRenderings) {
withIndex.push({
...feat,
pointRenderingIndex: rendering.index
pointRenderingIndex: rendering.index,
})
}
} else {
// This is a a line: add the centroids
let centerpoint: [number, number] = undefined;
let projectedCenterPoint : [number, number] = undefined
if(this.hasCentroid){
centerpoint = GeoOperations.centerpointCoordinates(feat)
if(this.projectedCentroidRenderings.length > 0){
projectedCenterPoint = <[number,number]> GeoOperations.nearestPoint(feat, centerpoint).geometry.coordinates
let centerpoint: [number, number] = undefined
let projectedCenterPoint: [number, number] = undefined
if (this.hasCentroid) {
centerpoint = GeoOperations.centerpointCoordinates(feat)
if (this.projectedCentroidRenderings.length > 0) {
projectedCenterPoint = <[number, number]>(
GeoOperations.nearestPoint(feat, centerpoint).geometry.coordinates
)
}
}
for (const rendering of this.centroidRenderings) {
addAsPoint(feat, rendering, centerpoint)
}
if (feat.geometry.type === "LineString") {
for (const rendering of this.projectedCentroidRenderings) {
addAsPoint(feat, rendering, projectedCenterPoint)
}
@ -58,73 +65,69 @@ export default class RenderingMultiPlexerFeatureSource {
const coordinate = coordinates[coordinates.length - 1]
addAsPoint(feat, rendering, coordinate)
}
}else{
} else {
for (const rendering of this.projectedCentroidRenderings) {
addAsPoint(feat, rendering, centerpoint)
}
}
// AT last, add it 'as is' to what we should render
// AT last, add it 'as is' to what we should render
for (let i = 0; i < this.lineRenderObjects.length; i++) {
withIndex.push({
...feat,
lineRenderingIndex: i
lineRenderingIndex: i,
})
}
}
}
constructor(upstream: FeatureSource, layer: LayerConfig) {
const pointRenderObjects: { rendering: PointRenderingConfig, index: number }[] = layer.mapRendering.map((r, i) => ({
rendering: r,
index: i
}))
this.pointRenderings = pointRenderObjects.filter(r => r.rendering.location.has("point"))
this.centroidRenderings = pointRenderObjects.filter(r => r.rendering.location.has("centroid"))
this.projectedCentroidRenderings = pointRenderObjects.filter(r => r.rendering.location.has("projected_centerpoint"))
this.startRenderings = pointRenderObjects.filter(r => r.rendering.location.has("start"))
this.endRenderings = pointRenderObjects.filter(r => r.rendering.location.has("end"))
this.hasCentroid = this.centroidRenderings.length > 0 || this.projectedCentroidRenderings.length > 0
const pointRenderObjects: { rendering: PointRenderingConfig; index: number }[] =
layer.mapRendering.map((r, i) => ({
rendering: r,
index: i,
}))
this.pointRenderings = pointRenderObjects.filter((r) => r.rendering.location.has("point"))
this.centroidRenderings = pointRenderObjects.filter((r) =>
r.rendering.location.has("centroid")
)
this.projectedCentroidRenderings = pointRenderObjects.filter((r) =>
r.rendering.location.has("projected_centerpoint")
)
this.startRenderings = pointRenderObjects.filter((r) => r.rendering.location.has("start"))
this.endRenderings = pointRenderObjects.filter((r) => r.rendering.location.has("end"))
this.hasCentroid =
this.centroidRenderings.length > 0 || this.projectedCentroidRenderings.length > 0
this.lineRenderObjects = layer.lineRendering
this.features = upstream.features.map(
features => {
if (features === undefined) {
return undefined;
}
const withIndex: any[] = [];
function addAsPoint(feat, rendering, coordinate) {
const patched = {
...feat,
pointRenderingIndex: rendering.index
}
patched.geometry = {
type: "Point",
coordinates: coordinate
}
withIndex.push(patched)
}
for (const f of features) {
const feat = f.feature;
if(feat === undefined){
continue
}
this.inspectFeature(feat, addAsPoint, withIndex)
}
return withIndex;
this.features = upstream.features.map((features) => {
if (features === undefined) {
return undefined
}
);
const withIndex: any[] = []
function addAsPoint(feat, rendering, coordinate) {
const patched = {
...feat,
pointRenderingIndex: rendering.index,
}
patched.geometry = {
type: "Point",
coordinates: coordinate,
}
withIndex.push(patched)
}
for (const f of features) {
const feat = f.feature
if (feat === undefined) {
continue
}
this.inspectFeature(feat, addAsPoint, withIndex)
}
return withIndex
})
}
}
}

View file

@ -1,21 +1,24 @@
import {UIEventSource} from "../../UIEventSource";
import FilteredLayer from "../../../Models/FilteredLayer";
import {FeatureSourceForLayer, Tiled} from "../FeatureSource";
import {BBox} from "../../BBox";
import { UIEventSource } from "../../UIEventSource"
import FilteredLayer from "../../../Models/FilteredLayer"
import { FeatureSourceForLayer, Tiled } from "../FeatureSource"
import { BBox } from "../../BBox"
export default class SimpleFeatureSource implements FeatureSourceForLayer, Tiled {
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>;
public readonly name: string = "SimpleFeatureSource";
public readonly layer: FilteredLayer;
public readonly bbox: BBox = BBox.global;
public readonly tileIndex: number;
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>
public readonly name: string = "SimpleFeatureSource"
public readonly layer: FilteredLayer
public readonly bbox: BBox = BBox.global
public readonly tileIndex: number
constructor(layer: FilteredLayer, tileIndex: number, featureSource?: UIEventSource<{ feature: any; freshness: Date }[]> ) {
constructor(
layer: FilteredLayer,
tileIndex: number,
featureSource?: UIEventSource<{ feature: any; freshness: Date }[]>
) {
this.name = "SimpleFeatureSource(" + layer.layerDef.id + ")"
this.layer = layer
this.tileIndex = tileIndex ?? 0;
this.tileIndex = tileIndex ?? 0
this.bbox = BBox.fromTileIndex(this.tileIndex)
this.features = featureSource ?? new UIEventSource<{ feature: any; freshness: Date }[]>([]);
this.features = featureSource ?? new UIEventSource<{ feature: any; freshness: Date }[]>([])
}
}
}

View file

@ -1,62 +1,90 @@
import FeatureSource, {FeatureSourceForLayer, Tiled} from "../FeatureSource";
import {ImmutableStore, Store, UIEventSource} from "../../UIEventSource";
import {stat} from "fs";
import FilteredLayer from "../../../Models/FilteredLayer";
import {BBox} from "../../BBox";
import {Feature} from "@turf/turf";
import FeatureSource, { FeatureSourceForLayer, Tiled } from "../FeatureSource"
import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource"
import { stat } from "fs"
import FilteredLayer from "../../../Models/FilteredLayer"
import { BBox } from "../../BBox"
import { Feature } from "@turf/turf"
/**
* A simple, read only feature store.
*/
export default class StaticFeatureSource implements FeatureSource {
public readonly features: Store<{ feature: any; freshness: Date }[]>;
public readonly features: Store<{ feature: any; freshness: Date }[]>
public readonly name: string
constructor(features: Store<{ feature: Feature, freshness: Date }[]>, name = "StaticFeatureSource") {
constructor(
features: Store<{ feature: Feature; freshness: Date }[]>,
name = "StaticFeatureSource"
) {
if (features === undefined) {
throw "Static feature source received undefined as source"
}
this.name = name;
this.features = features;
this.name = name
this.features = features
}
public static fromGeojsonAndDate(features: { feature: Feature, freshness: Date }[], name = "StaticFeatureSourceFromGeojsonAndDate"): StaticFeatureSource {
return new StaticFeatureSource(new ImmutableStore(features), name);
public static fromGeojsonAndDate(
features: { feature: Feature; freshness: Date }[],
name = "StaticFeatureSourceFromGeojsonAndDate"
): StaticFeatureSource {
return new StaticFeatureSource(new ImmutableStore(features), name)
}
public static fromGeojson(geojson: Feature[], name = "StaticFeatureSourceFromGeojson"): StaticFeatureSource {
const now = new Date();
return StaticFeatureSource.fromGeojsonAndDate(geojson.map(feature => ({feature, freshness: now})), name);
public static fromGeojson(
geojson: Feature[],
name = "StaticFeatureSourceFromGeojson"
): StaticFeatureSource {
const now = new Date()
return StaticFeatureSource.fromGeojsonAndDate(
geojson.map((feature) => ({ feature, freshness: now })),
name
)
}
public static fromGeojsonStore(geojson: Store<Feature[]>, name = "StaticFeatureSourceFromGeojson"): StaticFeatureSource {
const now = new Date();
const mapped : Store<{feature: Feature, freshness: Date}[]> = geojson.map(features => features.map(feature => ({feature, freshness: now})))
return new StaticFeatureSource(mapped, name);
public static fromGeojsonStore(
geojson: Store<Feature[]>,
name = "StaticFeatureSourceFromGeojson"
): StaticFeatureSource {
const now = new Date()
const mapped: Store<{ feature: Feature; freshness: Date }[]> = geojson.map((features) =>
features.map((feature) => ({ feature, freshness: now }))
)
return new StaticFeatureSource(mapped, name)
}
static fromDateless(featureSource: Store<{ feature: Feature }[]>, name = "StaticFeatureSourceFromDateless") {
const now = new Date();
return new StaticFeatureSource(featureSource.map(features => features.map(feature => ({
feature: feature.feature,
freshness: now
}))), name);
static fromDateless(
featureSource: Store<{ feature: Feature }[]>,
name = "StaticFeatureSourceFromDateless"
) {
const now = new Date()
return new StaticFeatureSource(
featureSource.map((features) =>
features.map((feature) => ({
feature: feature.feature,
freshness: now,
}))
),
name
)
}
}
export class TiledStaticFeatureSource extends StaticFeatureSource implements Tiled, FeatureSourceForLayer{
export class TiledStaticFeatureSource
extends StaticFeatureSource
implements Tiled, FeatureSourceForLayer
{
public readonly bbox: BBox = BBox.global
public readonly tileIndex: number
public readonly layer: FilteredLayer
public readonly bbox: BBox = BBox.global;
public readonly tileIndex: number;
public readonly layer: FilteredLayer;
constructor(features: Store<{ feature: any, freshness: Date }[]>, layer: FilteredLayer ,tileIndex : number = 0) {
super(features);
this.tileIndex = tileIndex ;
this.layer= layer;
constructor(
features: Store<{ feature: any; freshness: Date }[]>,
layer: FilteredLayer,
tileIndex: number = 0
) {
super(features)
this.tileIndex = tileIndex
this.layer = layer
this.bbox = BBox.fromTileIndex(this.tileIndex)
}
}