forked from MapComplete/MapComplete
Huge refactorings of JSON-parsing and Tagsfilter, other cleanups, warning cleanups and lots of small subtle bugfixes
This commit is contained in:
parent
9a5b35b9f3
commit
a57b7d93fa
113 changed files with 1565 additions and 2594 deletions
|
@ -48,9 +48,4 @@ export class ElementStorage {
|
|||
}
|
||||
console.log("Can not find eventsource with id ", elementId);
|
||||
}
|
||||
|
||||
|
||||
removeId(oldId: string) {
|
||||
delete this._elements[oldId];
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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, []);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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'>";
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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>";
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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('"', '"');
|
||||
}
|
||||
return string;
|
||||
private static Escape(string: string) {
|
||||
return string.replace(/"/g, '"')
|
||||
.replace(/&/g, "&");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 }[] {
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue