forked from MapComplete/MapComplete
OsmObjects can now be used as featureSource, load selected object immediately, zoom to selected object on open; fix #191
This commit is contained in:
parent
5ce4140510
commit
a0c1bc2137
13 changed files with 205 additions and 98 deletions
|
@ -185,6 +185,9 @@ export class InitUiElements {
|
||||||
// Reset the loading message once things are loaded
|
// Reset the loading message once things are loaded
|
||||||
new CenterMessageBox().AttachTo("centermessage");
|
new CenterMessageBox().AttachTo("centermessage");
|
||||||
|
|
||||||
|
// At last, zoom to the needed location if the focus is on an element
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static LoadLayoutFromHash(userLayoutParam: UIEventSource<string>) {
|
static LoadLayoutFromHash(userLayoutParam: UIEventSource<string>) {
|
||||||
|
@ -391,12 +394,18 @@ export class InitUiElements {
|
||||||
|
|
||||||
const updater = new LoadFromOverpass(state.locationControl, state.layoutToUse, state.leafletMap);
|
const updater = new LoadFromOverpass(state.locationControl, state.layoutToUse, state.leafletMap);
|
||||||
State.state.layerUpdater = updater;
|
State.state.layerUpdater = updater;
|
||||||
const source = new FeaturePipeline(state.filteredLayers, updater, state.layoutToUse, state.changes, state.locationControl);
|
const source = new FeaturePipeline(state.filteredLayers,
|
||||||
|
updater,
|
||||||
|
state.osmApiFeatureSource,
|
||||||
|
state.layoutToUse,
|
||||||
|
state.changes,
|
||||||
|
state.locationControl,
|
||||||
|
state.selectedElement);
|
||||||
|
|
||||||
new ShowDataLayer(source.features, State.state.leafletMap, State.state.layoutToUse);
|
new ShowDataLayer(source.features, State.state.leafletMap, State.state.layoutToUse);
|
||||||
|
|
||||||
new SelectedFeatureHandler(Hash.hash, State.state.selectedElement, source);
|
const selectedFeatureHandler = new SelectedFeatureHandler(Hash.hash, State.state.selectedElement, source, State.state.osmApiFeatureSource);
|
||||||
|
selectedFeatureHandler.zoomToSelectedFeature(State.state.locationControl);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
import {UIEventSource} from "../UIEventSource";
|
import {UIEventSource} from "../UIEventSource";
|
||||||
import FeatureSource from "../FeatureSource/FeatureSource";
|
import FeatureSource from "../FeatureSource/FeatureSource";
|
||||||
|
import {OsmObject, OsmObjectMeta} from "../Osm/OsmObject";
|
||||||
|
import Loc from "../../Models/Loc";
|
||||||
|
import FeaturePipeline from "../FeatureSource/FeaturePipeline";
|
||||||
|
import OsmApiFeatureSource from "../FeatureSource/OsmApiFeatureSource";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes sure the hash shows the selected element and vice-versa
|
* Makes sure the hash shows the selected element and vice-versa.
|
||||||
*/
|
*/
|
||||||
export default class SelectedFeatureHandler {
|
export default class SelectedFeatureHandler {
|
||||||
private readonly _featureSource: FeatureSource;
|
private readonly _featureSource: FeatureSource;
|
||||||
|
@ -10,13 +14,16 @@ export default class SelectedFeatureHandler {
|
||||||
private readonly _selectedFeature: UIEventSource<any>;
|
private readonly _selectedFeature: UIEventSource<any>;
|
||||||
|
|
||||||
private static readonly _no_trigger_on = ["welcome","copyright","layers"]
|
private static readonly _no_trigger_on = ["welcome","copyright","layers"]
|
||||||
|
private readonly _osmApiSource: OsmApiFeatureSource;
|
||||||
|
|
||||||
constructor(hash: UIEventSource<string>,
|
constructor(hash: UIEventSource<string>,
|
||||||
selectedFeature: UIEventSource<any>,
|
selectedFeature: UIEventSource<any>,
|
||||||
featureSource: FeatureSource) {
|
featureSource: FeaturePipeline,
|
||||||
|
osmApiSource: OsmApiFeatureSource) {
|
||||||
this._hash = hash;
|
this._hash = hash;
|
||||||
this._selectedFeature = selectedFeature;
|
this._selectedFeature = selectedFeature;
|
||||||
this._featureSource = featureSource;
|
this._featureSource = featureSource;
|
||||||
|
this._osmApiSource = osmApiSource;
|
||||||
const self = this;
|
const self = this;
|
||||||
hash.addCallback(h => {
|
hash.addCallback(h => {
|
||||||
if (h === undefined || h === "") {
|
if (h === undefined || h === "") {
|
||||||
|
@ -26,6 +33,8 @@ export default class SelectedFeatureHandler {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
hash.addCallbackAndRun(h => self.downloadFeature(h))
|
||||||
|
|
||||||
featureSource.features.addCallback(_ => self.selectFeature());
|
featureSource.features.addCallback(_ => self.selectFeature());
|
||||||
|
|
||||||
selectedFeature.addCallback(feature => {
|
selectedFeature.addCallback(feature => {
|
||||||
|
@ -45,6 +54,32 @@ export default class SelectedFeatureHandler {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If a feature is selected via the hash, zoom there
|
||||||
|
public zoomToSelectedFeature(location: UIEventSource<Loc>){
|
||||||
|
const hash = this._hash.data;
|
||||||
|
if(hash === undefined || SelectedFeatureHandler._no_trigger_on.indexOf(hash) >= 0){
|
||||||
|
return; // No valid feature selected
|
||||||
|
}
|
||||||
|
// We should have a valid osm-ID and zoom to it
|
||||||
|
OsmObject.DownloadObject(hash, (element: OsmObject, meta: OsmObjectMeta) => {
|
||||||
|
const centerpoint = element.centerpoint();
|
||||||
|
console.log("Zooming to location for select point: ", centerpoint)
|
||||||
|
location.data.lat = centerpoint[0]
|
||||||
|
location.data.lon = centerpoint[1]
|
||||||
|
location.ping();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private downloadFeature(hash: string){
|
||||||
|
if(hash === undefined || hash === ""){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(SelectedFeatureHandler._no_trigger_on.indexOf(hash) >= 0){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._osmApiSource.load(hash)
|
||||||
|
}
|
||||||
|
|
||||||
private selectFeature(){
|
private selectFeature(){
|
||||||
const features = this._featureSource?.features?.data;
|
const features = this._featureSource?.features?.data;
|
||||||
if(features === undefined){
|
if(features === undefined){
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
import {UIEventSource} from "../UIEventSource";
|
|
||||||
import {ElementStorage} from "../ElementStorage";
|
|
||||||
import {OsmObject, OsmObjectMeta} from "../Osm/OsmObject";
|
|
||||||
|
|
||||||
export default class UpdateTagsFromOsmAPI {
|
|
||||||
|
|
||||||
|
|
||||||
/***
|
|
||||||
* This actor downloads the element from the OSM-API and updates the corresponding tags in the UI-updater.
|
|
||||||
*/
|
|
||||||
constructor(idToDownload: UIEventSource<string>, allElements: ElementStorage, callback? : (element: OsmObject) => void) {
|
|
||||||
|
|
||||||
idToDownload.addCallbackAndRun(id => {
|
|
||||||
if (id === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
OsmObject.DownloadObject(id, (element: OsmObject, meta: OsmObjectMeta) => {
|
|
||||||
console.debug("Updating tags from the OSM-API: ", element)
|
|
||||||
|
|
||||||
|
|
||||||
const tags = element.tags;
|
|
||||||
tags["_last_edit:contributor"] = meta["_last_edit:contributor"]
|
|
||||||
tags["_last_edit:contributor:uid"] = meta["_last_edit:contributor:uid"]
|
|
||||||
tags["_last_edit:changeset"] = meta["_last_edit:changeset"]
|
|
||||||
tags["_last_edit:timestamp"] = meta["_last_edit:timestamp"].toLocaleString()
|
|
||||||
tags["_version_number"] = meta._version_number
|
|
||||||
if (!allElements.has(id)) {
|
|
||||||
console.warn("Adding element by id")
|
|
||||||
allElements.addElementById(id, new UIEventSource<any>(tags))
|
|
||||||
} else {
|
|
||||||
// We merge
|
|
||||||
console.warn("merging by OSM API UPDATE")
|
|
||||||
allElements.addOrGetById(id, tags)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -48,6 +48,7 @@ export class ElementStorage {
|
||||||
const keptKeys = es.data;
|
const keptKeys = es.data;
|
||||||
// The element already exists
|
// The element already exists
|
||||||
// We use the new feature to overwrite all the properties in the already existing eventsource
|
// We use the new feature to overwrite all the properties in the already existing eventsource
|
||||||
|
console.log("Merging multiple instances of ", elementId)
|
||||||
let somethingChanged = false;
|
let somethingChanged = false;
|
||||||
for (const k in newProperties) {
|
for (const k in newProperties) {
|
||||||
const v = newProperties[k];
|
const v = newProperties[k];
|
||||||
|
|
|
@ -18,27 +18,32 @@ export default class FeaturePipeline implements FeatureSource {
|
||||||
|
|
||||||
public features: UIEventSource<{ feature: any; freshness: Date }[]>;
|
public features: UIEventSource<{ feature: any; freshness: Date }[]>;
|
||||||
|
|
||||||
public readonly name = "FeaturePipeline"
|
public readonly name = "FeaturePipeline"
|
||||||
|
|
||||||
constructor(flayers: UIEventSource<{ isDisplayed: UIEventSource<boolean>, layerDef: LayerConfig }[]>,
|
constructor(flayers: UIEventSource<{ isDisplayed: UIEventSource<boolean>, layerDef: LayerConfig }[]>,
|
||||||
updater: FeatureSource,
|
updater: FeatureSource,
|
||||||
|
fromOsmApi: FeatureSource,
|
||||||
layout: UIEventSource<LayoutConfig>,
|
layout: UIEventSource<LayoutConfig>,
|
||||||
newPoints: FeatureSource,
|
newPoints: FeatureSource,
|
||||||
locationControl: UIEventSource<Loc>) {
|
locationControl: UIEventSource<Loc>,
|
||||||
|
selectedElement: UIEventSource<any>) {
|
||||||
|
|
||||||
|
// first we metatag, then we save to get the metatags into storage too
|
||||||
|
// Note that we need to register before we do metatagging (as it expects the event sources)
|
||||||
|
|
||||||
const amendedOverpassSource =
|
const amendedOverpassSource =
|
||||||
new RememberingSource(
|
new RememberingSource(
|
||||||
new LocalStorageSaver(
|
new LocalStorageSaver(
|
||||||
new MetaTaggingFeatureSource( // first we metatag, then we save to get the metatags into storage too
|
new FeatureDuplicatorPerLayer(flayers,
|
||||||
new RegisteringFeatureSource(
|
new MetaTaggingFeatureSource(
|
||||||
new FeatureDuplicatorPerLayer(flayers,
|
new RegisteringFeatureSource(
|
||||||
updater)
|
updater)
|
||||||
)), layout));
|
)), layout));
|
||||||
|
|
||||||
const geojsonSources: FeatureSource [] = GeoJsonSource
|
const geojsonSources: FeatureSource [] = GeoJsonSource
|
||||||
.ConstructMultiSource(flayers.data, locationControl)
|
.ConstructMultiSource(flayers.data, locationControl)
|
||||||
.map(geojsonSource => new RegisteringFeatureSource(new FeatureDuplicatorPerLayer(flayers, geojsonSource)));
|
.map(geojsonSource => new RegisteringFeatureSource(new FeatureDuplicatorPerLayer(flayers, geojsonSource)));
|
||||||
|
|
||||||
const amendedLocalStorageSource =
|
const amendedLocalStorageSource =
|
||||||
new RememberingSource(new RegisteringFeatureSource(new FeatureDuplicatorPerLayer(flayers, new LocalStorageSource(layout))
|
new RememberingSource(new RegisteringFeatureSource(new FeatureDuplicatorPerLayer(flayers, new LocalStorageSource(layout))
|
||||||
));
|
));
|
||||||
|
@ -46,10 +51,16 @@ export default class FeaturePipeline implements FeatureSource {
|
||||||
newPoints = new MetaTaggingFeatureSource(new FeatureDuplicatorPerLayer(flayers,
|
newPoints = new MetaTaggingFeatureSource(new FeatureDuplicatorPerLayer(flayers,
|
||||||
new RegisteringFeatureSource(newPoints)));
|
new RegisteringFeatureSource(newPoints)));
|
||||||
|
|
||||||
|
const amendedOsmApiSource = new RememberingSource(
|
||||||
|
new FeatureDuplicatorPerLayer(flayers,
|
||||||
|
new MetaTaggingFeatureSource(
|
||||||
|
new RegisteringFeatureSource(fromOsmApi))));
|
||||||
|
|
||||||
const merged =
|
const merged =
|
||||||
|
|
||||||
new FeatureSourceMerger([
|
new FeatureSourceMerger([
|
||||||
amendedOverpassSource,
|
amendedOverpassSource,
|
||||||
|
amendedOsmApiSource,
|
||||||
amendedLocalStorageSource,
|
amendedLocalStorageSource,
|
||||||
newPoints,
|
newPoints,
|
||||||
...geojsonSources
|
...geojsonSources
|
||||||
|
@ -60,6 +71,7 @@ export default class FeaturePipeline implements FeatureSource {
|
||||||
new FilteringFeatureSource(
|
new FilteringFeatureSource(
|
||||||
flayers,
|
flayers,
|
||||||
locationControl,
|
locationControl,
|
||||||
|
selectedElement,
|
||||||
merged
|
merged
|
||||||
));
|
));
|
||||||
this.features = source.features;
|
this.features = source.features;
|
||||||
|
|
|
@ -5,12 +5,14 @@ import Loc from "../../Models/Loc";
|
||||||
|
|
||||||
export default class FilteringFeatureSource implements FeatureSource {
|
export default class FilteringFeatureSource implements FeatureSource {
|
||||||
public features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<{ feature: any; freshness: Date }[]>([]);
|
public features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<{ feature: any; freshness: Date }[]>([]);
|
||||||
public readonly name = "FilteringFeatureSource"
|
public readonly name = "FilteringFeatureSource"
|
||||||
|
|
||||||
constructor(layers: UIEventSource<{
|
constructor(layers: UIEventSource<{
|
||||||
isDisplayed: UIEventSource<boolean>,
|
isDisplayed: UIEventSource<boolean>,
|
||||||
layerDef: LayerConfig
|
layerDef: LayerConfig
|
||||||
}[]>,
|
}[]>,
|
||||||
location: UIEventSource<Loc>,
|
location: UIEventSource<Loc>,
|
||||||
|
selectedElement: UIEventSource<any>,
|
||||||
upstream: FeatureSource) {
|
upstream: FeatureSource) {
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
|
@ -18,7 +20,7 @@ public readonly name = "FilteringFeatureSource"
|
||||||
function update() {
|
function update() {
|
||||||
|
|
||||||
const layerDict = {};
|
const layerDict = {};
|
||||||
if(layers.data.length == 0){
|
if (layers.data.length == 0) {
|
||||||
throw "No layers defined!"
|
throw "No layers defined!"
|
||||||
}
|
}
|
||||||
for (const layer of layers.data) {
|
for (const layer of layers.data) {
|
||||||
|
@ -32,6 +34,11 @@ public readonly name = "FilteringFeatureSource"
|
||||||
const newFeatures = features.filter(f => {
|
const newFeatures = features.filter(f => {
|
||||||
const layerId = f.feature._matching_layer_id;
|
const layerId = f.feature._matching_layer_id;
|
||||||
|
|
||||||
|
if(selectedElement.data === f.feature){
|
||||||
|
// This is the selected object - it gets a free pass even if zoom is not sufficient
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (layerId !== undefined) {
|
if (layerId !== undefined) {
|
||||||
const layer: {
|
const layer: {
|
||||||
isDisplayed: UIEventSource<boolean>,
|
isDisplayed: UIEventSource<boolean>,
|
||||||
|
@ -41,16 +48,16 @@ public readonly name = "FilteringFeatureSource"
|
||||||
missingLayers.add(layerId)
|
missingLayers.add(layerId)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isShown = layer.layerDef.isShown
|
const isShown = layer.layerDef.isShown
|
||||||
const tags = f.feature.properties;
|
const tags = f.feature.properties;
|
||||||
if(isShown.IsKnown(tags)){
|
if (isShown.IsKnown(tags)) {
|
||||||
const result = layer.layerDef.isShown.GetRenderValue(f.feature.properties).txt;
|
const result = layer.layerDef.isShown.GetRenderValue(f.feature.properties).txt;
|
||||||
if(result !== "yes"){
|
if (result !== "yes") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FilteringFeatureSource.showLayer(layer, location)) {
|
if (FilteringFeatureSource.showLayer(layer, location)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -69,7 +76,7 @@ public readonly name = "FilteringFeatureSource"
|
||||||
});
|
});
|
||||||
console.log("Filtering layer source: input: ", upstream.features.data?.length, "output:", newFeatures.length)
|
console.log("Filtering layer source: input: ", upstream.features.data?.length, "output:", newFeatures.length)
|
||||||
self.features.setData(newFeatures);
|
self.features.setData(newFeatures);
|
||||||
if(missingLayers.size > 0){
|
if (missingLayers.size > 0) {
|
||||||
console.error("Some layers were not found: ", Array.from(missingLayers))
|
console.error("Some layers were not found: ", Array.from(missingLayers))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,7 +93,7 @@ public readonly name = "FilteringFeatureSource"
|
||||||
if (l.zoom < layer.layerDef.minzoom) {
|
if (l.zoom < layer.layerDef.minzoom) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if(l.zoom > layer.layerDef.maxzoom){
|
if (l.zoom > layer.layerDef.maxzoom) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!layer.isDisplayed.data) {
|
if (!layer.isDisplayed.data) {
|
||||||
|
@ -98,13 +105,13 @@ public readonly name = "FilteringFeatureSource"
|
||||||
}).addCallback(() => {
|
}).addCallback(() => {
|
||||||
update();
|
update();
|
||||||
});
|
});
|
||||||
|
|
||||||
layers.addCallback(update);
|
layers.addCallback(update);
|
||||||
|
|
||||||
const registered = new Set<UIEventSource<boolean>>();
|
const registered = new Set<UIEventSource<boolean>>();
|
||||||
layers.addCallbackAndRun(layers => {
|
layers.addCallbackAndRun(layers => {
|
||||||
for (const layer of layers) {
|
for (const layer of layers) {
|
||||||
if(registered.has(layer.isDisplayed)){
|
if (registered.has(layer.isDisplayed)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
registered.add(layer.isDisplayed);
|
registered.add(layer.isDisplayed);
|
||||||
|
|
21
Logic/FeatureSource/OsmApiFeatureSource.ts
Normal file
21
Logic/FeatureSource/OsmApiFeatureSource.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import FeatureSource from "./FeatureSource";
|
||||||
|
import {UIEventSource} from "../UIEventSource";
|
||||||
|
import {OsmObject} from "../Osm/OsmObject";
|
||||||
|
|
||||||
|
|
||||||
|
export default class OsmApiFeatureSource implements FeatureSource {
|
||||||
|
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<{ feature: any; freshness: Date }[]>([]);
|
||||||
|
public readonly name: string = "OsmApiFeatureSource";
|
||||||
|
|
||||||
|
|
||||||
|
public load(id: string){
|
||||||
|
console.log("Updating from OSM API: ", id)
|
||||||
|
OsmObject.DownloadObject(id, (element, meta) => {
|
||||||
|
const geojson = element.asGeoJson();
|
||||||
|
console.warn(geojson)
|
||||||
|
geojson.id = geojson.properties.id;
|
||||||
|
this.features.setData([{feature:geojson, freshness: meta["_last_edit:timestamp"]}])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,9 +1,7 @@
|
||||||
import LayerConfig from "../Customizations/JSON/LayerConfig";
|
import LayerConfig from "../Customizations/JSON/LayerConfig";
|
||||||
import SimpleMetaTagger from "./SimpleMetaTagger";
|
import SimpleMetaTagger from "./SimpleMetaTagger";
|
||||||
import {ExtraFunction} from "./ExtraFunction";
|
import {ExtraFunction} from "./ExtraFunction";
|
||||||
import State from "../State";
|
|
||||||
import {Relation} from "./Osm/ExtractRelations";
|
import {Relation} from "./Osm/ExtractRelations";
|
||||||
import {meta} from "@turf/turf";
|
|
||||||
|
|
||||||
|
|
||||||
interface Params {
|
interface Params {
|
||||||
|
|
|
@ -13,6 +13,9 @@ export abstract class OsmObject {
|
||||||
protected constructor(type: string, id: number) {
|
protected constructor(type: string, id: number) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
|
this.tags = {
|
||||||
|
id: id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static DownloadObject(id, continuation: ((element: OsmObject, meta: OsmObjectMeta) => void)) {
|
static DownloadObject(id, continuation: ((element: OsmObject, meta: OsmObjectMeta) => void)) {
|
||||||
|
@ -20,7 +23,7 @@ export abstract class OsmObject {
|
||||||
const type = splitted[0];
|
const type = splitted[0];
|
||||||
const idN = splitted[1];
|
const idN = splitted[1];
|
||||||
|
|
||||||
const newContinuation = (element: OsmObject, meta :OsmObjectMeta) => {
|
const newContinuation = (element: OsmObject, meta: OsmObjectMeta) => {
|
||||||
console.log("Received: ", element, "with meta", meta);
|
console.log("Received: ", element, "with meta", meta);
|
||||||
continuation(element, meta);
|
continuation(element, meta);
|
||||||
}
|
}
|
||||||
|
@ -59,7 +62,12 @@ export abstract class OsmObject {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract SaveExtraData(element);
|
// The centerpoint of the feature, as [lat, lon]
|
||||||
|
public abstract centerpoint(): [number, number];
|
||||||
|
|
||||||
|
public abstract asGeoJson(): any;
|
||||||
|
|
||||||
|
abstract SaveExtraData(element: any, allElements: any[]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates the changeset-XML for tags
|
* Generates the changeset-XML for tags
|
||||||
|
@ -78,12 +86,21 @@ export abstract class OsmObject {
|
||||||
|
|
||||||
Download(continuation: ((element: OsmObject, meta: OsmObjectMeta) => void)) {
|
Download(continuation: ((element: OsmObject, meta: OsmObjectMeta) => void)) {
|
||||||
const self = this;
|
const self = this;
|
||||||
$.getJSON("https://www.openstreetmap.org/api/0.6/" + this.type + "/" + this.id,
|
const full = this.type !== "way" ? "" : "/full";
|
||||||
function (data) {
|
const url = "https://www.openstreetmap.org/api/0.6/" + this.type + "/" + this.id + full;
|
||||||
const element = data.elements[0];
|
$.getJSON(url, function (data) {
|
||||||
|
const element = data.elements[data.elements.length - 1];
|
||||||
self.tags = element.tags;
|
self.tags = element.tags;
|
||||||
|
const tgs = self.tags;
|
||||||
|
tgs["_last_edit:contributor"] = element.user
|
||||||
|
tgs["_last_edit:contributor:uid"] = element.uid
|
||||||
|
tgs["_last_edit:changeset"] = element.changeset
|
||||||
|
tgs["_last_edit:timestamp"] = element.timestamp
|
||||||
|
tgs["_version_number"] = element.version
|
||||||
|
tgs["id"] = self.type+"/"+self.id;
|
||||||
|
|
||||||
self.version = element.version;
|
self.version = element.version;
|
||||||
self.SaveExtraData(element);
|
self.SaveExtraData(element, data.elements);
|
||||||
continuation(self, {
|
continuation(self, {
|
||||||
"_last_edit:contributor": element.user,
|
"_last_edit:contributor": element.user,
|
||||||
"_last_edit:contributor:uid": element.uid,
|
"_last_edit:contributor:uid": element.uid,
|
||||||
|
@ -135,21 +152,36 @@ export class OsmNode extends OsmObject {
|
||||||
ChangesetXML(changesetId: string): string {
|
ChangesetXML(changesetId: string): string {
|
||||||
let tags = this.TagsXML();
|
let tags = this.TagsXML();
|
||||||
|
|
||||||
let change =
|
return ' <node id="' + this.id + '" changeset="' + changesetId + '" ' + this.VersionXML() + ' lat="' + this.lat + '" lon="' + this.lon + '">\n' +
|
||||||
' <node id="' + this.id + '" changeset="' + changesetId + '" ' + this.VersionXML() + ' lat="' + this.lat + '" lon="' + this.lon + '">\n' +
|
|
||||||
tags +
|
tags +
|
||||||
' </node>\n';
|
' </node>\n';
|
||||||
|
|
||||||
return change;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SaveExtraData(element) {
|
SaveExtraData(element) {
|
||||||
this.lat = element.lat;
|
this.lat = element.lat;
|
||||||
this.lon = element.lon;
|
this.lon = element.lon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
centerpoint(): [number, number] {
|
||||||
|
return [this.lat, this.lon];
|
||||||
|
}
|
||||||
|
|
||||||
|
asGeoJson() {
|
||||||
|
return {
|
||||||
|
"type": "Feature",
|
||||||
|
"properties": this.tags,
|
||||||
|
"geometry": {
|
||||||
|
"type": "Point",
|
||||||
|
"coordinates": [
|
||||||
|
this.lon,
|
||||||
|
this.lat
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OsmObjectMeta{
|
export interface OsmObjectMeta {
|
||||||
"_last_edit:contributor": string,
|
"_last_edit:contributor": string,
|
||||||
"_last_edit:contributor:uid": number,
|
"_last_edit:contributor:uid": number,
|
||||||
"_last_edit:changeset": number,
|
"_last_edit:changeset": number,
|
||||||
|
@ -161,12 +193,19 @@ export interface OsmObjectMeta{
|
||||||
export class OsmWay extends OsmObject {
|
export class OsmWay extends OsmObject {
|
||||||
|
|
||||||
nodes: number[];
|
nodes: number[];
|
||||||
|
coordinates: [number, number][] = []
|
||||||
|
lat: number;
|
||||||
|
lon: number;
|
||||||
|
|
||||||
constructor(id) {
|
constructor(id) {
|
||||||
super("way", id);
|
super("way", id);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
centerpoint(): [number, number] {
|
||||||
|
return [this.lat, this.lon];
|
||||||
|
}
|
||||||
|
|
||||||
ChangesetXML(changesetId: string): string {
|
ChangesetXML(changesetId: string): string {
|
||||||
let tags = this.TagsXML();
|
let tags = this.TagsXML();
|
||||||
let nds = "";
|
let nds = "";
|
||||||
|
@ -174,18 +213,40 @@ export class OsmWay extends OsmObject {
|
||||||
nds += ' <nd ref="' + this.nodes[node] + '"/>\n';
|
nds += ' <nd ref="' + this.nodes[node] + '"/>\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
let change =
|
return ' <way id="' + this.id + '" changeset="' + changesetId + '" ' + this.VersionXML() + '>\n' +
|
||||||
' <way id="' + this.id + '" changeset="' + changesetId + '" ' + this.VersionXML() + '>\n' +
|
|
||||||
nds +
|
nds +
|
||||||
tags +
|
tags +
|
||||||
' </way>\n';
|
' </way>\n';
|
||||||
|
|
||||||
return change;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SaveExtraData(element) {
|
SaveExtraData(element, allNodes) {
|
||||||
|
console.log("Way-extras: ", allNodes)
|
||||||
|
|
||||||
|
|
||||||
|
for (const node of allNodes) {
|
||||||
|
if (node.type === "node") {
|
||||||
|
const n = new OsmNode(node.id);
|
||||||
|
n.SaveExtraData(node)
|
||||||
|
const cp = n.centerpoint();
|
||||||
|
this.coordinates.push(cp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let count = this.coordinates.length;
|
||||||
|
this.lat = this.coordinates.map(c => c[0]).reduce((a, b) => a + b, 0) / count;
|
||||||
|
this.lon = this.coordinates.map(c => c[1]).reduce((a, b) => a + b, 0) / count;
|
||||||
this.nodes = element.nodes;
|
this.nodes = element.nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
asGeoJson() {
|
||||||
|
return {
|
||||||
|
"type": "Feature",
|
||||||
|
"properties": this.tags,
|
||||||
|
"geometry": {
|
||||||
|
"type": "LineString",
|
||||||
|
"coordinates": this.coordinates.map(c => [c[1], c[0]])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class OsmRelation extends OsmObject {
|
export class OsmRelation extends OsmObject {
|
||||||
|
@ -197,24 +258,29 @@ export class OsmRelation extends OsmObject {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
centerpoint(): [number, number] {
|
||||||
|
return [0, 0]; // TODO
|
||||||
|
}
|
||||||
|
|
||||||
ChangesetXML(changesetId: string): string {
|
ChangesetXML(changesetId: string): string {
|
||||||
let members = "";
|
let members = "";
|
||||||
for (const memberI in this.members) {
|
for (const member of this.members) {
|
||||||
const member = this.members[memberI];
|
|
||||||
members += ' <member type="' + member.type + '" ref="' + member.ref + '" role="' + member.role + '"/>\n';
|
members += ' <member type="' + member.type + '" ref="' + member.ref + '" role="' + member.role + '"/>\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
let tags = this.TagsXML();
|
let tags = this.TagsXML();
|
||||||
let change =
|
return ' <relation id="' + this.id + '" changeset="' + changesetId + '" ' + this.VersionXML() + '>\n' +
|
||||||
' <relation id="' + this.id + '" changeset="' + changesetId + '" ' + this.VersionXML() + '>\n' +
|
|
||||||
members +
|
members +
|
||||||
tags +
|
tags +
|
||||||
' </relation>\n';
|
' </relation>\n';
|
||||||
return change;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SaveExtraData(element) {
|
SaveExtraData(element) {
|
||||||
this.members = element.members;
|
this.members = element.members;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
asGeoJson() {
|
||||||
|
throw "Not Implemented"
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -7,7 +7,6 @@ import {Utils} from "../Utils";
|
||||||
import opening_hours from "opening_hours";
|
import opening_hours from "opening_hours";
|
||||||
import {UIElement} from "../UI/UIElement";
|
import {UIElement} from "../UI/UIElement";
|
||||||
import Combine from "../UI/Base/Combine";
|
import Combine from "../UI/Base/Combine";
|
||||||
import UpdateTagsFromOsmAPI from "./Actors/UpdateTagsFromOsmAPI";
|
|
||||||
|
|
||||||
|
|
||||||
const cardinalDirections = {
|
const cardinalDirections = {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Utils } from "../Utils";
|
||||||
|
|
||||||
export default class Constants {
|
export default class Constants {
|
||||||
|
|
||||||
public static vNumber = "0.7.0c";
|
public static vNumber = "0.7.1";
|
||||||
|
|
||||||
// The user journey states thresholds when a new feature gets unlocked
|
// The user journey states thresholds when a new feature gets unlocked
|
||||||
public static userJourney = {
|
public static userJourney = {
|
||||||
|
|
6
State.ts
6
State.ts
|
@ -18,7 +18,7 @@ import LayerConfig from "./Customizations/JSON/LayerConfig";
|
||||||
import TitleHandler from "./Logic/Actors/TitleHandler";
|
import TitleHandler from "./Logic/Actors/TitleHandler";
|
||||||
import PendingChangesUploader from "./Logic/Actors/PendingChangesUploader";
|
import PendingChangesUploader from "./Logic/Actors/PendingChangesUploader";
|
||||||
import {Relation} from "./Logic/Osm/ExtractRelations";
|
import {Relation} from "./Logic/Osm/ExtractRelations";
|
||||||
import UpdateTagsFromOsmAPI from "./Logic/Actors/UpdateTagsFromOsmAPI";
|
import OsmApiFeatureSource from "./Logic/FeatureSource/OsmApiFeatureSource";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains the global state: a bunch of UI-event sources
|
* Contains the global state: a bunch of UI-event sources
|
||||||
|
@ -58,6 +58,8 @@ export default class State {
|
||||||
public favouriteLayers: UIEventSource<string[]>;
|
public favouriteLayers: UIEventSource<string[]>;
|
||||||
|
|
||||||
public layerUpdater: UpdateFromOverpass;
|
public layerUpdater: UpdateFromOverpass;
|
||||||
|
|
||||||
|
public osmApiFeatureSource : OsmApiFeatureSource = new OsmApiFeatureSource();
|
||||||
|
|
||||||
|
|
||||||
public filteredLayers: UIEventSource<{
|
public filteredLayers: UIEventSource<{
|
||||||
|
@ -253,8 +255,6 @@ export default class State {
|
||||||
|
|
||||||
new TitleHandler(this.layoutToUse, this.selectedElement, this.allElements);
|
new TitleHandler(this.layoutToUse, this.selectedElement, this.allElements);
|
||||||
|
|
||||||
new UpdateTagsFromOsmAPI(this.selectedElement.map(el => el?.properties?.id), this.allElements)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static asFloat(source: UIEventSource<string>): UIEventSource<number> {
|
private static asFloat(source: UIEventSource<string>): UIEventSource<number> {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import {VerticalCombine} from "../Base/VerticalCombine";
|
|
||||||
import {UIElement} from "../UIElement";
|
import {UIElement} from "../UIElement";
|
||||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||||
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
|
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
|
||||||
|
@ -53,15 +52,17 @@ export default class MoreScreen extends UIElement {
|
||||||
if(path === ""){
|
if(path === ""){
|
||||||
path = "."
|
path = "."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const params = `z=${currentLocation.zoom ?? 1}&lat=${currentLocation.lat ?? 0}&lon=${currentLocation.lon ?? 0}`
|
||||||
let linkText =
|
let linkText =
|
||||||
`${path}/${layout.id.toLowerCase()}?z=${currentLocation.zoom}&lat=${currentLocation.lat}&lon=${currentLocation.lon}`
|
`${path}/${layout.id.toLowerCase()}?${params}`
|
||||||
|
|
||||||
if (location.hostname === "localhost" || location.hostname === "127.0.0.1") {
|
if (location.hostname === "localhost" || location.hostname === "127.0.0.1") {
|
||||||
linkText = `${path}/index.html?layout=${layout.id}&z=${currentLocation.zoom}&lat=${currentLocation.lat}&lon=${currentLocation.lon}`
|
linkText = `${path}/index.html?layout=${layout.id}&${params}`
|
||||||
}
|
}
|
||||||
|
|
||||||
if (customThemeDefinition) {
|
if (customThemeDefinition) {
|
||||||
linkText = `${path}/?userlayout=${layout.id}&z=${currentLocation.zoom}&lat=${currentLocation.lat}&lon=${currentLocation.lon}#${customThemeDefinition}`
|
linkText = `${path}/?userlayout=${layout.id}&${params}#${customThemeDefinition}`
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue