Huge refactorings of JSON-parsing and Tagsfilter, other cleanups, warning cleanups and lots of small subtle bugfixes

This commit is contained in:
Pieter Vander Vennet 2020-08-30 01:13:18 +02:00
parent 9a5b35b9f3
commit a57b7d93fa
113 changed files with 1565 additions and 2594 deletions

View file

@ -48,9 +48,4 @@ export class ElementStorage {
}
console.log("Can not find eventsource with id ", elementId);
}
removeId(oldId: string) {
delete this._elements[oldId];
}
}

View file

@ -1,4 +1,4 @@
import {TagsFilter, TagUtils} from "./TagsFilter";
import {TagsFilter, TagUtils} from "./Tags";
import {UIEventSource} from "./UIEventSource";
import L from "leaflet"
import {GeoOperations} from "./GeoOperations";
@ -21,6 +21,7 @@ export class FilteredLayer {
public readonly name: string | UIElement;
public readonly filters: TagsFilter;
public readonly isDisplayed: UIEventSource<boolean> = new UIEventSource(true);
private readonly combinedIsDisplayed : UIEventSource<boolean>;
public readonly layerDef: LayerDefinition;
private readonly _maxAllowedOverlap: number;
@ -29,8 +30,8 @@ export class FilteredLayer {
/** The featurecollection from overpass
*/
private _dataFromOverpass : any[];
private _wayHandling: number;
private _dataFromOverpass: any[];
private readonly _wayHandling: number;
/** List of new elements, geojson features
*/
private _newElements = [];
@ -60,7 +61,12 @@ export class FilteredLayer {
this.filters = layerDef.overpassFilter;
this._maxAllowedOverlap = layerDef.maxAllowedOverlapPercentage;
const self = this;
this.isDisplayed.addCallback(function (isDisplayed) {
this.combinedIsDisplayed = this.isDisplayed.map<boolean>(isDisplayed => {
return isDisplayed && State.state.locationControl.data.zoom >= self.layerDef.minzoom
},
[State.state.locationControl]
);
this.combinedIsDisplayed.addCallback(function (isDisplayed) {
const map = State.state.bm.map;
if (self._geolayer !== undefined && self._geolayer !== null) {
if (isDisplayed) {
@ -91,7 +97,8 @@ export class FilteredLayer {
const selfFeatures = [];
for (let feature of geojson.features) {
// feature.properties contains all the properties
var tags = TagUtils.proprtiesToKV(feature.properties);
const tags = TagUtils.proprtiesToKV(feature.properties);
if (this.filters.matches(tags)) {
const centerPoint = GeoOperations.centerpoint(feature);
feature.properties["_surface"] = ""+GeoOperations.surfaceAreaInSqMeters(feature);
@ -204,7 +211,6 @@ export class FilteredLayer {
style: function (feature) {
return self._style(feature.properties);
},
pointToLayer: function (feature, latLng) {
const style = self._style(feature.properties);
let marker;
@ -231,7 +237,7 @@ export class FilteredLayer {
const uiElement = self._showOnPopup(eventSource, feature);
const popup = L.popup({}, marker).setContent(uiElement.Render());
marker.bindPopup(popup)
.on("popupopen", (popup) => {
.on("popupopen", () => {
uiElement.Activate();
uiElement.Update();
});
@ -264,7 +270,7 @@ export class FilteredLayer {
eventSource.addCallback(feature.updateStyle);
layer.on("click", function (e) {
const prevSelectedElement = State.state.selectedElement.data?.feature.updateStyle();
State.state.selectedElement.data?.feature.updateStyle();
State.state.selectedElement.setData({feature: feature});
feature.updateStyle()
if (feature.geometry.type === "Point") {
@ -272,13 +278,13 @@ export class FilteredLayer {
}
const uiElement = self._showOnPopup(eventSource, feature);
const popup = L.popup({
L.popup({
autoPan: true,
})
.setContent(uiElement.Render())
}).setContent(uiElement.Render())
.setLatLng(e.latlng)
.openOn(State.state.bm.map);
uiElement.Update();
uiElement.Activate();
L.DomEvent.stop(e); // Marks the event as consumed
@ -286,7 +292,7 @@ export class FilteredLayer {
}
});
if (this.isDisplayed.data) {
if (this.combinedIsDisplayed.data) {
this._geolayer.addTo(State.state.bm.map);
}
}

View file

@ -77,37 +77,6 @@ export class GeoOperations {
return false;
}
/**
* Simple check: that every point of the polygon is inside the container
* @param polygon
* @param container
*/
private static isPolygonInside(polygon, container) {
for (const coor of polygon.geometry.coordinates[0]) {
if (!GeoOperations.inside(coor, container)) {
return false;
}
}
return true;
}
/**
* Simple check: one point of the polygon is inside the container
* @param polygon
* @param container
*/
private static isPolygonTouching(polygon, container) {
for (const coor of polygon.geometry.coordinates[0]) {
if (GeoOperations.inside(coor, container)) {
return true;
}
}
return false;
}
private static inside(pointCoordinate, feature): boolean {
// ray-casting algorithm based on
// http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
@ -124,7 +93,7 @@ export class GeoOperations {
let poly = feature.geometry.coordinates[0];
var inside = false;
for (var i = 0, j = poly.length - 1; i < poly.length; j = i++) {
for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
const coori = poly[i];
const coorj = poly[j];
@ -133,7 +102,7 @@ export class GeoOperations {
const xj = coorj[0];
const yj = coorj[1];
var intersect = ((yi > y) != (yj > y))
const intersect = ((yi > y) != (yj > y))
&& (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
if (intersect) {
inside = !inside;
@ -146,7 +115,7 @@ export class GeoOperations {
}
class BBox {
class BBox{
readonly maxLat: number;
readonly maxLon: number;
@ -188,10 +157,8 @@ class BBox {
if (this.minLon > other.maxLon) {
return false;
}
if (this.minLat > other.maxLat) {
return false;
}
return true;
return this.minLat <= other.maxLat;
}
static get(feature) {

View file

@ -1,12 +1,11 @@
import {WikimediaImage} from "../UI/Image/WikimediaImage";
import {SimpleImageElement} from "../UI/Image/SimpleImageElement";
import {UIElement} from "../UI/UIElement";
import {Changes} from "./Osm/Changes";
import {ImgurImage} from "../UI/Image/ImgurImage";
import {State} from "../State";
import {ImagesInCategory, Wikidata, Wikimedia} from "./Web/Wikimedia";
import {UIEventSource} from "./UIEventSource";
import {Tag} from "./TagsFilter";
import {Tag} from "./Tags";
/**
* There are multiple way to fetch images for an object
@ -150,7 +149,6 @@ export class ImageSearcher extends UIEventSource<string[]> {
}
for (const key in this._tags.data) {
// @ts-ignore
if (key.startsWith("image:")) {
const url = this._tags.data[key]
this.AddImage(url);

View file

@ -1,9 +1,8 @@
import {Or, TagsFilter} from "./TagsFilter";
import {Or, TagsFilter} from "./Tags";
import {UIEventSource} from "./UIEventSource";
import {FilteredLayer} from "./FilteredLayer";
import {Bounds} from "./Bounds";
import {Overpass} from "./Osm/Overpass";
import {Basemap} from "./Leaflet/Basemap";
import {State} from "../State";
export class LayerUpdater {
@ -18,20 +17,20 @@ export class LayerUpdater {
* If the map location changes, we check for each layer if it is loaded:
* we start checking the bounds at the first zoom level the layer might operate. If in bounds - no reload needed, otherwise we continue walking down
*/
private previousBounds: Map<number, Bounds[]> = new Map<number, Bounds[]>();
private readonly previousBounds: Map<number, Bounds[]> = new Map<number, Bounds[]>();
/**
* The most important layer should go first, as that one gets first pick for the questions
* @param map
* @param minzoom
* @param layers
*/
constructor(state: State) {
const self = this;
let minzoom = Math.min(...state.layoutToUse.data.layers.map(layer => layer.minzoom));
this.sufficentlyZoomed = State.state.locationControl.map(location => location.zoom >= minzoom);
this.sufficentlyZoomed = State.state.locationControl.map(location => {
let minzoom = Math.min(...state.layoutToUse.data.layers.map(layer => layer.minzoom ?? 18));
return location.zoom >= minzoom;
}, [state.layoutToUse]
);
for (let i = 0; i < 25; i++) {
// This update removes all data on all layers -> erase the map on lower levels too
this.previousBounds.set(i, []);
@ -47,7 +46,7 @@ export class LayerUpdater {
}
private GetFilter(state: State) {
var filters: TagsFilter[] = [];
const filters: TagsFilter[] = [];
state = state ?? State.state;
for (const layer of state.layoutToUse.data.layers) {
if (state.locationControl.data.zoom < layer.minzoom) {
@ -142,15 +141,14 @@ export class LayerUpdater {
const w = Math.max(-180, bounds.getWest() - diff);
const queryBounds = {north: n, east: e, south: s, west: w};
const z = state.locationControl.data.zoom;
this.previousBounds.get(z).push(queryBounds);
const z = Math.floor(state.locationControl.data.zoom);
this.runningQuery.setData(true);
const self = this;
const overpass = new Overpass(filter);
overpass.queryGeoJson(queryBounds,
function (data) {
self.previousBounds.get(z).push(queryBounds);
self.handleData(data)
},
function (reason) {
@ -162,33 +160,21 @@ export class LayerUpdater {
private IsInBounds(state: State, bounds: Bounds): boolean {
if (this.previousBounds === undefined) {
return false;
}
const b = state.bm.map.getBounds();
if (b.getSouth() < bounds.south) {
return false;
}
if (b.getNorth() > bounds.north) {
return false;
}
if (b.getEast() > bounds.east) {
return false;
}
if (b.getWest() < bounds.west) {
return false;
}
return true;
return b.getSouth() >= bounds.south &&
b.getNorth() <= bounds.north &&
b.getEast() <= bounds.east &&
b.getWest() >= bounds.west;
}
public ForceRefresh(){
this.previousBounds = undefined;
public ForceRefresh() {
for (let i = 0; i < 25; i++) {
this.previousBounds.set(i, []);
}
}
}

View file

@ -60,12 +60,12 @@ export class Basemap {
// @ts-ignore
public map: Map;
public readonly map: Map;
public Location: UIEventSource<{ zoom: number, lat: number, lon: number }>;
public LastClickLocation: UIEventSource<{ lat: number, lon: number }> = new UIEventSource<{ lat: number, lon: number }>(undefined)
private _previousLayer: L.tileLayer = undefined;
public CurrentLayer: UIEventSource<{
public readonly Location: UIEventSource<{ zoom: number, lat: number, lon: number }>;
public readonly LastClickLocation: UIEventSource<{ lat: number, lon: number }> = new UIEventSource<{ lat: number, lon: number }>(undefined)
private _previousLayer: L.tileLayer = undefined;
public readonly CurrentLayer: UIEventSource<{
id: string,
name: string,
layer: L.tileLayer

View file

@ -7,10 +7,10 @@ import {Basemap} from "./Basemap";
export class GeoLocationHandler extends UIElement {
private _isActive: UIEventSource<boolean> = new UIEventSource<boolean>(false);
private _permission: UIEventSource<string> = new UIEventSource<string>("");
private readonly _isActive: UIEventSource<boolean> = new UIEventSource<boolean>(false);
private readonly _permission: UIEventSource<string> = new UIEventSource<string>("");
private _marker: any;
private _hasLocation: UIEventSource<boolean>;
private readonly _hasLocation: UIEventSource<boolean>;
constructor() {
super(undefined);
@ -84,13 +84,13 @@ export class GeoLocationHandler extends UIElement {
}
if (this._hasLocation.data) {
return "<img src='assets/crosshair-blue.svg' alt='locate me'>";
return "<img src='./assets/crosshair-blue.svg' alt='locate me'>";
}
if (this._isActive.data) {
return "<img src='assets/crosshair-blue-center.svg' alt='locate me'>";
return "<img src='./assets/crosshair-blue-center.svg' alt='locate me'>";
}
return "<img src='assets/crosshair.svg' alt='locate me'>";
return "<img src='./assets/crosshair.svg' alt='locate me'>";
}

View file

@ -1,6 +1,4 @@
import {Basemap} from "./Basemap";
import L from "leaflet";
import {UIEventSource} from "../UIEventSource";
import {UIElement} from "../../UI/UIElement";
import {State} from "../../State";

View file

@ -2,11 +2,8 @@
* Handles all changes made to OSM.
* Needs an authenticator via OsmConnection
*/
import {UIEventSource} from "../UIEventSource";
import {OsmConnection} from "./OsmConnection";
import {OsmNode, OsmObject} from "./OsmObject";
import {And, Tag, TagsFilter} from "../TagsFilter";
import {ElementStorage} from "../ElementStorage";
import {And, Tag, TagsFilter} from "../Tags";
import {State} from "../../State";
import {Utils} from "../../Utils";
@ -163,6 +160,8 @@ export class Changes {
console.log("Beginning upload...");
// At last, we build the changeset and upload
State.state.osmConnection.UploadChangeset(
State.state.layoutToUse.data,
State.state.allElements,
function (csId) {
let modifications = "";
@ -190,11 +189,10 @@ export class Changes {
}
if (modifications.length > 0) {
changes +=
"<modify>" +
"<modify>\n" +
modifications +
"</modify>";
"\n</modify>";
}
changes += "</osmChange>";

View file

@ -1,6 +1,8 @@
import {State} from "../../State";
import {OsmConnection, UserDetails} from "./OsmConnection";
import {UIEventSource} from "../UIEventSource";
import {ElementStorage} from "../ElementStorage";
import {Layout} from "../../Customizations/Layout";
import {State} from "../../State";
export class ChangesetHandler {
@ -22,11 +24,14 @@ export class ChangesetHandler {
}
public UploadChangeset(generateChangeXML: (csid: string) => string,
continuation: () => void) {
public UploadChangeset(
layout: Layout,
allElements: ElementStorage,
generateChangeXML: (csid: string) => string,
continuation: () => void) {
if (this._dryRun) {
var changesetXML = generateChangeXML("123456");
const changesetXML = generateChangeXML("123456");
console.log(changesetXML);
continuation();
return;
@ -34,14 +39,14 @@ export class ChangesetHandler {
const self = this;
if (this.currentChangeset.data === undefined || this.currentChangeset.data === "") {
// We have to open a new changeset
this.OpenChangeset((csId) => {
this.OpenChangeset(layout,(csId) => {
this.currentChangeset.setData(csId);
const changeset = generateChangeXML(csId);
console.log(changeset);
self.AddChange(csId, changeset,
allElements,
() => {
},
(e) => {
@ -55,6 +60,7 @@ export class ChangesetHandler {
self.AddChange(
csId,
generateChangeXML(csId),
allElements,
() => {
},
(e) => {
@ -62,7 +68,7 @@ export class ChangesetHandler {
// Mark the CS as closed...
this.currentChangeset.setData("");
// ... and try again. As the cs is closed, no recursive loop can exist
self.UploadChangeset(generateChangeXML, continuation);
self.UploadChangeset(layout, allElements, generateChangeXML, continuation);
}
)
@ -71,9 +77,10 @@ export class ChangesetHandler {
}
private OpenChangeset(continuation: (changesetId: string) => void) {
private OpenChangeset(
layout : Layout,
continuation: (changesetId: string) => void) {
const layout = State.state.layoutToUse.data;
const commentExtra = layout.changesetMessage !== undefined? " - "+layout.changesetMessage : "";
this.auth.xhr({
method: 'PUT',
@ -81,8 +88,8 @@ export class ChangesetHandler {
options: {header: {'Content-Type': 'text/xml'}},
content: [`<osm><changeset>`,
`<tag k="created_by" v="MapComplete ${State.vNumber}" />`,
`<tag k="comment" v="Adding data with #MapComplete for theme #${layout.name}${commentExtra}"/>`,
`<tag k="theme" v="${layout.name}"/>`,
`<tag k="comment" v="Adding data with #MapComplete for theme #${layout.id}${commentExtra}"/>`,
`<tag k="theme" v="${layout.id}"/>`,
layout.maintainer !== undefined ? `<tag k="theme-creator" v="${layout.maintainer}"/>` : "",
`</changeset></osm>`].join("")
}, function (err, response) {
@ -98,6 +105,7 @@ export class ChangesetHandler {
private AddChange(changesetId: string,
changesetXML: string,
allElements: ElementStorage,
continuation: ((changesetId: string, idMapping: any) => void),
onFail: ((changesetId: string) => void) = undefined) {
this.auth.xhr({
@ -113,7 +121,7 @@ export class ChangesetHandler {
}
return;
}
const mapping = ChangesetHandler.parseUploadChangesetResponse(response);
const mapping = ChangesetHandler.parseUploadChangesetResponse(response, allElements);
console.log("Uploaded changeset ", changesetId);
continuation(changesetId, mapping);
});
@ -145,7 +153,7 @@ export class ChangesetHandler {
});
}
public static parseUploadChangesetResponse(response: XMLDocument) {
private static parseUploadChangesetResponse(response: XMLDocument, allElements: ElementStorage) {
const nodes = response.getElementsByTagName("node");
// @ts-ignore
for (const node of nodes) {
@ -157,9 +165,9 @@ export class ChangesetHandler {
continue;
}
console.log("Rewriting id: ", oldId, "-->", newId);
const element = State.state.allElements.getElement("node/" + oldId);
const element = allElements.getElement("node/" + oldId);
element.data.id = "node/" + newId;
State.state.allElements.addElementById("node/" + newId, element);
allElements.addElementById("node/" + newId, element);
element.ping();
}

View file

@ -1,10 +1,10 @@
// @ts-ignore
import osmAuth from "osm-auth";
import {UIEventSource} from "../UIEventSource";
import {State} from "../../State";
import {All} from "../../Customizations/Layouts/All";
import {OsmPreferences} from "./OsmPreferences";
import {ChangesetHandler} from "./ChangesetHandler";
import {Layout} from "../../Customizations/Layout";
import {ElementStorage} from "../ElementStorage";
export class UserDetails {
@ -32,8 +32,7 @@ export class OsmConnection {
constructor(dryRun: boolean, oauth_token: UIEventSource<string>,
// Used to keep multiple changesets open and to write to the correct changeset
layoutName: string,
singlePage: boolean = true,
useDevServer:boolean = false) {
singlePage: boolean = true) {
let pwaStandAloneMode = false;
try {
@ -55,7 +54,6 @@ export class OsmConnection {
oauth_secret: 'wDBRTCem0vxD7txrg1y6p5r8nvmz8tAhET7zDASI',
singlepage: false,
auto: true,
url: useDevServer ? "https://master.apis.dev.openstreetmap.org" : undefined
});
} else {
@ -65,7 +63,6 @@ export class OsmConnection {
singlepage: true,
landing: window.location.href,
auto: true,
url: useDevServer ? "https://master.apis.dev.openstreetmap.org" : undefined
});
}
@ -97,9 +94,12 @@ export class OsmConnection {
}
public UploadChangeset(generateChangeXML: (csid: string) => string,
public UploadChangeset(
layout: Layout,
allElements: ElementStorage,
generateChangeXML: (csid: string) => string,
continuation: () => void = () => {}) {
this.changesetHandler.UploadChangeset(generateChangeXML, continuation);
this.changesetHandler.UploadChangeset(layout, allElements, generateChangeXML, continuation);
}
public GetPreference(key: string, prefix: string = "mapcomplete-"): UIEventSource<string> {
@ -168,11 +168,12 @@ export class OsmConnection {
data.unreadMessages = parseInt(messages.getAttribute("unread"));
data.totalMessages = parseInt(messages.getAttribute("count"));
self.userDetails.ping();
for (const action of self._onLoggedIn) {
action(self.userDetails.data);
}
self._onLoggedIn = [];
self.userDetails.ping();
});
}

View file

@ -1,18 +1,16 @@
/**
* Helps in uplaoding, by generating the rigth title, decription and by adding the tag to the changeset
*/
import {Changes} from "./Changes";
import {UIEventSource} from "../UIEventSource";
import {ImageUploadFlow} from "../../UI/ImageUploadFlow";
import {UserDetails} from "./OsmConnection";
import {SlideShow} from "../../UI/SlideShow";
import {State} from "../../State";
import {Tag} from "../TagsFilter";
import {Tag} from "../Tags";
export class OsmImageUploadHandler {
private _tags: UIEventSource<any>;
private _slideShow: SlideShow;
private _preferedLicense: UIEventSource<string>;
private readonly _tags: UIEventSource<any>;
private readonly _slideShow: SlideShow;
private readonly _preferedLicense: UIEventSource<string>;
constructor(tags: UIEventSource<any>,
preferedLicense: UIEventSource<string>,

View file

@ -18,13 +18,21 @@ export abstract class OsmObject {
const splitted = id.split("/");
const type = splitted[0];
const idN = splitted[1];
const newContinuation = (element: OsmObject) => {
console.log("Received: ",element);
continuation(element);
}
switch (type) {
case("node"):
return new OsmNode(idN).Download(continuation);
return new OsmNode(idN).Download(newContinuation);
case("way"):
return new OsmWay(idN).Download(continuation);
return new OsmWay(idN).Download(newContinuation);
case("relation"):
return new OsmRelation(idN).Download(continuation);
return new OsmRelation(idN).Download(newContinuation);
}
}
@ -38,11 +46,9 @@ export abstract class OsmObject {
* @param string
* @constructor
*/
private Escape(string: string) {
while (string.indexOf('"') >= 0) {
string = string.replace('"', '&quot;');
}
return string;
private static Escape(string: string) {
return string.replace(/"/g, '&quot;')
.replace(/&/g, "&amp;");
}
/**
@ -54,7 +60,7 @@ export abstract class OsmObject {
for (const key in this.tags) {
const v = this.tags[key];
if (v !== "") {
tags += ' <tag k="' + this.Escape(key) + '" v="' + this.Escape(this.tags[key]) + '"/>\n'
tags += ' <tag k="' + OsmObject.Escape(key) + '" v="' + OsmObject.Escape(this.tags[key]) + '"/>\n'
}
}
return tags;

View file

@ -1,6 +1,5 @@
import {UIEventSource} from "../UIEventSource";
import {OsmConnection, UserDetails} from "./OsmConnection";
import {All} from "../../Customizations/Layouts/All";
import {Utils} from "../../Utils";
export class OsmPreferences {
@ -68,8 +67,6 @@ export class OsmPreferences {
}
if (l > 25) {
throw "Length to long";
source.setData(undefined);
return;
}
const prefsCount = Number(l);
let str = "";
@ -154,7 +151,7 @@ export class OsmPreferences {
method: 'DELETE',
path: '/api/0.6/user/preferences/' + k,
options: {header: {'Content-Type': 'text/plain'}},
}, function (error, result) {
}, function (error) {
if (error) {
console.log("Could not remove preference", error);
return;
@ -172,7 +169,7 @@ export class OsmPreferences {
path: '/api/0.6/user/preferences/' + k,
options: {header: {'Content-Type': 'text/plain'}},
content: v
}, function (error, result) {
}, function (error) {
if (error) {
console.log(`Could not set preference "${k}"'`, error);
return;

View file

@ -1,11 +1,11 @@
/**
* Interfaces overpass to get all the latest data
*/
import {Bounds} from "../Bounds";
import {TagsFilter} from "../TagsFilter";
import {TagsFilter} from "../Tags";
import $ from "jquery"
import * as OsmToGeoJson from "osmtogeojson";
/**
* Interfaces overpass to get all the latest data
*/
export class Overpass {
private _filter: TagsFilter
public static testUrl: string = null

View file

@ -6,14 +6,8 @@ import {AllKnownLayouts} from "../Customizations/AllKnownLayouts";
import Combine from "../UI/Base/Combine";
import {Img} from "../UI/Img";
import {CheckBox} from "../UI/Input/CheckBox";
import {VerticalCombine} from "../UI/Base/VerticalCombine";
import {FixedUiElement} from "../UI/Base/FixedUiElement";
import {SubtleButton} from "../UI/Base/SubtleButton";
import {PersonalLayout} from "./PersonalLayout";
import {All} from "../Customizations/Layouts/All";
import {Layout} from "../Customizations/Layout";
import {TagDependantUIElement} from "../Customizations/UIElementConstructor";
import {TagRendering} from "../Customizations/TagRendering";
export class PersonalLayersPanel extends UIElement {
private checkboxes: UIElement[] = [];
@ -22,7 +16,6 @@ export class PersonalLayersPanel extends UIElement {
super(State.state.favouriteLayers);
this.ListenTo(State.state.osmConnection.userDetails);
const t = Translations.t.favourite;
this.UpdateView([]);
const self = this;
@ -38,9 +31,9 @@ export class PersonalLayersPanel extends UIElement {
const favs = State.state.favouriteLayers.data ?? [];
const controls = new Map<string, UIEventSource<boolean>>();
const allLayouts = AllKnownLayouts.layoutsList.concat(extraThemes);
console.log("ALL LAYOUTS", allLayouts)
for (const layout of allLayouts) {
if (layout.name === PersonalLayout.NAME) {
if (layout.id === PersonalLayout.NAME) {
continue;
}
if (layout.hideFromOverview &&
@ -60,10 +53,9 @@ export class PersonalLayersPanel extends UIElement {
this.checkboxes.push(header);
for (const layer of layout.layers) {
let icon = layer.icon;
if (icon !== undefined && typeof (icon) !== "string") {
icon = icon.GetContent({"id": "node/-1"}) ?? "./assets/bug.svg";
icon = icon.GetContent({"id": "node/-1"}).txt ?? "./assets/bug.svg";
}
const image = (layer.icon ? `<img src='${layer.icon}'>` : Img.checkmark);
const noimage = (layer.icon ? `<img src='${layer.icon}'>` : Img.no_checkmark);

View file

@ -5,7 +5,7 @@ export abstract class TagsFilter {
abstract asOverpass(): string[]
abstract substituteValues(tags: any) : TagsFilter;
matchesProperties(properties: any) : boolean{
matchesProperties(properties: Map<string, string>): boolean {
return this.matches(TagUtils.proprtiesToKV(properties));
}
@ -13,80 +13,60 @@ export abstract class TagsFilter {
}
export class Regex extends TagsFilter {
private _k: string;
private _r: string;
export class RegexTag extends TagsFilter {
private readonly key: RegExp;
private readonly value: RegExp;
private readonly invert: boolean;
constructor(k: string, r: string) {
constructor(key: RegExp, value: RegExp, invert: boolean = false) {
super();
this._k = k;
this._r = r;
this.key = key;
this.value = value;
this.invert = invert;
}
asOverpass(): string[] {
return ["['" + this._k + "'~'" + this._r + "']"];
return [`['${this.key.source}'${this.invert ? "!" : ""}~'${this.value.source}']`];
}
matches(tags: { k: string; v: string }[]): boolean {
if(!(tags instanceof Array)){
throw "You used 'matches' on something that is not a list. Did you mean to use 'matchesProperties'?"
}
for (const tag of tags) {
if (tag.k === this._k) {
if (tag.v === "") {
// This tag has been removed
return false;
}
if (this._r === "*") {
// Any is allowed
return true;
}
const matchCount =tag.v.match(this._r)?.length;
return (matchCount ?? 0) > 0;
if (tag.k.match(this.key)) {
return tag.v.match(this.value) !== null;
}
}
return false;
}
substituteValues(tags: any) : TagsFilter{
throw "Substituting values is not supported on regex tags"
console.warn("Not substituting values on regex tags");
return this;
}
asHumanString() {
return this._k+"~="+this._r;
return `${this.key}${this.invert ? "!" : ""}~${this.value}`;
}
}
export class Tag extends TagsFilter {
public key: string
public value: string | RegExp
public invertValue: boolean
constructor(key: string | RegExp, value: string | RegExp, invertValue = false) {
if (value instanceof RegExp && invertValue) {
throw new Error("Unsupported combination: RegExp value and inverted value (use regex to invert the match)")
}
public value: string
constructor(key: string, value: string) {
super()
// @ts-ignore
this.key = key
// @ts-ignore
this.value = value
this.invertValue = invertValue
}
private static regexOrStrMatches(regexOrStr: string | RegExp, testStr: string) {
if (typeof regexOrStr === 'string') {
return regexOrStr === testStr
} else if (regexOrStr instanceof RegExp) {
return (regexOrStr as RegExp).test(testStr)
if(key === undefined || key === ""){
throw "Invalid key";
}
if(value === undefined){
throw "Invalid value";
}
if(value === undefined || value === "*"){
console.warn(`Got suspicious tag ${key}=* ; did you mean ${key}!~*`)
}
throw new Error("<regexOrStr> must be of type RegExp or string")
}
matches(tags: { k: string; v: string }[]): boolean {
@ -95,70 +75,36 @@ export class Tag extends TagsFilter {
}
for (const tag of tags) {
if (Tag.regexOrStrMatches(this.key, tag.k)) {
if (this.key == tag.k) {
if (tag.v === "") {
// This tag has been removed -> always matches false
return false;
}
if (this.value === "*") {
// Any is allowed (as long as the tag is not empty)
if (this.value === tag.v) {
return true;
}
if(this.value === tag.v){
return !this.invertValue;
}
return Tag.regexOrStrMatches(this.value, tag.v) !== this.invertValue
}
}
return this.invertValue
return false;
}
asOverpass(): string[] {
// @ts-ignore
const keyIsRegex = this.key instanceof RegExp
// @ts-ignore
const key = keyIsRegex ? (this.key as RegExp).source : this.key
// @ts-ignore
const valIsRegex = this.value instanceof RegExp
// @ts-ignore
const val = valIsRegex ? (this.value as RegExp).source : this.value
const regexKeyPrefix = keyIsRegex ? '~' : ''
const anyVal = this.value === "*"
if (anyVal && !keyIsRegex) {
return [`[${regexKeyPrefix}"${key}"]`];
}
if (this.value === "") {
// NOT having this key
return ['[!"' + key + '"]'];
return ['[!"' + this.key + '"]'];
}
const compareOperator = (valIsRegex || keyIsRegex) ? '~' : (this.invertValue ? '!=' : '=')
return [`[${regexKeyPrefix}"${key}"${compareOperator}"${keyIsRegex && anyVal ? '.' : val}"]`];
return [`["${this.key}"="${this.value}"]`];
}
substituteValues(tags: any) {
if (typeof this.value !== 'string') {
throw new Error("substituteValues() only possible with tag value of type string")
}
return new Tag(this.key, TagUtils.ApplyTemplate(this.value as string, tags));
}
asHumanString(linkToWiki: boolean, shorten: boolean) {
let v = ""
if (typeof (this.value) === "string") {
v = this.value;
} else {
// value is a regex
v = this.value.source;
}
let v = this.value;
if (shorten) {
v = Utils.EllipsesAfter(v, 25);
}
@ -167,26 +113,11 @@ export class Tag extends TagsFilter {
`=` +
`<a href='https://wiki.openstreetmap.org/wiki/Tag:${this.key}%3D${this.value}' target='_blank'>${v}</a>`
}
if (typeof (this.value) === "string") {
return this.key + (this.invertValue ? "!=": "=") + v;
}else{
// value is a regex
return this.key + "~=" + this.value.source;
}
return this.key + "=" + v;
}
}
export function anyValueExcept(key: string, exceptValue: string) {
return new And([
new Tag(key, "*"),
new Tag(key, exceptValue, true)
])
}
export class Or extends TagsFilter {
public or: TagsFilter[]
@ -248,8 +179,8 @@ export class And extends TagsFilter {
return true;
}
private combine(filter: string, choices: string[]): string[] {
var values = []
private static combine(filter: string, choices: string[]): string[] {
const values = [];
for (const or of choices) {
values.push(filter + or);
}
@ -257,19 +188,18 @@ export class And extends TagsFilter {
}
asOverpass(): string[] {
var allChoices: string[] = null;
let allChoices: string[] = null;
for (const andElement of this.and) {
var andElementFilter = andElement.asOverpass();
const andElementFilter = andElement.asOverpass();
if (allChoices === null) {
allChoices = andElementFilter;
continue;
}
var newChoices: string[] = []
for (var choice of allChoices) {
const newChoices: string[] = [];
for (const choice of allChoices) {
newChoices.push(
...this.combine(choice, andElementFilter)
...And.combine(choice, andElementFilter)
)
}
allChoices = newChoices;
@ -291,31 +221,6 @@ export class And extends TagsFilter {
}
export class Not extends TagsFilter{
private not: TagsFilter;
constructor(not: TagsFilter) {
super();
this.not = not;
}
asOverpass(): string[] {
throw "Not supported yet"
}
matches(tags: { k: string; v: string }[]): boolean {
return !this.not.matches(tags);
}
substituteValues(tags: any): TagsFilter {
return new Not(this.not.substituteValues(tags));
}
asHumanString(linkToWiki: boolean, shorten: boolean) {
return "!" + this.not.asHumanString(linkToWiki, shorten);
}
}
export class TagUtils {
static proprtiesToKV(properties: any): { k: string, v: string }[] {

View file

@ -43,7 +43,7 @@ export class Imgur {
const apiUrl = 'https://api.imgur.com/3/image/'+hash;
const apiKey = '7070e7167f0a25a';
var settings = {
const settings = {
async: true,
crossDomain: true,
processData: false,
@ -86,7 +86,7 @@ export class Imgur {
const apiUrl = 'https://api.imgur.com/3/image';
const apiKey = '7070e7167f0a25a';
var settings = {
const settings = {
async: true,
crossDomain: true,
processData: false,
@ -99,7 +99,7 @@ export class Imgur {
},
mimeType: 'multipart/form-data',
};
var formData = new FormData();
const formData = new FormData();
formData.append('image', blob);
formData.append("title", title);
formData.append("description", description)

View file

@ -23,7 +23,7 @@ export class Wikimedia {
"api.php?action=query&prop=imageinfo&iiprop=extmetadata&" +
"titles=" + filename +
"&format=json&origin=*";
$.getJSON(url, function (data, status) {
$.getJSON(url, function (data) {
const licenseInfo = new LicenseInfo();
const license = data.query.pages[-1].imageinfo[0].extmetadata;