forked from MapComplete/MapComplete
Add binoculars theme, auto reformat everything
This commit is contained in:
parent
38dea806c5
commit
78d6482c88
586 changed files with 115573 additions and 111842 deletions
|
@ -4,7 +4,6 @@ import Svg from "../../Svg";
|
|||
import Img from "../../UI/Base/Img";
|
||||
import {LocalStorageSource} from "../Web/LocalStorageSource";
|
||||
import {VariableUiElement} from "../../UI/Base/VariableUIElement";
|
||||
import BaseUIElement from "../../UI/BaseUIElement";
|
||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
||||
import {QueryParameters} from "../Web/QueryParameters";
|
||||
|
||||
|
@ -161,16 +160,16 @@ export default class GeoLocationHandler extends VariableUiElement {
|
|||
} else {
|
||||
lastClick.setData(new Date())
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
lastClick.setData(new Date())
|
||||
}
|
||||
}
|
||||
|
||||
self.init(true, true);
|
||||
});
|
||||
|
||||
|
||||
const latLonGiven = QueryParameters.wasInitialized("lat") && QueryParameters.wasInitialized("lon")
|
||||
|
||||
|
||||
this.init(false, !latLonGiven);
|
||||
|
||||
isLocked.addCallbackAndRunD(isLocked => {
|
||||
|
@ -180,7 +179,7 @@ export default class GeoLocationHandler extends VariableUiElement {
|
|||
leafletMap.data?.dragging?.enable()
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
this._currentGPSLocation.addCallback((location) => {
|
||||
self._previousLocationGrant.setData("granted");
|
||||
|
|
|
@ -6,7 +6,7 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
|||
|
||||
export default class InstalledThemes {
|
||||
public installedThemes: UIEventSource<{ layout: LayoutConfig; definition: string }[]>;
|
||||
|
||||
|
||||
constructor(osmConnection: OsmConnection) {
|
||||
this.installedThemes = osmConnection.preferencesHandler.preferences.map<{ layout: LayoutConfig, definition: string }[]>(allPreferences => {
|
||||
const installedThemes: { layout: LayoutConfig, definition: string }[] = [];
|
||||
|
@ -25,10 +25,10 @@ export default class InstalledThemes {
|
|||
}
|
||||
try {
|
||||
let layoutJson;
|
||||
try{
|
||||
try {
|
||||
layoutJson = JSON.parse(atob(customLayout.data))
|
||||
}catch(e){
|
||||
layoutJson = JSON.parse( Utils.UnMinify(LZString.decompressFromBase64(customLayout.data)))
|
||||
} catch (e) {
|
||||
layoutJson = JSON.parse(Utils.UnMinify(LZString.decompressFromBase64(customLayout.data)))
|
||||
}
|
||||
const layout = new LayoutConfig(layoutJson, false);
|
||||
installedThemes.push({
|
||||
|
@ -43,7 +43,7 @@ export default class InstalledThemes {
|
|||
}
|
||||
|
||||
InstalledThemes.DeleteInvalid(osmConnection, invalidThemes);
|
||||
|
||||
|
||||
return installedThemes;
|
||||
|
||||
});
|
||||
|
|
|
@ -7,13 +7,13 @@ import Loc from "../../Models/Loc";
|
|||
* Sets the current background layer to a layer that is actually available
|
||||
*/
|
||||
export default class LayerResetter {
|
||||
|
||||
constructor( currentBackgroundLayer: UIEventSource<BaseLayer>,
|
||||
location: UIEventSource<Loc>,
|
||||
availableLayers: UIEventSource<BaseLayer[]>,
|
||||
defaultLayerId: UIEventSource<string> = undefined) {
|
||||
defaultLayerId = defaultLayerId ?? new UIEventSource<string>(AvailableBaseLayers.osmCarto.id);
|
||||
|
||||
|
||||
constructor(currentBackgroundLayer: UIEventSource<BaseLayer>,
|
||||
location: UIEventSource<Loc>,
|
||||
availableLayers: UIEventSource<BaseLayer[]>,
|
||||
defaultLayerId: UIEventSource<string> = undefined) {
|
||||
defaultLayerId = defaultLayerId ?? new UIEventSource<string>(AvailableBaseLayers.osmCarto.id);
|
||||
|
||||
// Change the baselayer back to OSM if we go out of the current range of the layer
|
||||
availableLayers.addCallbackAndRun(availableLayers => {
|
||||
let defaultLayer = undefined;
|
||||
|
@ -28,7 +28,7 @@ export default class LayerResetter {
|
|||
if (availableLayer.min_zoom > location.data.zoom) {
|
||||
break;
|
||||
}
|
||||
if(availableLayer.id === defaultLayerId.data){
|
||||
if (availableLayer.id === defaultLayerId.data) {
|
||||
defaultLayer = availableLayer;
|
||||
}
|
||||
return; // All good - the current layer still works!
|
||||
|
@ -38,7 +38,7 @@ export default class LayerResetter {
|
|||
console.log("AvailableBaseLayers-actor: detected that the current bounds aren't sufficient anymore - reverting to OSM standard")
|
||||
currentBackgroundLayer.setData(defaultLayer ?? AvailableBaseLayers.osmCarto);
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -13,7 +13,7 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
|||
export default class OverpassFeatureSource implements FeatureSource {
|
||||
|
||||
public readonly name = "OverpassFeatureSource"
|
||||
|
||||
|
||||
/**
|
||||
* The last loaded features of the geojson
|
||||
*/
|
||||
|
@ -147,7 +147,7 @@ export default class OverpassFeatureSource implements FeatureSource {
|
|||
}
|
||||
|
||||
const bounds = this._leafletMap.data?.getBounds();
|
||||
if(bounds === undefined){
|
||||
if (bounds === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ export default class PendingChangesUploader {
|
|||
|
||||
|
||||
function onunload(e) {
|
||||
if(changes.pendingChanges.data.length == 0){
|
||||
if (changes.pendingChanges.data.length == 0) {
|
||||
return;
|
||||
}
|
||||
changes.flushChanges("onbeforeunload - probably closing or something similar");
|
||||
|
|
|
@ -3,6 +3,7 @@ Actors
|
|||
|
||||
An **actor** is a module which converts one UIEventSource into another while performing logic.
|
||||
|
||||
Typically, it will only expose the constructor taking some UIEventSources (and configuration) and a few fields which are UIEVentSources.
|
||||
Typically, it will only expose the constructor taking some UIEventSources (and configuration) and a few fields which are
|
||||
UIEVentSources.
|
||||
|
||||
An actor should _never_ have a dependency on 'State' and should _never_ import it
|
|
@ -9,11 +9,10 @@ import OsmApiFeatureSource from "../FeatureSource/OsmApiFeatureSource";
|
|||
* Makes sure the hash shows the selected element and vice-versa.
|
||||
*/
|
||||
export default class SelectedFeatureHandler {
|
||||
private static readonly _no_trigger_on = ["welcome", "copyright", "layers", "new"]
|
||||
private readonly _featureSource: FeatureSource;
|
||||
private readonly _hash: UIEventSource<string>;
|
||||
private readonly _selectedFeature: UIEventSource<any>;
|
||||
|
||||
private static readonly _no_trigger_on = ["welcome", "copyright", "layers", "new"]
|
||||
private readonly _osmApiSource: OsmApiFeatureSource;
|
||||
|
||||
constructor(hash: UIEventSource<string>,
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import * as L from "leaflet";
|
||||
import Svg from "../../Svg";
|
||||
import {UIEventSource} from "../UIEventSource";
|
||||
import Img from "../../UI/Base/Img";
|
||||
import ScrollableFullScreen from "../../UI/Base/ScrollableFullScreen";
|
||||
import AddNewMarker from "../../UI/BigComponents/AddNewMarker";
|
||||
import FilteredLayer from "../../Models/FilteredLayer";
|
||||
|
@ -32,28 +30,28 @@ export default class StrayClickHandler {
|
|||
})
|
||||
|
||||
lastClickLocation.addCallback(function (lastClick) {
|
||||
|
||||
|
||||
if (self._lastMarker !== undefined) {
|
||||
leafletMap.data?.removeLayer(self._lastMarker);
|
||||
}
|
||||
|
||||
if(lastClick === undefined){
|
||||
|
||||
if (lastClick === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
selectedElement.setData(undefined);
|
||||
const clickCoor : [number, number] = [lastClick.lat, lastClick.lon]
|
||||
const clickCoor: [number, number] = [lastClick.lat, lastClick.lon]
|
||||
self._lastMarker = L.marker(clickCoor, {
|
||||
icon: L.divIcon({
|
||||
html: new AddNewMarker(filteredLayers).ConstructElement(),
|
||||
iconSize: [50, 50],
|
||||
iconAnchor: [25, 50],
|
||||
popupAnchor: [0, -45]
|
||||
})
|
||||
})
|
||||
});
|
||||
const popup = L.popup({
|
||||
autoPan: true,
|
||||
autoPanPaddingTopLeft: [15,15],
|
||||
autoPanPaddingTopLeft: [15, 15],
|
||||
closeOnEscapeKey: true,
|
||||
autoClose: true
|
||||
}).setContent("<div id='strayclick' style='height: 65vh'></div>");
|
||||
|
@ -61,13 +59,13 @@ export default class StrayClickHandler {
|
|||
self._lastMarker.bindPopup(popup);
|
||||
|
||||
self._lastMarker.on("click", () => {
|
||||
if(leafletMap.data.getZoom() < Constants.userJourney.minZoomLevelToAddNewPoints){
|
||||
if (leafletMap.data.getZoom() < Constants.userJourney.minZoomLevelToAddNewPoints) {
|
||||
self._lastMarker.closePopup()
|
||||
leafletMap.data.flyTo(clickCoor, Constants.userJourney.minZoomLevelToAddNewPoints)
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
uiToShow.AttachTo("strayclick")
|
||||
uiToShow.Activate();
|
||||
});
|
||||
|
|
|
@ -7,7 +7,7 @@ import Combine from "../../UI/Base/Combine";
|
|||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
||||
|
||||
class TitleElement extends UIEventSource<string> {
|
||||
|
||||
|
||||
private readonly _layoutToUse: UIEventSource<LayoutConfig>;
|
||||
private readonly _selectedFeature: UIEventSource<any>;
|
||||
private readonly _allElementsStorage: ElementStorage;
|
||||
|
@ -16,17 +16,17 @@ class TitleElement extends UIEventSource<string> {
|
|||
selectedFeature: UIEventSource<any>,
|
||||
allElementsStorage: ElementStorage) {
|
||||
super("MapComplete");
|
||||
|
||||
|
||||
this._layoutToUse = layoutToUse;
|
||||
this._selectedFeature = selectedFeature;
|
||||
this._allElementsStorage = allElementsStorage;
|
||||
|
||||
|
||||
this.syncWith(
|
||||
this._selectedFeature.map(
|
||||
selected => {
|
||||
const defaultTitle = Translations.WT(this._layoutToUse.data?.title)?.txt ??"MapComplete"
|
||||
const defaultTitle = Translations.WT(this._layoutToUse.data?.title)?.txt ?? "MapComplete"
|
||||
|
||||
if(selected === undefined){
|
||||
if (selected === undefined) {
|
||||
return defaultTitle
|
||||
}
|
||||
|
||||
|
@ -49,10 +49,9 @@ class TitleElement extends UIEventSource<string> {
|
|||
}
|
||||
, [Locale.language, layoutToUse]
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@ import {UIEventSource} from "./UIEventSource";
|
|||
|
||||
export class ElementStorage {
|
||||
|
||||
private _elements = new Map<string, UIEventSource<any>>();
|
||||
public ContainingFeatures = new Map<string, any>();
|
||||
private _elements = new Map<string, UIEventSource<any>>();
|
||||
|
||||
constructor() {
|
||||
|
||||
|
@ -25,16 +25,16 @@ export class ElementStorage {
|
|||
addOrGetElement(feature: any): UIEventSource<any> {
|
||||
const elementId = feature.properties.id;
|
||||
const newProperties = feature.properties;
|
||||
|
||||
|
||||
const es = this.addOrGetById(elementId, newProperties)
|
||||
|
||||
// At last, we overwrite the tag of the new feature to use the tags in the already existing event source
|
||||
feature.properties = es.data
|
||||
|
||||
if(!this.ContainingFeatures.has(elementId)){
|
||||
|
||||
if (!this.ContainingFeatures.has(elementId)) {
|
||||
this.ContainingFeatures.set(elementId, feature);
|
||||
}
|
||||
|
||||
|
||||
return es;
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,7 @@ export class ElementStorage {
|
|||
const debug_msg = []
|
||||
let somethingChanged = false;
|
||||
for (const k in newProperties) {
|
||||
if(!newProperties.hasOwnProperty(k)){
|
||||
if (!newProperties.hasOwnProperty(k)) {
|
||||
continue;
|
||||
}
|
||||
const v = newProperties[k];
|
||||
|
|
|
@ -14,13 +14,13 @@ export default class FeatureDuplicatorPerLayer implements FeatureSource {
|
|||
public readonly name;
|
||||
|
||||
constructor(layers: UIEventSource<FilteredLayer[]>, upstream: FeatureSource) {
|
||||
this.name = "FeatureDuplicator of "+upstream.name;
|
||||
this.name = "FeatureDuplicator of " + upstream.name;
|
||||
this.features = upstream.features.map(features => {
|
||||
const newFeatures: { feature: any, freshness: Date }[] = [];
|
||||
if(features === undefined){
|
||||
if (features === undefined) {
|
||||
return newFeatures;
|
||||
}
|
||||
|
||||
|
||||
for (const f of features) {
|
||||
if (f.feature._matching_layer_id) {
|
||||
// Already matched previously
|
||||
|
@ -29,7 +29,7 @@ export default class FeatureDuplicatorPerLayer implements FeatureSource {
|
|||
continue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
let foundALayer = false;
|
||||
for (const layer of layers.data) {
|
||||
if (layer.layerDef.source.osmTags.matchesProperties(f.feature.properties)) {
|
||||
|
@ -43,7 +43,7 @@ export default class FeatureDuplicatorPerLayer implements FeatureSource {
|
|||
id: f.feature.id,
|
||||
type: f.feature.type,
|
||||
properties: f.feature.properties,
|
||||
_matching_layer_id : layer.layerDef.id
|
||||
_matching_layer_id: layer.layerDef.id
|
||||
}
|
||||
newFeatures.push({feature: newFeature, freshness: f.freshness});
|
||||
} else {
|
||||
|
|
|
@ -24,8 +24,8 @@ export class FeatureSourceUtils {
|
|||
options = Utils.setDefaults(options, defaults);
|
||||
|
||||
// Select all features, ignore the freshness and other data
|
||||
let featureList: any[] = featurePipeline.features.data.map((feature) =>
|
||||
JSON.parse(JSON.stringify((feature.feature)))); // Make a deep copy!
|
||||
let featureList: any[] = featurePipeline.features.data.map((feature) =>
|
||||
JSON.parse(JSON.stringify((feature.feature)))); // Make a deep copy!
|
||||
|
||||
if (!options.metadata) {
|
||||
for (let i = 0; i < featureList.length; i++) {
|
||||
|
@ -39,7 +39,7 @@ export class FeatureSourceUtils {
|
|||
}
|
||||
return {type: "FeatureCollection", features: featureList}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -57,12 +57,12 @@ export default class FeatureSourceMerger implements FeatureSource {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!somethingChanged){
|
||||
|
||||
if (!somethingChanged) {
|
||||
// We don't bother triggering an update
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const newList = [];
|
||||
all.forEach((value, key) => {
|
||||
newList.push(value)
|
||||
|
|
|
@ -79,8 +79,8 @@ export default class FilteringFeatureSource implements FeatureSource {
|
|||
if (result !== "yes") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const tagsFilter = layer.appliedFilters.data;
|
||||
if (tagsFilter) {
|
||||
if (!tagsFilter.matchesProperties(f.feature.properties)) {
|
||||
|
@ -118,7 +118,7 @@ export default class FilteringFeatureSource implements FeatureSource {
|
|||
if (l.zoom < layer.layerDef.minzoom) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (!layer.isDisplayed.data) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -13,10 +13,10 @@ export default class GeoJsonSource implements FeatureSource {
|
|||
|
||||
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>;
|
||||
public readonly name;
|
||||
public readonly isOsmCache: boolean
|
||||
private onFail: ((errorMsg: any, url: string) => void) = undefined;
|
||||
private readonly layerId: string;
|
||||
private readonly seenids: Set<string> = new Set<string>()
|
||||
public readonly isOsmCache: boolean
|
||||
|
||||
private constructor(locationControl: UIEventSource<Loc>,
|
||||
flayer: { isDisplayed: UIEventSource<boolean>, layerDef: LayerConfig },
|
||||
|
|
|
@ -12,23 +12,23 @@ export default class LocalStorageSaver implements FeatureSource {
|
|||
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>;
|
||||
|
||||
public readonly name = "LocalStorageSaver";
|
||||
|
||||
|
||||
constructor(source: FeatureSource, layout: UIEventSource<LayoutConfig>) {
|
||||
this.features = source.features;
|
||||
|
||||
this.features.addCallbackAndRunD(features => {
|
||||
const now = new Date().getTime()
|
||||
features = features.filter(f => layout.data.cacheTimeout > Math.abs(now - f.freshness.getTime())/1000)
|
||||
|
||||
|
||||
if(features.length == 0){
|
||||
features = features.filter(f => layout.data.cacheTimeout > Math.abs(now - f.freshness.getTime()) / 1000)
|
||||
|
||||
|
||||
if (features.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const key = LocalStorageSaver.storageKey+layout.data.id
|
||||
const key = LocalStorageSaver.storageKey + layout.data.id
|
||||
localStorage.setItem(key, JSON.stringify(features));
|
||||
console.log("Saved ",features.length, "elements to",key)
|
||||
console.log("Saved ", features.length, "elements to", key)
|
||||
} catch (e) {
|
||||
console.warn("Could not save the features to local storage:", e)
|
||||
}
|
||||
|
|
|
@ -16,14 +16,14 @@ export default class LocalStorageSource implements FeatureSource {
|
|||
if (fromStorage == null) {
|
||||
return;
|
||||
}
|
||||
const loaded : { feature: any; freshness: Date | string }[]=
|
||||
const loaded: { feature: any; freshness: Date | string }[] =
|
||||
JSON.parse(fromStorage);
|
||||
|
||||
const parsed : { feature: any; freshness: Date }[]= loaded.map(ff => ({
|
||||
|
||||
const parsed: { feature: any; freshness: Date }[] = loaded.map(ff => ({
|
||||
feature: ff.feature,
|
||||
freshness : typeof ff.freshness == "string" ? new Date(ff.freshness) : ff.freshness
|
||||
freshness: typeof ff.freshness == "string" ? new Date(ff.freshness) : ff.freshness
|
||||
}))
|
||||
|
||||
|
||||
this.features.setData(parsed);
|
||||
console.log("Loaded ", loaded.length, " features from localstorage as cache")
|
||||
} catch (e) {
|
||||
|
|
|
@ -19,10 +19,10 @@ export default class MetaTaggingFeatureSource implements FeatureSource {
|
|||
const self = this;
|
||||
this.name = "MetaTagging of " + source.name
|
||||
|
||||
if(allFeaturesSource === undefined){
|
||||
if (allFeaturesSource === undefined) {
|
||||
throw ("UIEVentSource is undefined")
|
||||
}
|
||||
|
||||
|
||||
function update() {
|
||||
const featuresFreshness = source.features.data
|
||||
if (featuresFreshness === undefined) {
|
||||
|
|
|
@ -8,17 +8,17 @@ export default class RememberingSource implements FeatureSource {
|
|||
public readonly features: UIEventSource<{ feature: any, freshness: Date }[]>;
|
||||
|
||||
public readonly name;
|
||||
|
||||
|
||||
constructor(source: FeatureSource) {
|
||||
const self = this;
|
||||
this.name = "RememberingSource of "+source.name;
|
||||
this.name = "RememberingSource of " + source.name;
|
||||
const empty = [];
|
||||
this.features = source.features.map(features => {
|
||||
const oldFeatures = self.features?.data ?? empty;
|
||||
if (features === undefined) {
|
||||
return oldFeatures;
|
||||
}
|
||||
|
||||
|
||||
// Then new ids
|
||||
const ids = new Set<string>(features.map(f => f.feature.properties.id + f.feature.geometry.type + f.feature._matching_layer_id));
|
||||
// the old data
|
||||
|
|
|
@ -185,6 +185,60 @@ export class GeoOperations {
|
|||
return turf.length(feature) * 1000
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the closest point on a way from a given point
|
||||
* @param way The road on which you want to find a point
|
||||
* @param point Point defined as [lon, lat]
|
||||
*/
|
||||
public static nearestPoint(way, point: [number, number]) {
|
||||
return turf.nearestPointOnLine(way, point, {units: "kilometers"});
|
||||
}
|
||||
|
||||
public static toCSV(features: any[]): string {
|
||||
|
||||
const headerValuesSeen = new Set<string>();
|
||||
const headerValuesOrdered: string[] = []
|
||||
|
||||
function addH(key) {
|
||||
if (!headerValuesSeen.has(key)) {
|
||||
headerValuesSeen.add(key)
|
||||
headerValuesOrdered.push(key)
|
||||
}
|
||||
}
|
||||
|
||||
addH("_lat")
|
||||
addH("_lon")
|
||||
|
||||
const lines: string[] = []
|
||||
|
||||
for (const feature of features) {
|
||||
const properties = feature.properties;
|
||||
for (const key in properties) {
|
||||
if (!properties.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
addH(key)
|
||||
|
||||
}
|
||||
}
|
||||
headerValuesOrdered.sort()
|
||||
for (const feature of features) {
|
||||
const properties = feature.properties;
|
||||
let line = ""
|
||||
for (const key of headerValuesOrdered) {
|
||||
const value = properties[key]
|
||||
if (value === undefined) {
|
||||
line += ","
|
||||
} else {
|
||||
line += JSON.stringify(value) + ","
|
||||
}
|
||||
}
|
||||
lines.push(line)
|
||||
}
|
||||
|
||||
return headerValuesOrdered.map(v => JSON.stringify(v)).join(",") + "\n" + lines.join("\n")
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the intersection between two features.
|
||||
* Returns the length if intersecting a linestring and a (multi)polygon (in meters), returns a surface area (in m²) if intersecting two (multi)polygons
|
||||
|
@ -277,60 +331,6 @@ export class GeoOperations {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the closest point on a way from a given point
|
||||
* @param way The road on which you want to find a point
|
||||
* @param point Point defined as [lon, lat]
|
||||
*/
|
||||
public static nearestPoint(way, point: [number, number]) {
|
||||
return turf.nearestPointOnLine(way, point, {units: "kilometers"});
|
||||
}
|
||||
|
||||
public static toCSV(features: any[]): string {
|
||||
|
||||
const headerValuesSeen = new Set<string>();
|
||||
const headerValuesOrdered: string[] = []
|
||||
|
||||
function addH(key) {
|
||||
if (!headerValuesSeen.has(key)) {
|
||||
headerValuesSeen.add(key)
|
||||
headerValuesOrdered.push(key)
|
||||
}
|
||||
}
|
||||
|
||||
addH("_lat")
|
||||
addH("_lon")
|
||||
|
||||
const lines: string[] = []
|
||||
|
||||
for (const feature of features) {
|
||||
const properties = feature.properties;
|
||||
for (const key in properties) {
|
||||
if (!properties.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
addH(key)
|
||||
|
||||
}
|
||||
}
|
||||
headerValuesOrdered.sort()
|
||||
for (const feature of features) {
|
||||
const properties = feature.properties;
|
||||
let line = ""
|
||||
for (const key of headerValuesOrdered) {
|
||||
const value = properties[key]
|
||||
if (value === undefined) {
|
||||
line += ","
|
||||
} else {
|
||||
line += JSON.stringify(value) + ","
|
||||
}
|
||||
}
|
||||
lines.push(line)
|
||||
}
|
||||
|
||||
return headerValuesOrdered.map(v => JSON.stringify(v)).join(",") + "\n" + lines.join("\n")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -364,7 +364,7 @@ export class BBox {
|
|||
static get(feature) {
|
||||
if (feature.bbox?.overlapsWith === undefined) {
|
||||
const turfBbox: number[] = turf.bbox(feature)
|
||||
feature.bbox = new BBox([[turfBbox[0], turfBbox[1]],[turfBbox[2], turfBbox[3]]]);
|
||||
feature.bbox = new BBox([[turfBbox[0], turfBbox[1]], [turfBbox[2], turfBbox[3]]]);
|
||||
}
|
||||
|
||||
return feature.bbox;
|
||||
|
|
|
@ -2,8 +2,8 @@ import {Mapillary} from "./Mapillary";
|
|||
import {Wikimedia} from "./Wikimedia";
|
||||
import {Imgur} from "./Imgur";
|
||||
|
||||
export default class AllImageProviders{
|
||||
|
||||
export default class AllImageProviders {
|
||||
|
||||
public static ImageAttributionSource = [Imgur.singleton, Mapillary.singleton, Wikimedia.singleton]
|
||||
|
||||
|
||||
}
|
|
@ -18,12 +18,13 @@ export default abstract class ImageAttributionSource {
|
|||
}
|
||||
|
||||
|
||||
|
||||
public abstract SourceIcon(backlinkSource?: string) : BaseUIElement;
|
||||
protected abstract DownloadAttribution(url: string): UIEventSource<LicenseInfo>;
|
||||
public abstract SourceIcon(backlinkSource?: string): BaseUIElement;
|
||||
|
||||
/*Converts a value to a URL. Can return null if not applicable*/
|
||||
public PrepareUrl(value: string): string{
|
||||
public PrepareUrl(value: string): string {
|
||||
return value;
|
||||
}
|
||||
|
||||
protected abstract DownloadAttribution(url: string): UIEventSource<LicenseInfo>;
|
||||
|
||||
}
|
|
@ -6,8 +6,8 @@ import {UIEventSource} from "../UIEventSource";
|
|||
import BaseUIElement from "../../UI/BaseUIElement";
|
||||
|
||||
export class Imgur extends ImageAttributionSource {
|
||||
|
||||
public static readonly singleton = new Imgur();
|
||||
|
||||
public static readonly singleton = new Imgur();
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import $ from "jquery"
|
||||
import {LicenseInfo} from "./Wikimedia";
|
||||
import ImageAttributionSource from "./ImageAttributionSource";
|
||||
import BaseUIElement from "../../UI/BaseUIElement";
|
||||
|
|
|
@ -77,7 +77,7 @@ export class Wikimedia extends ImageAttributionSource {
|
|||
|
||||
static GetWikiData(id: number, handleWikidata: ((Wikidata) => void)) {
|
||||
const url = "https://www.wikidata.org/wiki/Special:EntityData/Q" + id + ".json";
|
||||
Utils.downloadJson(url).then (response => {
|
||||
Utils.downloadJson(url).then(response => {
|
||||
const entity = response.entities["Q" + id];
|
||||
const commons = entity.sitelinks.commonswiki;
|
||||
const wd = new Wikidata();
|
||||
|
@ -139,10 +139,10 @@ export class Wikimedia extends ImageAttributionSource {
|
|||
"titles=" + filename +
|
||||
"&format=json&origin=*";
|
||||
Utils.downloadJson(url).then(
|
||||
data =>{
|
||||
data => {
|
||||
const licenseInfo = new LicenseInfo();
|
||||
const license = (data.query.pages[-1].imageinfo ?? [])[0]?.extmetadata;
|
||||
if(license === undefined){
|
||||
if (license === undefined) {
|
||||
console.error("This file has no usable metedata or license attached... Please fix the license info file yourself!")
|
||||
source.setData(null)
|
||||
return;
|
||||
|
@ -156,10 +156,10 @@ export class Wikimedia extends ImageAttributionSource {
|
|||
licenseInfo.licenseShortName = license.LicenseShortName?.value;
|
||||
licenseInfo.credit = license.Credit?.value;
|
||||
licenseInfo.description = license.ImageDescription?.value;
|
||||
source.setData(licenseInfo);
|
||||
source.setData(licenseInfo);
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
return source;
|
||||
|
||||
}
|
||||
|
|
|
@ -18,6 +18,9 @@ interface Params {
|
|||
export default class MetaTagging {
|
||||
|
||||
|
||||
private static errorPrintCount = 0;
|
||||
private static readonly stopErrorOutputAt = 10;
|
||||
|
||||
/**
|
||||
* An actor which adds metatags on every feature in the given object
|
||||
* The features are a list of geojson-features, with a "properties"-field and geometry
|
||||
|
@ -86,9 +89,6 @@ export default class MetaTagging {
|
|||
|
||||
}
|
||||
|
||||
private static errorPrintCount = 0;
|
||||
private static readonly stopErrorOutputAt = 10;
|
||||
|
||||
private static createRetaggingFunc(layer: LayerConfig):
|
||||
((params: Params, feature: any) => void) {
|
||||
const calculatedTags: [string, string][] = layer.calculatedTags;
|
||||
|
@ -111,7 +111,7 @@ export default class MetaTagging {
|
|||
const f = (featuresPerLayer, feature: any) => {
|
||||
try {
|
||||
let result = func(feature);
|
||||
if(result instanceof UIEventSource){
|
||||
if (result instanceof UIEventSource) {
|
||||
result.addCallbackAndRunD(d => {
|
||||
if (typeof d !== "string") {
|
||||
// Make sure it is a string!
|
||||
|
@ -121,7 +121,7 @@ export default class MetaTagging {
|
|||
})
|
||||
result = result.data
|
||||
}
|
||||
|
||||
|
||||
if (result === undefined || result === "") {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -8,11 +8,10 @@ import {GeoOperations} from "../../GeoOperations";
|
|||
|
||||
export default class CreateNewNodeAction extends OsmChangeAction {
|
||||
|
||||
public newElementId: string = undefined
|
||||
private readonly _basicTags: Tag[];
|
||||
private readonly _lat: number;
|
||||
private readonly _lon: number;
|
||||
|
||||
public newElementId: string = undefined
|
||||
private readonly _snapOnto: OsmWay;
|
||||
private readonly _reusePointDistance: number;
|
||||
|
||||
|
@ -21,7 +20,7 @@ export default class CreateNewNodeAction extends OsmChangeAction {
|
|||
this._basicTags = basicTags;
|
||||
this._lat = lat;
|
||||
this._lon = lon;
|
||||
if(lat === undefined || lon === undefined){
|
||||
if (lat === undefined || lon === undefined) {
|
||||
throw "Lat or lon are undefined!"
|
||||
}
|
||||
this._snapOnto = options?.snapOnto;
|
||||
|
@ -82,20 +81,20 @@ export default class CreateNewNodeAction extends OsmChangeAction {
|
|||
id: reusedPointId
|
||||
}]
|
||||
}
|
||||
|
||||
|
||||
const locations = [...this._snapOnto.coordinates]
|
||||
locations.forEach(coor => coor.reverse())
|
||||
console.log("Locations are: ", locations)
|
||||
const ids = [...this._snapOnto.nodes]
|
||||
|
||||
|
||||
locations.splice(index + 1, 0, [this._lon, this._lat])
|
||||
ids.splice(index + 1, 0, id)
|
||||
|
||||
|
||||
// Allright, we have to insert a new point in the way
|
||||
return [
|
||||
newPointChange,
|
||||
{
|
||||
type:"way",
|
||||
type: "way",
|
||||
id: this._snapOnto.id,
|
||||
changes: {
|
||||
locations: locations,
|
||||
|
|
|
@ -30,7 +30,7 @@ export default class DeleteAction {
|
|||
* Does actually delete the feature; returns the event source 'this.isDeleted'
|
||||
* If deletion is not allowed, triggers the callback instead
|
||||
*/
|
||||
public DoDelete(reason: string, onNotAllowed : () => void): void {
|
||||
public DoDelete(reason: string, onNotAllowed: () => void): void {
|
||||
const isDeleted = this.isDeleted
|
||||
const self = this;
|
||||
let deletionStarted = false;
|
||||
|
@ -40,23 +40,21 @@ export default class DeleteAction {
|
|||
// Already deleted...
|
||||
return;
|
||||
}
|
||||
|
||||
if(canBeDeleted.canBeDeleted === false){
|
||||
|
||||
if (canBeDeleted.canBeDeleted === false) {
|
||||
// We aren't allowed to delete
|
||||
deletionStarted = true;
|
||||
onNotAllowed();
|
||||
isDeleted.setData(true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!canBeDeleted) {
|
||||
// We are not allowed to delete (yet), this might change in the future though
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
deletionStarted = true;
|
||||
OsmObject.DownloadObject(self._id).addCallbackAndRun(obj => {
|
||||
if (obj === undefined) {
|
||||
|
@ -207,7 +205,7 @@ export default class DeleteAction {
|
|||
canBeDeleted: false,
|
||||
reason: t.partOfOthers
|
||||
})
|
||||
}else{
|
||||
} else {
|
||||
// alright, this point can be safely deleted!
|
||||
state.setData({
|
||||
canBeDeleted: true,
|
||||
|
@ -215,7 +213,6 @@ export default class DeleteAction {
|
|||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
)
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
/**
|
||||
* The logic to handle relations after a way within
|
||||
* The logic to handle relations after a way within
|
||||
*/
|
||||
import OsmChangeAction from "./OsmChangeAction";
|
||||
import {Changes} from "../Changes";
|
||||
import {ChangeDescription} from "./ChangeDescription";
|
||||
import {OsmRelation, OsmWay} from "../OsmObject";
|
||||
import {OsmRelation} from "../OsmObject";
|
||||
|
||||
export default class RelationSplitlHandler extends OsmChangeAction{
|
||||
export default class RelationSplitlHandler extends OsmChangeAction {
|
||||
|
||||
constructor(partOf: OsmRelation[], newWayIds: number[], originalNodes: number[]) {
|
||||
super()
|
||||
|
|
|
@ -23,18 +23,18 @@ export class Changes {
|
|||
|
||||
public readonly pendingChanges = LocalStorageSource.GetParsed<ChangeDescription[]>("pending-changes", [])
|
||||
private readonly isUploading = new UIEventSource(false);
|
||||
|
||||
private readonly previouslyCreated : OsmObject[] = []
|
||||
|
||||
private readonly previouslyCreated: OsmObject[] = []
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
private static createChangesetFor(csId: string,
|
||||
allChanges: {
|
||||
modifiedObjects: OsmObject[],
|
||||
newObjects: OsmObject[],
|
||||
deletedObjects: OsmObject[]
|
||||
}): string {
|
||||
allChanges: {
|
||||
modifiedObjects: OsmObject[],
|
||||
newObjects: OsmObject[],
|
||||
deletedObjects: OsmObject[]
|
||||
}): string {
|
||||
|
||||
const changedElements = allChanges.modifiedObjects ?? []
|
||||
const newElements = allChanges.newObjects ?? []
|
||||
|
@ -70,6 +70,88 @@ export class Changes {
|
|||
.map(c => c.type + "/" + c.id))
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new ID and updates the value for the next ID
|
||||
*/
|
||||
public getNewID() {
|
||||
return Changes._nextId--;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads all the pending changes in one go.
|
||||
* Triggered by the 'PendingChangeUploader'-actor in Actors
|
||||
*/
|
||||
public flushChanges(flushreason: string = undefined) {
|
||||
if (this.pendingChanges.data.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isUploading.data) {
|
||||
console.log("Is already uploading... Abort")
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
this.isUploading.setData(true)
|
||||
|
||||
console.log("Beginning upload... " + flushreason ?? "");
|
||||
// At last, we build the changeset and upload
|
||||
const self = this;
|
||||
const pending = self.pendingChanges.data;
|
||||
const neededIds = Changes.GetNeededIds(pending)
|
||||
console.log("Needed ids", neededIds)
|
||||
OsmObject.DownloadAll(neededIds, true).addCallbackAndRunD(osmObjects => {
|
||||
console.log("Got the fresh objects!", osmObjects, "pending: ", pending)
|
||||
try {
|
||||
|
||||
|
||||
const changes: {
|
||||
newObjects: OsmObject[],
|
||||
modifiedObjects: OsmObject[]
|
||||
deletedObjects: OsmObject[]
|
||||
|
||||
} = self.CreateChangesetObjects(pending, osmObjects)
|
||||
if (changes.newObjects.length + changes.deletedObjects.length + changes.modifiedObjects.length === 0) {
|
||||
console.log("No changes to be made")
|
||||
self.pendingChanges.setData([])
|
||||
self.isUploading.setData(false)
|
||||
return true; // Unregister the callback
|
||||
}
|
||||
|
||||
|
||||
State.state.osmConnection.UploadChangeset(
|
||||
State.state.layoutToUse.data,
|
||||
State.state.allElements,
|
||||
(csId) => Changes.createChangesetFor(csId, changes),
|
||||
() => {
|
||||
console.log("Upload successfull!")
|
||||
self.pendingChanges.setData([]);
|
||||
self.isUploading.setData(false)
|
||||
},
|
||||
() => {
|
||||
console.log("Upload failed - trying again later")
|
||||
return self.isUploading.setData(false);
|
||||
} // Failed - mark to try again
|
||||
)
|
||||
} catch (e) {
|
||||
console.error("Could not handle changes - probably an old, pending changeset in localstorage with an invalid format; erasing those", e)
|
||||
self.pendingChanges.setData([])
|
||||
self.isUploading.setData(false)
|
||||
}
|
||||
return true;
|
||||
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
public applyAction(action: OsmChangeAction) {
|
||||
const changes = action.Perform(this)
|
||||
console.log("Received changes:", changes)
|
||||
this.pendingChanges.data.push(...changes);
|
||||
this.pendingChanges.ping();
|
||||
}
|
||||
|
||||
private CreateChangesetObjects(changes: ChangeDescription[], downloadedOsmObjects: OsmObject[]): {
|
||||
newObjects: OsmObject[],
|
||||
modifiedObjects: OsmObject[]
|
||||
|
@ -85,7 +167,7 @@ export class Changes {
|
|||
}
|
||||
|
||||
for (const o of this.previouslyCreated) {
|
||||
objects.set(o.type + "/" + o.id, o)
|
||||
objects.set(o.type + "/" + o.id, o)
|
||||
states.set(o.type + "/" + o.id, "unchanged")
|
||||
}
|
||||
|
||||
|
@ -93,8 +175,8 @@ export class Changes {
|
|||
for (const change of changes) {
|
||||
const id = change.type + "/" + change.id
|
||||
if (!objects.has(id)) {
|
||||
if(change.id >= 0){
|
||||
throw "Did not get an object that should be known: "+id
|
||||
if (change.id >= 0) {
|
||||
throw "Did not get an object that should be known: " + id
|
||||
}
|
||||
// This is a new object that should be created
|
||||
states.set(id, "created")
|
||||
|
@ -189,7 +271,7 @@ export class Changes {
|
|||
break;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (changed && state === "unchanged") {
|
||||
|
@ -203,7 +285,7 @@ export class Changes {
|
|||
modifiedObjects: [],
|
||||
deletedObjects: []
|
||||
}
|
||||
|
||||
|
||||
objects.forEach((v, id) => {
|
||||
|
||||
const state = states.get(id)
|
||||
|
@ -221,86 +303,4 @@ export class Changes {
|
|||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new ID and updates the value for the next ID
|
||||
*/
|
||||
public getNewID() {
|
||||
return Changes._nextId--;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads all the pending changes in one go.
|
||||
* Triggered by the 'PendingChangeUploader'-actor in Actors
|
||||
*/
|
||||
public flushChanges(flushreason: string = undefined) {
|
||||
if (this.pendingChanges.data.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isUploading.data) {
|
||||
console.log("Is already uploading... Abort")
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
this.isUploading.setData(true)
|
||||
|
||||
console.log("Beginning upload... "+flushreason ?? "");
|
||||
// At last, we build the changeset and upload
|
||||
const self = this;
|
||||
const pending = self.pendingChanges.data;
|
||||
const neededIds = Changes.GetNeededIds(pending)
|
||||
console.log("Needed ids", neededIds)
|
||||
OsmObject.DownloadAll(neededIds, true).addCallbackAndRunD(osmObjects => {
|
||||
console.log("Got the fresh objects!", osmObjects, "pending: ", pending)
|
||||
try{
|
||||
|
||||
|
||||
const changes: {
|
||||
newObjects: OsmObject[],
|
||||
modifiedObjects: OsmObject[]
|
||||
deletedObjects: OsmObject[]
|
||||
|
||||
} = self.CreateChangesetObjects(pending, osmObjects)
|
||||
if (changes.newObjects.length + changes.deletedObjects.length + changes.modifiedObjects.length === 0) {
|
||||
console.log("No changes to be made")
|
||||
self.pendingChanges.setData([])
|
||||
self.isUploading.setData(false)
|
||||
return true; // Unregister the callback
|
||||
}
|
||||
|
||||
|
||||
State.state.osmConnection.UploadChangeset(
|
||||
State.state.layoutToUse.data,
|
||||
State.state.allElements,
|
||||
(csId) => Changes.createChangesetFor(csId, changes),
|
||||
() => {
|
||||
console.log("Upload successfull!")
|
||||
self.pendingChanges.setData([]);
|
||||
self.isUploading.setData(false)
|
||||
},
|
||||
() => {
|
||||
console.log("Upload failed - trying again later")
|
||||
return self.isUploading.setData(false);
|
||||
} // Failed - mark to try again
|
||||
)
|
||||
}catch(e){
|
||||
console.error("Could not handle changes - probably an old, pending changeset in localstorage with an invalid format; erasing those", e)
|
||||
self.pendingChanges.setData([])
|
||||
self.isUploading.setData(false)
|
||||
}
|
||||
return true;
|
||||
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
public applyAction(action: OsmChangeAction) {
|
||||
const changes = action.Perform(this)
|
||||
console.log("Received changes:", changes)
|
||||
this.pendingChanges.data.push(...changes);
|
||||
this.pendingChanges.ping();
|
||||
}
|
||||
}
|
|
@ -258,7 +258,7 @@ export class ChangesetHandler {
|
|||
}, function (err, response) {
|
||||
if (response === undefined) {
|
||||
console.log("err", err);
|
||||
if(options.onFail){
|
||||
if (options.onFail) {
|
||||
options.onFail()
|
||||
}
|
||||
return;
|
||||
|
|
|
@ -15,7 +15,7 @@ export interface Relation {
|
|||
|
||||
export default class ExtractRelations {
|
||||
|
||||
public static RegisterRelations(overpassJson: any) : void{
|
||||
public static RegisterRelations(overpassJson: any): void {
|
||||
const memberships = ExtractRelations.BuildMembershipTable(ExtractRelations.GetRelationElements(overpassJson))
|
||||
State.state.knownRelations.setData(memberships)
|
||||
}
|
||||
|
|
|
@ -6,12 +6,14 @@ export class Geocoding {
|
|||
private static readonly host = "https://nominatim.openstreetmap.org/search?";
|
||||
|
||||
static Search(query: string,
|
||||
handleResult: ((places: { display_name: string, lat: number, lon: number, boundingbox: number[],
|
||||
osm_type: string, osm_id: string}[]) => void),
|
||||
handleResult: ((places: {
|
||||
display_name: string, lat: number, lon: number, boundingbox: number[],
|
||||
osm_type: string, osm_id: string
|
||||
}[]) => void),
|
||||
onFail: (() => void)) {
|
||||
const b = State.state.leafletMap.data.getBounds();
|
||||
const url = Geocoding.host + "format=json&limit=1&viewbox=" +
|
||||
`${b.getEast()},${b.getNorth()},${b.getWest()},${b.getSouth()}`+
|
||||
`${b.getEast()},${b.getNorth()},${b.getWest()},${b.getSouth()}` +
|
||||
"&accept-language=nl&q=" + query;
|
||||
Utils.downloadJson(
|
||||
url)
|
||||
|
|
|
@ -22,7 +22,7 @@ export default class UserDetails {
|
|||
public dryRun: boolean;
|
||||
home: { lon: number; lat: number };
|
||||
public backend: string;
|
||||
|
||||
|
||||
constructor(backend: string) {
|
||||
this.backend = backend;
|
||||
}
|
||||
|
@ -47,10 +47,10 @@ export class OsmConnection {
|
|||
public auth;
|
||||
public userDetails: UIEventSource<UserDetails>;
|
||||
public isLoggedIn: UIEventSource<boolean>
|
||||
private fakeUser: boolean;
|
||||
_dryRun: boolean;
|
||||
public preferencesHandler: OsmPreferences;
|
||||
public changesetHandler: ChangesetHandler;
|
||||
private fakeUser: boolean;
|
||||
private _onLoggedIn: ((userDetails: UserDetails) => void)[] = [];
|
||||
private readonly _iframeMode: Boolean | boolean;
|
||||
private readonly _singlePage: boolean;
|
||||
|
@ -59,8 +59,9 @@ export class OsmConnection {
|
|||
oauth_secret: string,
|
||||
url: string
|
||||
};
|
||||
private isChecking = false;
|
||||
|
||||
constructor(dryRun: boolean,
|
||||
constructor(dryRun: boolean,
|
||||
fakeUser: boolean,
|
||||
oauth_token: UIEventSource<string>,
|
||||
// Used to keep multiple changesets open and to write to the correct changeset
|
||||
|
@ -77,17 +78,17 @@ export class OsmConnection {
|
|||
|
||||
this.userDetails = new UIEventSource<UserDetails>(new UserDetails(this._oauth_config.url), "userDetails");
|
||||
this.userDetails.data.dryRun = dryRun || fakeUser;
|
||||
if(fakeUser){
|
||||
if (fakeUser) {
|
||||
const ud = this.userDetails.data;
|
||||
ud.csCount = 5678
|
||||
ud.loggedIn= true;
|
||||
ud.loggedIn = true;
|
||||
ud.unreadMessages = 0
|
||||
ud.name = "Fake user"
|
||||
ud.totalMessages = 42;
|
||||
}
|
||||
const self =this;
|
||||
const self = this;
|
||||
this.isLoggedIn = this.userDetails.map(user => user.loggedIn).addCallback(isLoggedIn => {
|
||||
if(self.userDetails.data.loggedIn == false && isLoggedIn == true){
|
||||
if (self.userDetails.data.loggedIn == false && isLoggedIn == true) {
|
||||
// We have an inconsistency: the userdetails say we _didn't_ log in, but this actor says we do
|
||||
// This means someone attempted to toggle this; so we attempt to login!
|
||||
self.AttemptLogin()
|
||||
|
@ -150,7 +151,7 @@ export class OsmConnection {
|
|||
}
|
||||
|
||||
public AttemptLogin() {
|
||||
if(this.fakeUser){
|
||||
if (this.fakeUser) {
|
||||
console.log("AttemptLogin called, but ignored as fakeUser is set")
|
||||
return;
|
||||
}
|
||||
|
@ -191,7 +192,7 @@ export class OsmConnection {
|
|||
data.loggedIn = true;
|
||||
console.log("Login completed, userinfo is ", userInfo);
|
||||
data.name = userInfo.getAttribute('display_name');
|
||||
data.uid= Number(userInfo.getAttribute("id"))
|
||||
data.uid = Number(userInfo.getAttribute("id"))
|
||||
data.csCount = userInfo.getElementsByTagName("changesets")[0].getAttribute("count");
|
||||
|
||||
data.img = undefined;
|
||||
|
@ -249,20 +250,19 @@ export class OsmConnection {
|
|||
});
|
||||
}
|
||||
|
||||
private isChecking = false;
|
||||
private CheckForMessagesContinuously(){
|
||||
const self =this;
|
||||
if(this.isChecking){
|
||||
private CheckForMessagesContinuously() {
|
||||
const self = this;
|
||||
if (this.isChecking) {
|
||||
return;
|
||||
}
|
||||
this.isChecking = true;
|
||||
UIEventSource.Chronic(5 * 60 * 1000).addCallback(_ => {
|
||||
if (self.isLoggedIn .data) {
|
||||
console.log("Checking for messages")
|
||||
self.AttemptLogin();
|
||||
}
|
||||
if (self.isLoggedIn.data) {
|
||||
console.log("Checking for messages")
|
||||
self.AttemptLogin();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ export abstract class OsmObject {
|
|||
const splitted = id.split("/");
|
||||
const type = splitted[0];
|
||||
const idN = Number(splitted[1]);
|
||||
if(idN <0){
|
||||
if (idN < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -438,7 +438,7 @@ export class OsmWay extends OsmObject {
|
|||
|
||||
for (const nodeId of element.nodes) {
|
||||
const node = nodeDict.get(nodeId)
|
||||
if(node === undefined){
|
||||
if (node === undefined) {
|
||||
console.error("Error: node ", nodeId, "not found in ", nodeDict)
|
||||
// This is probably part of a relation which hasn't been fully downloaded
|
||||
continue;
|
||||
|
@ -498,7 +498,7 @@ export class OsmRelation extends OsmObject {
|
|||
|
||||
let tags = this.TagsXML();
|
||||
let cs = ""
|
||||
if(changesetId !== undefined){
|
||||
if (changesetId !== undefined) {
|
||||
cs = `changeset="${changesetId}"`
|
||||
}
|
||||
return ` <relation id="${this.id}" ${cs} ${this.VersionXML()}>
|
||||
|
|
|
@ -29,7 +29,7 @@ export class OsmPreferences {
|
|||
return this.longPreferences[prefix + key];
|
||||
}
|
||||
|
||||
const source = new UIEventSource<string>(undefined, "long-osm-preference:"+prefix+key);
|
||||
const source = new UIEventSource<string>(undefined, "long-osm-preference:" + prefix + key);
|
||||
this.longPreferences[prefix + key] = source;
|
||||
|
||||
const allStartWith = prefix + key + "-combined";
|
||||
|
@ -107,7 +107,7 @@ export class OsmPreferences {
|
|||
if (this.userDetails.data.loggedIn && this.preferences.data[key] === undefined) {
|
||||
this.UpdatePreferences();
|
||||
}
|
||||
const pref = new UIEventSource<string>(this.preferences.data[key],"osm-preference:"+key);
|
||||
const pref = new UIEventSource<string>(this.preferences.data[key], "osm-preference:" + key);
|
||||
pref.addCallback((v) => {
|
||||
this.SetPreference(key, v);
|
||||
});
|
||||
|
|
|
@ -15,7 +15,7 @@ export class Overpass {
|
|||
private readonly _timeout: UIEventSource<number>;
|
||||
private readonly _extraScripts: string[];
|
||||
private _includeMeta: boolean;
|
||||
|
||||
|
||||
constructor(filter: TagsFilter, extraScripts: string[],
|
||||
interpreterUrl: UIEventSource<string>,
|
||||
timeout: UIEventSource<number>,
|
||||
|
@ -42,7 +42,7 @@ export class Overpass {
|
|||
onFail("Runtime error (timeout)")
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
ExtractRelations.RegisterRelations(json)
|
||||
// @ts-ignore
|
||||
|
|
|
@ -12,12 +12,9 @@ export default class AspectedRouting {
|
|||
this.program = JSON.parse(JSON.stringify(program))
|
||||
delete this.program.name
|
||||
delete this.program.description
|
||||
delete this.program.unit
|
||||
delete this.program.unit
|
||||
}
|
||||
|
||||
public evaluate(properties){
|
||||
return AspectedRouting.interpret(this.program, properties)
|
||||
}
|
||||
/**
|
||||
* Interprets the given Aspected-routing program for the given properties
|
||||
*/
|
||||
|
@ -191,4 +188,8 @@ export default class AspectedRouting {
|
|||
return result;
|
||||
}
|
||||
|
||||
public evaluate(properties) {
|
||||
return AspectedRouting.interpret(this.program, properties)
|
||||
}
|
||||
|
||||
}
|
|
@ -96,7 +96,7 @@ export default class SimpleMetaTagger {
|
|||
const value = feature.properties[key]
|
||||
const [, denomination] = unit.findDenomination(value)
|
||||
let canonical = denomination?.canonicalValue(value) ?? undefined;
|
||||
if(canonical === value){
|
||||
if (canonical === value) {
|
||||
break;
|
||||
}
|
||||
console.log("Rewritten ", key, ` from '${value}' into '${canonical}'`)
|
||||
|
@ -110,7 +110,7 @@ export default class SimpleMetaTagger {
|
|||
}
|
||||
|
||||
}
|
||||
if(rewritten){
|
||||
if (rewritten) {
|
||||
State.state.allElements.getEventSourceById(feature.id).ping();
|
||||
}
|
||||
})
|
||||
|
|
|
@ -7,18 +7,6 @@ export class And extends TagsFilter {
|
|||
super();
|
||||
this.and = and
|
||||
}
|
||||
|
||||
normalize(){
|
||||
const ands = []
|
||||
for (const c of this.and) {
|
||||
if(c instanceof And){
|
||||
ands.push(...c.and)
|
||||
}else{
|
||||
ands.push(c)
|
||||
}
|
||||
}
|
||||
return new And(ands)
|
||||
}
|
||||
|
||||
private static combine(filter: string, choices: string[]): string[] {
|
||||
const values = [];
|
||||
|
@ -28,6 +16,18 @@ export class And extends TagsFilter {
|
|||
return values;
|
||||
}
|
||||
|
||||
normalize() {
|
||||
const ands = []
|
||||
for (const c of this.and) {
|
||||
if (c instanceof And) {
|
||||
ands.push(...c.and)
|
||||
} else {
|
||||
ands.push(c)
|
||||
}
|
||||
}
|
||||
return new And(ands)
|
||||
}
|
||||
|
||||
matchesProperties(tags: any): boolean {
|
||||
for (const tagsFilter of this.and) {
|
||||
if (!tagsFilter.matchesProperties(tags)) {
|
||||
|
|
|
@ -4,19 +4,19 @@ export default class ComparingTag implements TagsFilter {
|
|||
private readonly _key: string;
|
||||
private readonly _predicate: (value: string) => boolean;
|
||||
private readonly _representation: string;
|
||||
|
||||
constructor(key: string, predicate : (value:string | undefined) => boolean, representation: string = "") {
|
||||
|
||||
constructor(key: string, predicate: (value: string | undefined) => boolean, representation: string = "") {
|
||||
this._key = key;
|
||||
this._predicate = predicate;
|
||||
this._representation = representation;
|
||||
}
|
||||
|
||||
|
||||
asChange(properties: any): { k: string; v: string }[] {
|
||||
throw "A comparable tag can not be used to be uploaded to OSM"
|
||||
}
|
||||
|
||||
asHumanString(linkToWiki: boolean, shorten: boolean, properties: any) {
|
||||
return this._key+this._representation
|
||||
return this._key + this._representation
|
||||
}
|
||||
|
||||
asOverpass(): string[] {
|
||||
|
@ -38,5 +38,5 @@ export default class ComparingTag implements TagsFilter {
|
|||
usedKeys(): string[] {
|
||||
return [this._key];
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -16,7 +16,7 @@ export class RegexTag extends TagsFilter {
|
|||
}
|
||||
|
||||
private static doesMatch(fromTag: string, possibleRegex: string | RegExp): boolean {
|
||||
if(fromTag === undefined){
|
||||
if (fromTag === undefined) {
|
||||
return;
|
||||
}
|
||||
if (typeof possibleRegex === "string") {
|
||||
|
@ -45,7 +45,7 @@ export class RegexTag extends TagsFilter {
|
|||
|
||||
matchesProperties(tags: any): boolean {
|
||||
for (const key in tags) {
|
||||
if(key === undefined){
|
||||
if (key === undefined) {
|
||||
continue;
|
||||
}
|
||||
if (RegexTag.doesMatch(key, this.key)) {
|
||||
|
@ -86,14 +86,14 @@ export class RegexTag extends TagsFilter {
|
|||
}
|
||||
|
||||
asChange(properties: any): { k: string; v: string }[] {
|
||||
if(this.invert){
|
||||
if (this.invert) {
|
||||
return []
|
||||
}
|
||||
if (typeof this.key === "string") {
|
||||
if( typeof this.value === "string"){
|
||||
if (typeof this.value === "string") {
|
||||
return [{k: this.key, v: this.value}]
|
||||
}
|
||||
if(this.value.toString() != "/^..*$/"){
|
||||
if (this.value.toString() != "/^..*$/") {
|
||||
console.warn("Regex value in tag; using wildcard:", this.key, this.value)
|
||||
}
|
||||
return [{k: this.key, v: undefined}]
|
||||
|
|
|
@ -24,19 +24,19 @@ export class Tag extends TagsFilter {
|
|||
|
||||
matchesProperties(properties: any): boolean {
|
||||
for (const propertiesKey in properties) {
|
||||
if(!properties.hasOwnProperty(propertiesKey)){
|
||||
if (!properties.hasOwnProperty(propertiesKey)) {
|
||||
continue
|
||||
}
|
||||
if (this.key === propertiesKey) {
|
||||
const value = properties[propertiesKey];
|
||||
if(value === undefined){
|
||||
if (value === undefined) {
|
||||
continue
|
||||
}
|
||||
return value === this.value;
|
||||
}
|
||||
}
|
||||
// The tag was not found
|
||||
|
||||
|
||||
if (this.value === "") {
|
||||
// and it shouldn't be found!
|
||||
return true;
|
||||
|
@ -52,6 +52,7 @@ export class Tag extends TagsFilter {
|
|||
}
|
||||
return [`["${this.key}"="${this.value}"]`];
|
||||
}
|
||||
|
||||
asHumanString(linkToWiki?: boolean, shorten?: boolean) {
|
||||
let v = this.value;
|
||||
if (shorten) {
|
||||
|
@ -84,6 +85,6 @@ export class Tag extends TagsFilter {
|
|||
}
|
||||
|
||||
asChange(properties: any): { k: string; v: string }[] {
|
||||
return [{k: this.key, v: this.value}];
|
||||
return [{k: this.key, v: this.value}];
|
||||
}
|
||||
}
|
|
@ -10,6 +10,15 @@ import {AndOrTagConfigJson} from "../../Models/ThemeConfig/Json/TagConfigJson";
|
|||
import {isRegExp} from "util";
|
||||
|
||||
export class TagUtils {
|
||||
private static comparators
|
||||
: [string, (a: number, b: number) => boolean][]
|
||||
= [
|
||||
["<=", (a, b) => a <= b],
|
||||
[">=", (a, b) => a >= b],
|
||||
["<", (a, b) => a < b],
|
||||
[">", (a, b) => a > b],
|
||||
]
|
||||
|
||||
static ApplyTemplate(template: string, tags: any): string {
|
||||
for (const k in tags) {
|
||||
while (template.indexOf("{" + k + "}") >= 0) {
|
||||
|
@ -75,21 +84,21 @@ export class TagUtils {
|
|||
keyValues[tagsFilter.key].push(...tagsFilter.value.split(";"));
|
||||
continue;
|
||||
}
|
||||
|
||||
if(allowRegex && tagsFilter instanceof RegexTag) {
|
||||
|
||||
if (allowRegex && tagsFilter instanceof RegexTag) {
|
||||
const key = tagsFilter.key
|
||||
if(isRegExp(key)) {
|
||||
if (isRegExp(key)) {
|
||||
console.error("Invalid type to flatten the multiAnswer: key is a regex too", tagsFilter);
|
||||
throw "Invalid type to FlattenMultiAnswer"
|
||||
}
|
||||
const keystr = <string>key
|
||||
if (keyValues[keystr] === undefined) {
|
||||
keyValues[keystr ] = [];
|
||||
keyValues[keystr] = [];
|
||||
}
|
||||
keyValues[keystr].push(tagsFilter);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
console.error("Invalid type to flatten the multiAnswer", tagsFilter);
|
||||
throw "Invalid type to FlattenMultiAnswer"
|
||||
|
@ -138,13 +147,13 @@ export class TagUtils {
|
|||
|
||||
const actualValue = properties[splitKey].split(";");
|
||||
for (const neededValue of neededValues) {
|
||||
|
||||
if(neededValue instanceof RegexTag) {
|
||||
if(!neededValue.matchesProperties(properties)) {
|
||||
|
||||
if (neededValue instanceof RegexTag) {
|
||||
if (!neededValue.matchesProperties(properties)) {
|
||||
return false
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
if (actualValue.indexOf(neededValue) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
@ -170,15 +179,6 @@ export class TagUtils {
|
|||
}
|
||||
}
|
||||
|
||||
private static comparators
|
||||
: [string, (a: number, b: number) => boolean][]
|
||||
= [
|
||||
["<=", (a, b) => a <= b],
|
||||
[">=", (a, b) => a >= b],
|
||||
["<", (a, b) => a < b],
|
||||
[">", (a, b) => a > b],
|
||||
]
|
||||
|
||||
private static TagUnsafe(json: AndOrTagConfigJson | string, context: string = ""): TagsFilter {
|
||||
|
||||
if (json === undefined) {
|
||||
|
|
|
@ -11,13 +11,13 @@ export abstract class TagsFilter {
|
|||
abstract asHumanString(linkToWiki: boolean, shorten: boolean, properties: any);
|
||||
|
||||
abstract usedKeys(): string[];
|
||||
|
||||
|
||||
/**
|
||||
* Converts the tagsFilter into a list of key-values that should be uploaded to OSM.
|
||||
* Throws an error if not applicable.
|
||||
*
|
||||
*
|
||||
* Note: properties are the already existing tags-object. It is only used in the substituting tag
|
||||
*/
|
||||
abstract asChange(properties:any): {k: string, v:string}[]
|
||||
|
||||
abstract asChange(properties: any): { k: string, v: string }[]
|
||||
|
||||
}
|
|
@ -184,7 +184,7 @@ export class UIEventSource<T> {
|
|||
addCallbackAndRunD(callback: (data: T) => void) {
|
||||
this.addCallbackAndRun(data => {
|
||||
if (data !== undefined && data !== null) {
|
||||
return callback(data)
|
||||
return callback(data)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -194,9 +194,9 @@ export class UIEventSourceTools {
|
|||
|
||||
private static readonly _download_cache = new Map<string, UIEventSource<any>>()
|
||||
|
||||
public static downloadJsonCached(url: string): UIEventSource<any>{
|
||||
public static downloadJsonCached(url: string): UIEventSource<any> {
|
||||
const cached = UIEventSourceTools._download_cache.get(url)
|
||||
if(cached !== undefined){
|
||||
if (cached !== undefined) {
|
||||
return cached;
|
||||
}
|
||||
const src = new UIEventSource<any>(undefined)
|
||||
|
|
|
@ -48,7 +48,7 @@ export default class Hash {
|
|||
}
|
||||
hash.setData(newValue)
|
||||
}
|
||||
|
||||
|
||||
window.addEventListener('popstate', _ => {
|
||||
let newValue = window.location.hash.substr(1);
|
||||
if (newValue === "") {
|
||||
|
|
|
@ -24,7 +24,7 @@ export default class LiveQueryHandler {
|
|||
const source = new UIEventSource({});
|
||||
LiveQueryHandler[url] = source;
|
||||
|
||||
console.log("Fetching live data from a third-party (unknown) API:",url)
|
||||
console.log("Fetching live data from a third-party (unknown) API:", url)
|
||||
Utils.downloadJson(url).then(data => {
|
||||
for (const shorthandDescription of shorthandsSet) {
|
||||
|
||||
|
|
|
@ -4,19 +4,19 @@ import {UIEventSource} from "../UIEventSource";
|
|||
* UIEventsource-wrapper around localStorage
|
||||
*/
|
||||
export class LocalStorageSource {
|
||||
|
||||
static GetParsed<T>(key: string, defaultValue : T) : UIEventSource<T>{
|
||||
|
||||
static GetParsed<T>(key: string, defaultValue: T): UIEventSource<T> {
|
||||
return LocalStorageSource.Get(key).map(
|
||||
str => {
|
||||
if(str === undefined){
|
||||
if (str === undefined) {
|
||||
return defaultValue
|
||||
}
|
||||
try{
|
||||
try {
|
||||
return JSON.parse(str)
|
||||
}catch{
|
||||
} catch {
|
||||
return defaultValue
|
||||
}
|
||||
}, [],
|
||||
}, [],
|
||||
value => JSON.stringify(value)
|
||||
)
|
||||
}
|
||||
|
@ -24,17 +24,17 @@ export class LocalStorageSource {
|
|||
static Get(key: string, defaultValue: string = undefined): UIEventSource<string> {
|
||||
try {
|
||||
const saved = localStorage.getItem(key);
|
||||
const source = new UIEventSource<string>(saved ?? defaultValue, "localstorage:"+key);
|
||||
const source = new UIEventSource<string>(saved ?? defaultValue, "localstorage:" + key);
|
||||
|
||||
source.addCallback((data) => {
|
||||
try{
|
||||
try {
|
||||
localStorage.setItem(key, data);
|
||||
}catch(e){
|
||||
} catch (e) {
|
||||
// Probably exceeded the quota with this item!
|
||||
// Lets nuke everything
|
||||
localStorage.clear()
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
return source;
|
||||
} catch (e) {
|
||||
|
|
|
@ -69,6 +69,10 @@ export class QueryParameters {
|
|||
return docs.join("\n\n");
|
||||
}
|
||||
|
||||
public static wasInitialized(key: string): boolean {
|
||||
return QueryParameters._wasInitialized.has(key)
|
||||
}
|
||||
|
||||
private static addOrder(key) {
|
||||
if (this.order.indexOf(key) < 0) {
|
||||
this.order.push(key)
|
||||
|
@ -104,10 +108,6 @@ export class QueryParameters {
|
|||
console.log(QueryParameters.GenerateQueryParameterDocs())
|
||||
}
|
||||
}
|
||||
|
||||
public static wasInitialized(key: string) : boolean{
|
||||
return QueryParameters._wasInitialized.has(key)
|
||||
}
|
||||
|
||||
private static Serialize() {
|
||||
const parts = []
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue