Merge develop

This commit is contained in:
Pieter Vander Vennet 2021-07-27 22:23:42 +02:00
commit c8bd412476
49 changed files with 1342 additions and 977 deletions

View file

@ -0,0 +1,162 @@
import FeatureSource from "./FeatureSource";
import {UIEventSource} from "../UIEventSource";
import {Changes} from "../Osm/Changes";
import {ChangeDescription} from "../Osm/Actions/ChangeDescription";
import {Utils} from "../../Utils";
import {OsmNode, OsmRelation, OsmWay} from "../Osm/OsmObject";
/**
* Applies changes from 'Changes' onto a featureSource
*/
export default class ChangeApplicator implements FeatureSource {
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>;
public readonly name: string;
constructor(source: FeatureSource, changes: Changes, mode?: {
generateNewGeometries: boolean
}) {
this.name = "ChangesApplied(" + source.name + ")"
this.features = source.features
const seenChanges = new Set<ChangeDescription>();
const self = this;
let runningUpdate = false;
source.features.addCallbackAndRunD(features => {
if (runningUpdate) {
return; // No need to ping again
}
ChangeApplicator.ApplyChanges(features, changes.pendingChanges.data, mode)
seenChanges.clear()
})
changes.pendingChanges.addCallbackAndRunD(changes => {
runningUpdate = true;
changes = changes.filter(ch => !seenChanges.has(ch))
changes.forEach(c => seenChanges.add(c))
ChangeApplicator.ApplyChanges(self.features.data, changes, mode)
source.features.ping()
runningUpdate = false;
})
}
/**
* Returns true if the geometry is changed and the source should be pinged
*/
private static ApplyChanges(features: { feature: any; freshness: Date }[], cs: ChangeDescription[], mode: { generateNewGeometries: boolean }): boolean {
if (cs.length === 0 || features === undefined) {
return;
}
console.log("Applying changes ", this.name, cs)
let geometryChanged = false;
const changesPerId: Map<string, ChangeDescription[]> = new Map<string, ChangeDescription[]>()
for (const c of cs) {
const id = c.type + "/" + c.id
if (!changesPerId.has(id)) {
changesPerId.set(id, [])
}
changesPerId.get(id).push(c)
}
const now = new Date()
function add(feature) {
feature.id = feature.properties.id
features.push({
feature: feature,
freshness: now
})
console.log("Added a new feature: ", feature)
geometryChanged = true;
}
// First, create the new features - they have a negative ID
// We don't set the properties yet though
if (mode?.generateNewGeometries) {
changesPerId.forEach(cs => {
cs
.forEach(change => {
if (change.id >= 0) {
return; // Nothing to do here, already created
}
if (change.changes === undefined) {
// An update to the object - not the actual created
return;
}
try {
switch (change.type) {
case "node":
const n = new OsmNode(change.id)
n.lat = change.changes["lat"]
n.lon = change.changes["lon"]
const geojson = n.asGeoJson()
add(geojson)
break;
case "way":
const w = new OsmWay(change.id)
w.nodes = change.changes["nodes"]
add(w.asGeoJson())
break;
case "relation":
const r = new OsmRelation(change.id)
r.members = change.changes["members"]
add(r.asGeoJson())
break;
}
} catch (e) {
console.error(e)
}
})
})
}
for (const feature of features) {
const f = feature.feature;
const id = f.properties.id;
if (!changesPerId.has(id)) {
continue;
}
const changed = {}
// Copy all the properties
Utils.Merge(f, changed)
// play the changes onto the copied object
for (const change of changesPerId.get(id)) {
for (const kv of change.tags ?? []) {
// Apply tag changes and ping the consumers
f.properties[kv.k] = kv.v;
}
// Apply other changes to the object
if (change.changes !== undefined) {
geometryChanged = true;
switch (change.type) {
case "node":
// @ts-ignore
const coor: { lat, lon } = change.changes;
f.geometry.coordinates = [coor.lon, coor.lat]
break;
case "way":
f.geometry.coordinates = change.changes["locations"]
break;
case "relation":
console.error("Changes to relations are not yet supported")
break;
}
}
}
}
return geometryChanged
}
}

View file

@ -6,7 +6,6 @@ import FeatureDuplicatorPerLayer from "../FeatureSource/FeatureDuplicatorPerLaye
import FeatureSource from "../FeatureSource/FeatureSource";
import {UIEventSource} from "../UIEventSource";
import LocalStorageSaver from "./LocalStorageSaver";
import LayerConfig from "../../Customizations/JSON/LayerConfig";
import LocalStorageSource from "./LocalStorageSource";
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
import Loc from "../../Models/Loc";
@ -14,6 +13,8 @@ import GeoJsonSource from "./GeoJsonSource";
import MetaTaggingFeatureSource from "./MetaTaggingFeatureSource";
import RegisteringFeatureSource from "./RegisteringFeatureSource";
import FilteredLayer from "../../Models/FilteredLayer";
import {Changes} from "../Osm/Changes";
import ChangeApplicator from "./ChangeApplicator";
export default class FeaturePipeline implements FeatureSource {
@ -22,10 +23,10 @@ export default class FeaturePipeline implements FeatureSource {
public readonly name = "FeaturePipeline"
constructor(flayers: UIEventSource<FilteredLayer[]>,
changes: Changes,
updater: FeatureSource,
fromOsmApi: FeatureSource,
layout: UIEventSource<LayoutConfig>,
newPoints: FeatureSource,
locationControl: UIEventSource<Loc>,
selectedElement: UIEventSource<any>) {
@ -41,7 +42,9 @@ export default class FeaturePipeline implements FeatureSource {
new MetaTaggingFeatureSource(allLoadedFeatures,
new FeatureDuplicatorPerLayer(flayers,
new RegisteringFeatureSource(
updater)
new ChangeApplicator(
updater, changes
))
)), layout));
const geojsonSources: FeatureSource [] = GeoJsonSource
@ -49,8 +52,7 @@ export default class FeaturePipeline implements FeatureSource {
.map(geojsonSource => {
let source = new RegisteringFeatureSource(
new FeatureDuplicatorPerLayer(flayers,
geojsonSource
));
new ChangeApplicator(geojsonSource, changes)));
if (!geojsonSource.isOsmCache) {
source = new MetaTaggingFeatureSource(allLoadedFeatures, source, updater.features);
}
@ -58,25 +60,24 @@ export default class FeaturePipeline implements FeatureSource {
});
const amendedLocalStorageSource =
new RememberingSource(new RegisteringFeatureSource(new FeatureDuplicatorPerLayer(flayers, new LocalStorageSource(layout))
new RememberingSource(new RegisteringFeatureSource(new FeatureDuplicatorPerLayer(flayers, new ChangeApplicator(new LocalStorageSource(layout), changes))
));
newPoints = new MetaTaggingFeatureSource(allLoadedFeatures,
new FeatureDuplicatorPerLayer(flayers,
new RegisteringFeatureSource(newPoints)));
const amendedOsmApiSource = new RememberingSource(
new MetaTaggingFeatureSource(allLoadedFeatures,
new FeatureDuplicatorPerLayer(flayers,
new RegisteringFeatureSource(fromOsmApi))));
new RegisteringFeatureSource(new ChangeApplicator(fromOsmApi, changes,
{
// We lump in the new points here
generateNewGeometries: true
}
)))));
const merged =
new FeatureSourceMerger([
amendedOverpassSource,
amendedOsmApiSource,
amendedLocalStorageSource,
newPoints,
...geojsonSources
]);

View file

@ -1,6 +1,10 @@
import FeatureSource from "./FeatureSource";
import {UIEventSource} from "../UIEventSource";
/**
* Merges features from different featureSources
* Uses the freshest feature available in the case multiple sources offer data with the same identifier
*/
export default class FeatureSourceMerger implements FeatureSource {
public features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<{ feature: any; freshness: Date }[]>([]);

View file

@ -94,16 +94,9 @@ export default class FilteringFeatureSource implements FeatureSource {
return false;
}
return true;
});
console.log(
"Filtering layer source: input: ",
upstream.features.data?.length,
"output:",
newFeatures.length
);
self.features.setData(newFeatures);
if (missingLayers.size > 0) {
console.error(

View file

@ -15,15 +15,19 @@ export default class OsmApiFeatureSource implements FeatureSource {
public load(id: string) {
if(id.indexOf("-") >= 0){
if (id.indexOf("-") >= 0) {
// Newly added point - not yet in OSM
return;
}
console.debug("Downloading", id, "from the OSM-API")
OsmObject.DownloadObject(id).addCallbackAndRunD(element => {
const geojson = element.asGeoJson();
geojson.id = geojson.properties.id;
this.features.setData([{feature: geojson, freshness: element.timestamp}])
try {
const geojson = element.asGeoJson();
geojson.id = geojson.properties.id;
this.features.setData([{feature: geojson, freshness: element.timestamp}])
} catch (e) {
console.error(e)
}
})
}
@ -58,7 +62,7 @@ export default class OsmApiFeatureSource implements FeatureSource {
const bounds = Utils.tile_bounds(z, x, y);
console.log("Loading OSM data tile", z, x, y, " with bounds", bounds)
OsmObject.LoadArea(bounds, objects => {
const keptGeoJson: {feature:any, freshness: Date}[] = []
const keptGeoJson: { feature: any, freshness: Date }[] = []
// Which layer does the object match?
for (const object of objects) {
@ -69,7 +73,7 @@ export default class OsmApiFeatureSource implements FeatureSource {
if (doesMatch) {
const geoJson = object.asGeoJson();
geoJson._matching_layer_id = layer.id
keptGeoJson.push({feature: geoJson, freshness: object.timestamp})
keptGeoJson.push({feature: geoJson, freshness: object.timestamp})
break;
}