More cleanup, first somewhat working version of #171
This commit is contained in:
parent
1f93923820
commit
bef684aec7
19 changed files with 439 additions and 135 deletions
|
@ -230,7 +230,7 @@ export default class GeoLocationHandler extends VariableUiElement {
|
||||||
navigator?.permissions
|
navigator?.permissions
|
||||||
?.query({name: "geolocation"})
|
?.query({name: "geolocation"})
|
||||||
?.then(function (status) {
|
?.then(function (status) {
|
||||||
console.log("Geolocation is already", status);
|
console.log("Geolocation permission is ", status.state);
|
||||||
if (status.state === "granted") {
|
if (status.state === "granted") {
|
||||||
self.StartGeolocating(forceZoom);
|
self.StartGeolocating(forceZoom);
|
||||||
}
|
}
|
||||||
|
@ -289,7 +289,6 @@ export default class GeoLocationHandler extends VariableUiElement {
|
||||||
|
|
||||||
private StartGeolocating(zoomToGPS = true) {
|
private StartGeolocating(zoomToGPS = true) {
|
||||||
const self = this;
|
const self = this;
|
||||||
console.log("Starting geolocation");
|
|
||||||
|
|
||||||
this._lastUserRequest = zoomToGPS ? new Date() : new Date(0);
|
this._lastUserRequest = zoomToGPS ? new Date() : new Date(0);
|
||||||
if (self._permission.data === "denied") {
|
if (self._permission.data === "denied") {
|
||||||
|
@ -301,8 +300,6 @@ export default class GeoLocationHandler extends VariableUiElement {
|
||||||
this.MoveToCurrentLoction(16);
|
this.MoveToCurrentLoction(16);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Searching location using GPS");
|
|
||||||
|
|
||||||
if (self._isActive.data) {
|
if (self._isActive.data) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,8 +184,8 @@ export default class OverpassFeatureSource implements FeatureSource, FeatureSour
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.runningQuery.setData(true);
|
this.runningQuery.setData(true);
|
||||||
overpass.queryGeoJson(queryBounds,
|
overpass.queryGeoJson(queryBounds).
|
||||||
function (data, date) {
|
then(([data, date]) => {
|
||||||
self._previousBounds.get(z).push(queryBounds);
|
self._previousBounds.get(z).push(queryBounds);
|
||||||
self.retries.setData(0);
|
self.retries.setData(0);
|
||||||
const features = data.features.map(f => ({feature: f, freshness: date}));
|
const features = data.features.map(f => ({feature: f, freshness: date}));
|
||||||
|
@ -197,12 +197,12 @@ export default class OverpassFeatureSource implements FeatureSource, FeatureSour
|
||||||
console.error("Got the overpass response, but could not process it: ", e, e.stack)
|
console.error("Got the overpass response, but could not process it: ", e, e.stack)
|
||||||
}
|
}
|
||||||
self.runningQuery.setData(false);
|
self.runningQuery.setData(false);
|
||||||
},
|
})
|
||||||
function (reason) {
|
.catch((reason) => {
|
||||||
self.retries.data++;
|
self.retries.data++;
|
||||||
self.ForceRefresh();
|
self.ForceRefresh();
|
||||||
self.timeout.setData(self.retries.data * 5);
|
self.timeout.setData(self.retries.data * 5);
|
||||||
console.error(`QUERY FAILED (retrying in ${5 * self.retries.data} sec) due to ${reason}`);
|
console.error(`QUERY FAILED (retrying in ${5 * self.retries.data} sec) due to`, reason);
|
||||||
self.retries.ping();
|
self.retries.ping();
|
||||||
self.runningQuery.setData(false);
|
self.runningQuery.setData(false);
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,6 @@ export default class TitleHandler {
|
||||||
constructor(state) {
|
constructor(state) {
|
||||||
const currentTitle: UIEventSource<string> = state.selectedElement.map(
|
const currentTitle: UIEventSource<string> = state.selectedElement.map(
|
||||||
selected => {
|
selected => {
|
||||||
console.log("UPdating title")
|
|
||||||
|
|
||||||
const layout = state.layoutToUse.data
|
const layout = state.layoutToUse.data
|
||||||
const defaultTitle = Translations.WT(layout?.title)?.txt ?? "MapComplete"
|
const defaultTitle = Translations.WT(layout?.title)?.txt ?? "MapComplete"
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ import {BBox} from "../GeoOperations";
|
||||||
import {TileHierarchyMerger} from "./TiledFeatureSource/TileHierarchyMerger";
|
import {TileHierarchyMerger} from "./TiledFeatureSource/TileHierarchyMerger";
|
||||||
import RelationsTracker from "../Osm/RelationsTracker";
|
import RelationsTracker from "../Osm/RelationsTracker";
|
||||||
import {NewGeometryFromChangesFeatureSource} from "./Sources/NewGeometryFromChangesFeatureSource";
|
import {NewGeometryFromChangesFeatureSource} from "./Sources/NewGeometryFromChangesFeatureSource";
|
||||||
import ChangeGeometryApplicator from "./ChangeApplicator";
|
import ChangeGeometryApplicator from "./Sources/ChangeGeometryApplicator";
|
||||||
|
|
||||||
|
|
||||||
export default class FeaturePipeline implements FeatureSourceState {
|
export default class FeaturePipeline implements FeatureSourceState {
|
||||||
|
@ -159,7 +159,6 @@ export default class FeaturePipeline implements FeatureSourceState {
|
||||||
|
|
||||||
// Whenever fresh data comes in, we need to update the metatagging
|
// Whenever fresh data comes in, we need to update the metatagging
|
||||||
self.newDataLoadedSignal.stabilized(1000).addCallback(src => {
|
self.newDataLoadedSignal.stabilized(1000).addCallback(src => {
|
||||||
console.log("Got an update from ", src.name)
|
|
||||||
self.updateAllMetaTagging()
|
self.updateAllMetaTagging()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -183,7 +182,6 @@ export default class FeaturePipeline implements FeatureSourceState {
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateAllMetaTagging() {
|
private updateAllMetaTagging() {
|
||||||
console.log("Updating the meta tagging")
|
|
||||||
const self = this;
|
const self = this;
|
||||||
this.perLayerHierarchy.forEach(hierarchy => {
|
this.perLayerHierarchy.forEach(hierarchy => {
|
||||||
hierarchy.loadedTiles.forEach(src => {
|
hierarchy.loadedTiles.forEach(src => {
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
import FeatureSource, {FeatureSourceForLayer, IndexedFeatureSource} from "./FeatureSource";
|
|
||||||
import {UIEventSource} from "../UIEventSource";
|
|
||||||
import {Changes} from "../Osm/Changes";
|
|
||||||
import {ChangeDescription, ChangeDescriptionTools} from "../Osm/Actions/ChangeDescription";
|
|
||||||
import {Utils} from "../../Utils";
|
|
||||||
import FilteredLayer from "../../Models/FilteredLayer";
|
|
||||||
import {OsmNode, OsmRelation, OsmWay} from "../Osm/OsmObject";
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies geometry changes from 'Changes' onto every feature of a featureSource
|
* Applies geometry changes from 'Changes' onto every feature of a featureSource
|
||||||
*/
|
*/
|
||||||
|
import {Changes} from "../../Osm/Changes";
|
||||||
|
import {UIEventSource} from "../../UIEventSource";
|
||||||
|
import {FeatureSourceForLayer, IndexedFeatureSource} from "../FeatureSource";
|
||||||
|
import FilteredLayer from "../../../Models/FilteredLayer";
|
||||||
|
import {ChangeDescription, ChangeDescriptionTools} from "../../Osm/Actions/ChangeDescription";
|
||||||
|
|
||||||
|
|
||||||
export default class ChangeGeometryApplicator implements FeatureSourceForLayer {
|
export default class ChangeGeometryApplicator implements FeatureSourceForLayer {
|
||||||
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<{ feature: any; freshness: Date }[]>([]);
|
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<{ feature: any; freshness: Date }[]>([]);
|
||||||
public readonly name: string;
|
public readonly name: string;
|
||||||
|
@ -76,6 +74,7 @@ export default class ChangeGeometryApplicator implements FeatureSourceForLayer {
|
||||||
// We only apply the last change as that one'll have the latest geometry
|
// We only apply the last change as that one'll have the latest geometry
|
||||||
const change = changesForFeature[changesForFeature.length - 1]
|
const change = changesForFeature[changesForFeature.length - 1]
|
||||||
copy.feature.geometry = ChangeDescriptionTools.getGeojsonGeometry(change)
|
copy.feature.geometry = ChangeDescriptionTools.getGeojsonGeometry(change)
|
||||||
|
console.log("Applying a geometry change onto ", feature, change, copy)
|
||||||
newFeatures.push(copy)
|
newFeatures.push(copy)
|
||||||
}
|
}
|
||||||
this.features.setData(newFeatures)
|
this.features.setData(newFeatures)
|
|
@ -27,7 +27,7 @@ export default class TiledFromLocalStorageSource implements TileHierarchy<Featur
|
||||||
return Number(key.substring(prefix.length));
|
return Number(key.substring(prefix.length));
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log("Avaible datasets in local storage:", indexes)
|
console.log("Layer", layer.layerDef.id, "has following tiles in available in localstorage", indexes.map(i => Utils.tile_from_index(i).join("/")).join(", "))
|
||||||
|
|
||||||
const zLevels = indexes.map(i => i % 100)
|
const zLevels = indexes.map(i => i % 100)
|
||||||
const indexesSet = new Set(indexes)
|
const indexesSet = new Set(indexes)
|
||||||
|
|
|
@ -15,13 +15,15 @@ export interface RelationSplitInput {
|
||||||
* When a way is split and this way is part of a relation, the relation should be updated too to have the new segment if relevant.
|
* When a way is split and this way is part of a relation, the relation should be updated too to have the new segment if relevant.
|
||||||
*/
|
*/
|
||||||
export default class RelationSplitHandler extends OsmChangeAction {
|
export default class RelationSplitHandler extends OsmChangeAction {
|
||||||
|
private readonly _input: RelationSplitInput;
|
||||||
|
|
||||||
constructor(input: RelationSplitInput) {
|
constructor(input: RelationSplitInput) {
|
||||||
super()
|
super()
|
||||||
|
this._input = input;
|
||||||
}
|
}
|
||||||
|
|
||||||
async CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]> {
|
async CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]> {
|
||||||
return [];
|
return new InPlaceReplacedmentRTSH(this._input).CreateChangeDescriptions(changes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,11 @@ export default class SplitAction extends OsmChangeAction {
|
||||||
private readonly wayId: string;
|
private readonly wayId: string;
|
||||||
private readonly _splitPointsCoordinates: [number, number] []// lon, lat
|
private readonly _splitPointsCoordinates: [number, number] []// lon, lat
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param wayId
|
||||||
|
* @param splitPointCoordinates: lon, lat
|
||||||
|
*/
|
||||||
constructor(wayId: string, splitPointCoordinates: [number, number][]) {
|
constructor(wayId: string, splitPointCoordinates: [number, number][]) {
|
||||||
super()
|
super()
|
||||||
this.wayId = wayId;
|
this.wayId = wayId;
|
||||||
|
@ -39,12 +44,11 @@ export default class SplitAction extends OsmChangeAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
async CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]> {
|
async CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]> {
|
||||||
const self = this;
|
|
||||||
const originalElement = <OsmWay> await OsmObject.DownloadObjectAsync(this.wayId)
|
const originalElement = <OsmWay> await OsmObject.DownloadObjectAsync(this.wayId)
|
||||||
const originalNodes = originalElement.nodes;
|
const originalNodes = originalElement.nodes;
|
||||||
|
|
||||||
// First, calculate splitpoints and remove points close to one another
|
// First, calculate splitpoints and remove points close to one another
|
||||||
const splitInfo = self.CalculateSplitCoordinates(originalElement)
|
const splitInfo = this.CalculateSplitCoordinates(originalElement)
|
||||||
// Now we have a list with e.g.
|
// Now we have a list with e.g.
|
||||||
// [ { originalIndex: 0}, {originalIndex: 1, doSplit: true}, {originalIndex: 2}, {originalIndex: undefined, doSplit: true}, {originalIndex: 3}]
|
// [ { originalIndex: 0}, {originalIndex: 1, doSplit: true}, {originalIndex: 2}, {originalIndex: undefined, doSplit: true}, {originalIndex: 3}]
|
||||||
|
|
||||||
|
@ -166,8 +170,9 @@ export default class SplitAction extends OsmChangeAction {
|
||||||
private CalculateSplitCoordinates(osmWay: OsmWay, toleranceInM = 5): SplitInfo[] {
|
private CalculateSplitCoordinates(osmWay: OsmWay, toleranceInM = 5): SplitInfo[] {
|
||||||
const wayGeoJson = osmWay.asGeoJson()
|
const wayGeoJson = osmWay.asGeoJson()
|
||||||
// Should be [lon, lat][]
|
// Should be [lon, lat][]
|
||||||
const originalPoints = osmWay.coordinates.map(c => <[number, number]>c.reverse())
|
const originalPoints : [number, number][] = osmWay.coordinates.map(c => [c[1], c[0]])
|
||||||
const allPoints: {
|
const allPoints: {
|
||||||
|
// lon, lat
|
||||||
coordinates: [number, number],
|
coordinates: [number, number],
|
||||||
isSplitPoint: boolean,
|
isSplitPoint: boolean,
|
||||||
originalIndex?: number, // Original index
|
originalIndex?: number, // Original index
|
||||||
|
@ -180,6 +185,7 @@ export default class SplitAction extends OsmChangeAction {
|
||||||
// - `dist`: distance between pt and the closest point,
|
// - `dist`: distance between pt and the closest point,
|
||||||
// `location`: distance along the line between start and the closest point.
|
// `location`: distance along the line between start and the closest point.
|
||||||
let projected = GeoOperations.nearestPoint(wayGeoJson, c)
|
let projected = GeoOperations.nearestPoint(wayGeoJson, c)
|
||||||
|
// c is lon lat
|
||||||
return ({
|
return ({
|
||||||
coordinates: c,
|
coordinates: c,
|
||||||
isSplitPoint: true,
|
isSplitPoint: true,
|
||||||
|
@ -233,8 +239,12 @@ export default class SplitAction extends OsmChangeAction {
|
||||||
if (distToNext > distToPrev) {
|
if (distToNext > distToPrev) {
|
||||||
closest = prevPoint
|
closest = prevPoint
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ok, we have a closest point!
|
// Ok, we have a closest point!
|
||||||
|
|
||||||
|
if(closest.originalIndex === 0 || closest.originalIndex === originalPoints.length){
|
||||||
|
// We can not split on the first or last points...
|
||||||
|
continue
|
||||||
|
}
|
||||||
closest.isSplitPoint = true;
|
closest.isSplitPoint = true;
|
||||||
allPoints.splice(i, 1)
|
allPoints.splice(i, 1)
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ import {LocalStorageSource} from "../Web/LocalStorageSource";
|
||||||
export class Changes {
|
export class Changes {
|
||||||
|
|
||||||
|
|
||||||
private static _nextId = -1; // Newly assigned ID's are negative
|
private _nextId : number = -1; // Newly assigned ID's are negative
|
||||||
public readonly name = "Newly added features"
|
public readonly name = "Newly added features"
|
||||||
/**
|
/**
|
||||||
* All the newly created features as featureSource + all the modified features
|
* All the newly created features as featureSource + all the modified features
|
||||||
|
@ -30,6 +30,8 @@ export class Changes {
|
||||||
constructor() {
|
constructor() {
|
||||||
// We keep track of all changes just as well
|
// We keep track of all changes just as well
|
||||||
this.allChanges.setData([...this.pendingChanges.data])
|
this.allChanges.setData([...this.pendingChanges.data])
|
||||||
|
// If a pending change contains a negative ID, we save that
|
||||||
|
this._nextId = Math.min(-1, ...this.pendingChanges.data?.map(pch => pch.id) ?? [])
|
||||||
}
|
}
|
||||||
|
|
||||||
private static createChangesetFor(csId: string,
|
private static createChangesetFor(csId: string,
|
||||||
|
@ -77,7 +79,7 @@ export class Changes {
|
||||||
* Returns a new ID and updates the value for the next ID
|
* Returns a new ID and updates the value for the next ID
|
||||||
*/
|
*/
|
||||||
public getNewID() {
|
public getNewID() {
|
||||||
return Changes._nextId--;
|
return this._nextId--;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -63,17 +63,26 @@ export abstract class OsmObject {
|
||||||
if (idN < 0) {
|
if (idN < 0) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
switch (type) {
|
|
||||||
case("node"):
|
|
||||||
return await new OsmNode(idN).Download();
|
|
||||||
case("way"):
|
|
||||||
return await new OsmWay(idN).Download();
|
|
||||||
case("relation"):
|
|
||||||
return await new OsmRelation(idN).Download();
|
|
||||||
default:
|
|
||||||
throw ("Invalid object type:" + type + id);
|
|
||||||
|
|
||||||
|
const full = !id.startsWith("way") ? "" : "/full";
|
||||||
|
const url = `${OsmObject.backendURL}api/0.6/${id}${full}`;
|
||||||
|
const rawData = await Utils.downloadJson(url)
|
||||||
|
// A full query might contain more then just the requested object (e.g. nodes that are part of a way, where we only want the way)
|
||||||
|
const parsed = OsmObject.ParseObjects(rawData.elements);
|
||||||
|
// Lets fetch the object we need
|
||||||
|
for (const osmObject of parsed) {
|
||||||
|
if(osmObject.type !== type){
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
if(osmObject.id !== idN){
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Found the one!
|
||||||
|
return osmObject
|
||||||
|
}
|
||||||
|
throw "PANIC: requested object is not part of the response"
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -205,6 +214,7 @@ export abstract class OsmObject {
|
||||||
private static ParseObjects(elements: any[]): OsmObject[] {
|
private static ParseObjects(elements: any[]): OsmObject[] {
|
||||||
const objects: OsmObject[] = [];
|
const objects: OsmObject[] = [];
|
||||||
const allNodes: Map<number, OsmNode> = new Map<number, OsmNode>()
|
const allNodes: Map<number, OsmNode> = new Map<number, OsmNode>()
|
||||||
|
|
||||||
for (const element of elements) {
|
for (const element of elements) {
|
||||||
const type = element.type;
|
const type = element.type;
|
||||||
const idN = element.id;
|
const idN = element.id;
|
||||||
|
@ -226,6 +236,11 @@ export abstract class OsmObject {
|
||||||
osmObject.SaveExtraData(element, [])
|
osmObject.SaveExtraData(element, [])
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (osmObject !== undefined && OsmObject.backendURL !== OsmObject.defaultBackend) {
|
||||||
|
osmObject.tags["_backend"] = OsmObject.backendURL
|
||||||
|
}
|
||||||
|
|
||||||
osmObject?.LoadData(element)
|
osmObject?.LoadData(element)
|
||||||
objects.push(osmObject)
|
objects.push(osmObject)
|
||||||
}
|
}
|
||||||
|
@ -237,7 +252,7 @@ export abstract class OsmObject {
|
||||||
|
|
||||||
public abstract asGeoJson(): any;
|
public abstract asGeoJson(): any;
|
||||||
|
|
||||||
abstract SaveExtraData(element: any, allElements: any[]);
|
abstract SaveExtraData(element: any, allElements: OsmObject[]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates the changeset-XML for tags
|
* Generates the changeset-XML for tags
|
||||||
|
@ -260,37 +275,6 @@ export abstract class OsmObject {
|
||||||
return tags;
|
return tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Downloads the object, a full download for ways and relations
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
async Download(): Promise<OsmObject> {
|
|
||||||
const self = this;
|
|
||||||
const full = this.type !== "way" ? "" : "/full";
|
|
||||||
const url = `${OsmObject.backendURL}api/0.6/${this.type}/${this.id}${full}`;
|
|
||||||
return await Utils.downloadJson(url).then(data => {
|
|
||||||
const element = data.elements.pop();
|
|
||||||
let nodes = []
|
|
||||||
if (self.type === "way" && data.elements.length >= 0) {
|
|
||||||
nodes = OsmObject.ParseObjects(data.elements)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.type === "rellation") {
|
|
||||||
throw "We should save some extra data"
|
|
||||||
}
|
|
||||||
|
|
||||||
self.LoadData(element)
|
|
||||||
self.SaveExtraData(element, nodes);
|
|
||||||
|
|
||||||
if (OsmObject.backendURL !== OsmObject.defaultBackend) {
|
|
||||||
self.tags["_backend"] = OsmObject.backendURL
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
abstract ChangesetXML(changesetId: string): string;
|
abstract ChangesetXML(changesetId: string): string;
|
||||||
|
|
||||||
protected VersionXML() {
|
protected VersionXML() {
|
||||||
|
@ -363,7 +347,7 @@ export class OsmNode extends OsmObject {
|
||||||
|
|
||||||
export class OsmWay extends OsmObject {
|
export class OsmWay extends OsmObject {
|
||||||
|
|
||||||
nodes: number[];
|
nodes: number[] = [];
|
||||||
// The coordinates of the way, [lat, lon][]
|
// The coordinates of the way, [lat, lon][]
|
||||||
coordinates: [number, number][] = []
|
coordinates: [number, number][] = []
|
||||||
lat: number;
|
lat: number;
|
||||||
|
@ -400,6 +384,10 @@ export class OsmWay extends OsmObject {
|
||||||
nodeDict.set(node.id, node)
|
nodeDict.set(node.id, node)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (element.nodes === undefined) {
|
||||||
|
console.log("PANIC")
|
||||||
|
}
|
||||||
|
|
||||||
for (const nodeId of element.nodes) {
|
for (const nodeId of element.nodes) {
|
||||||
const node = nodeDict.get(nodeId)
|
const node = nodeDict.get(nodeId)
|
||||||
if (node === undefined) {
|
if (node === undefined) {
|
||||||
|
@ -419,7 +407,7 @@ export class OsmWay extends OsmObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
public asGeoJson() {
|
public asGeoJson() {
|
||||||
let coordinates : ([number, number][] | [number, number][][]) = this.coordinates.map(c => <[number, number]>c.reverse());
|
let coordinates: ([number, number][] | [number, number][][]) = this.coordinates.map(c => [c[1], c[0]]);
|
||||||
if (this.isPolygon()) {
|
if (this.isPolygon()) {
|
||||||
coordinates = [coordinates]
|
coordinates = [coordinates]
|
||||||
}
|
}
|
||||||
|
|
|
@ -145,14 +145,14 @@ export class OsmPreferences {
|
||||||
|
|
||||||
private SetPreference(k: string, v: string) {
|
private SetPreference(k: string, v: string) {
|
||||||
if (!this.userDetails.data.loggedIn) {
|
if (!this.userDetails.data.loggedIn) {
|
||||||
console.log(`Not saving preference ${k}: user not logged in`);
|
console.debug(`Not saving preference ${k}: user not logged in`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.preferences.data[k] === v) {
|
if (this.preferences.data[k] === v) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log("Updating preference", k, " to ", Utils.EllipsesAfter(v, 15));
|
console.debug("Updating preference", k, " to ", Utils.EllipsesAfter(v, 15));
|
||||||
|
|
||||||
if (v === undefined || v === "") {
|
if (v === undefined || v === "") {
|
||||||
this.auth.xhr({
|
this.auth.xhr({
|
||||||
|
@ -161,10 +161,10 @@ export class OsmPreferences {
|
||||||
options: {header: {'Content-Type': 'text/plain'}},
|
options: {header: {'Content-Type': 'text/plain'}},
|
||||||
}, function (error) {
|
}, function (error) {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.log("Could not remove preference", error);
|
console.warn("Could not remove preference", error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log("Preference ", k, "removed!");
|
console.debug("Preference ", k, "removed!");
|
||||||
|
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -31,7 +31,7 @@ export class Overpass {
|
||||||
this._relationTracker = relationTracker
|
this._relationTracker = relationTracker
|
||||||
}
|
}
|
||||||
|
|
||||||
queryGeoJson(bounds: Bounds, continuation: ((any, date: Date) => void), onFail: ((reason) => void)): void {
|
public async queryGeoJson(bounds: Bounds): Promise<[any, Date]> {
|
||||||
|
|
||||||
let query = this.buildQuery("[bbox:" + bounds.south + "," + bounds.west + "," + bounds.north + "," + bounds.east + "]")
|
let query = this.buildQuery("[bbox:" + bounds.south + "," + bounds.west + "," + bounds.north + "," + bounds.east + "]")
|
||||||
|
|
||||||
|
@ -40,24 +40,18 @@ export class Overpass {
|
||||||
query = Overpass.testUrl;
|
query = Overpass.testUrl;
|
||||||
}
|
}
|
||||||
const self = this;
|
const self = this;
|
||||||
Utils.downloadJson(query)
|
const json = await Utils.downloadJson(query)
|
||||||
.then(json => {
|
|
||||||
if (json.elements === [] && ((json.remarks ?? json.remark).indexOf("runtime error") >= 0)) {
|
if (json.elements === [] && ((json.remarks ?? json.remark).indexOf("runtime error") >= 0)) {
|
||||||
console.log("Timeout or other runtime error");
|
console.log("Timeout or other runtime error");
|
||||||
onFail("Runtime error (timeout)")
|
throw("Runtime error (timeout)")
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
self._relationTracker.RegisterRelations(json)
|
self._relationTracker.RegisterRelations(json)
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const geojson = OsmToGeoJson.default(json);
|
const geojson = OsmToGeoJson.default(json);
|
||||||
const osmTime = new Date(json.osm3s.timestamp_osm_base);
|
const osmTime = new Date(json.osm3s.timestamp_osm_base);
|
||||||
|
return [geojson, osmTime];
|
||||||
continuation(geojson, osmTime);
|
|
||||||
}).catch(e => {
|
|
||||||
onFail(e);
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildQuery(bbox: string): string {
|
buildQuery(bbox: string): string {
|
||||||
|
|
|
@ -100,7 +100,7 @@ export interface LayoutConfigJson {
|
||||||
* However, users tend to pan and zoom a lot. It is pretty annoying if every single pan means a reloading of the data.
|
* However, users tend to pan and zoom a lot. It is pretty annoying if every single pan means a reloading of the data.
|
||||||
* For this, the bounds are widened in order to make a small pan still within bounds of the loaded data.
|
* For this, the bounds are widened in order to make a small pan still within bounds of the loaded data.
|
||||||
*
|
*
|
||||||
* IF widenfactor is 0, this feature is disabled. A recommended value is between 0.5 and 0.01 (the latter for very dense queries)
|
* IF widenfactor is 1, this feature is disabled. A recommended value is between 1 and 3
|
||||||
*/
|
*/
|
||||||
widenFactor?: number;
|
widenFactor?: number;
|
||||||
|
|
||||||
|
|
|
@ -87,7 +87,7 @@ export default class LayoutConfig {
|
||||||
this.startZoom = json.startZoom;
|
this.startZoom = json.startZoom;
|
||||||
this.startLat = json.startLat;
|
this.startLat = json.startLat;
|
||||||
this.startLon = json.startLon;
|
this.startLon = json.startLon;
|
||||||
this.widenFactor = json.widenFactor ?? 0.05;
|
this.widenFactor = json.widenFactor ?? 1.5;
|
||||||
this.roamingRenderings = (json.roamingRenderings ?? []).map((tr, i) => {
|
this.roamingRenderings = (json.roamingRenderings ?? []).map((tr, i) => {
|
||||||
if (typeof tr === "string") {
|
if (typeof tr === "string") {
|
||||||
if (SharedTagRenderings.SharedTagRendering.get(tr) !== undefined) {
|
if (SharedTagRenderings.SharedTagRendering.get(tr) !== undefined) {
|
||||||
|
|
22
Utils.ts
22
Utils.ts
|
@ -356,18 +356,30 @@ export class Utils {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static injectedDownloads = {}
|
||||||
|
|
||||||
|
public static injectJsonDownloadForTests(url: string, data) {
|
||||||
|
Utils.injectedDownloads[url] = data
|
||||||
|
}
|
||||||
|
|
||||||
public static downloadJson(url: string): Promise<any> {
|
public static downloadJson(url: string): Promise<any> {
|
||||||
|
|
||||||
|
const injected = Utils.injectedDownloads[url]
|
||||||
|
if (injected !== undefined) {
|
||||||
|
console.log("Using injected resource for test for URL", url)
|
||||||
|
return new Promise((resolve, _) => resolve(injected))
|
||||||
|
}
|
||||||
|
|
||||||
if (this.externalDownloadFunction !== undefined) {
|
if (this.externalDownloadFunction !== undefined) {
|
||||||
return this.externalDownloadFunction(url)
|
return this.externalDownloadFunction(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise(
|
return new Promise((resolve, reject) => {
|
||||||
(resolve, reject) => {
|
|
||||||
try {
|
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
xhr.onload = () => {
|
xhr.onload = () => {
|
||||||
if (xhr.status == 200) {
|
if (xhr.status == 200) {
|
||||||
try {
|
try {
|
||||||
|
console.log("Got a response! Parsing now...")
|
||||||
resolve(JSON.parse(xhr.response))
|
resolve(JSON.parse(xhr.response))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject("Not a valid json: " + xhr.response)
|
reject("Not a valid json: " + xhr.response)
|
||||||
|
@ -379,12 +391,8 @@ export class Utils {
|
||||||
xhr.open('GET', url);
|
xhr.open('GET', url);
|
||||||
xhr.setRequestHeader("accept", "application/json")
|
xhr.setRequestHeader("accept", "application/json")
|
||||||
xhr.send();
|
xhr.send();
|
||||||
} catch (e) {
|
|
||||||
reject(e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
"startZoom": 14,
|
"startZoom": 14,
|
||||||
"startLon": 3.2228,
|
"startLon": 3.2228,
|
||||||
"maintainer": "MapComplete",
|
"maintainer": "MapComplete",
|
||||||
"widenfactor": 0.01,
|
"widenfactor": 2,
|
||||||
"roamingRenderings": [
|
"roamingRenderings": [
|
||||||
{
|
{
|
||||||
"question": {
|
"question": {
|
||||||
|
|
|
@ -3,6 +3,7 @@ import {InPlaceReplacedmentRTSH} from "../Logic/Osm/Actions/RelationSplitHandler
|
||||||
import {OsmObject, OsmRelation} from "../Logic/Osm/OsmObject";
|
import {OsmObject, OsmRelation} from "../Logic/Osm/OsmObject";
|
||||||
import {Changes} from "../Logic/Osm/Changes";
|
import {Changes} from "../Logic/Osm/Changes";
|
||||||
import {equal} from "assert";
|
import {equal} from "assert";
|
||||||
|
import {Utils} from "../Utils";
|
||||||
|
|
||||||
export default class RelationSplitHandlerSpec extends T {
|
export default class RelationSplitHandlerSpec extends T {
|
||||||
|
|
||||||
|
@ -58,6 +59,28 @@ export default class RelationSplitHandlerSpec extends T {
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
||||||
|
Utils.injectJsonDownloadForTests(
|
||||||
|
"https://www.openstreetmap.org/api/0.6/node/1124134958/ways",
|
||||||
|
{"version":"0.6","generator":"CGImap 0.8.5 (2937646 spike-07.openstreetmap.org)","copyright":"OpenStreetMap and contributors","attribution":"http://www.openstreetmap.org/copyright","license":"http://opendatacommons.org/licenses/odbl/1-0/","elements":[{"type":"way","id":97038428,"timestamp":"2019-06-19T12:26:24Z","version":6,"changeset":71399984,"user":"Pieter Vander Vennet","uid":3818858,"nodes":[1124134958,323729212,323729351,2542460408,187073405],"tags":{"highway":"residential","name":"Brugs-Kerkhofstraat","sett:pattern":"arc","surface":"sett"}},{"type":"way","id":97038434,"timestamp":"2019-06-19T12:26:24Z","version":5,"changeset":71399984,"user":"Pieter Vander Vennet","uid":3818858,"nodes":[1124134958,1124135024,187058607],"tags":{"bicycle":"use_sidepath","highway":"residential","name":"Kerkhofblommenstraat","sett:pattern":"arc","surface":"sett"}},{"type":"way","id":97038435,"timestamp":"2017-12-21T21:41:08Z","version":4,"changeset":54826837,"user":"Jakka","uid":2403313,"nodes":[1124134958,2576628889,1124135035,5298371485,5298371495],"tags":{"bicycle":"use_sidepath","highway":"residential","name":"Kerkhofblommenstraat"}},{"type":"way","id":251446313,"timestamp":"2019-01-07T19:22:47Z","version":4,"changeset":66106872,"user":"M!dgard","uid":763799,"nodes":[1124134958,5243143198,4555715455],"tags":{"foot":"yes","highway":"service"}}]}
|
||||||
|
)
|
||||||
|
|
||||||
|
Utils.injectJsonDownloadForTests(
|
||||||
|
"https://www.openstreetmap.org/api/0.6/relation/9572808",
|
||||||
|
{"version":"0.6","generator":"CGImap 0.8.5 (3128319 spike-07.openstreetmap.org)","copyright":"OpenStreetMap and contributors","attribution":"http://www.openstreetmap.org/copyright","license":"http://opendatacommons.org/licenses/odbl/1-0/","elements":[{"type":"relation","id":9572808,"timestamp":"2021-08-12T12:44:06Z","version":11,"changeset":109573204,"user":"A67-A67","uid":553736,"members":[{"type":"way","ref":173662702,"role":""},{"type":"way","ref":467606230,"role":""},{"type":"way","ref":126267167,"role":""},{"type":"way","ref":301897426,"role":""},{"type":"way","ref":687866206,"role":""},{"type":"way","ref":295132739,"role":""},{"type":"way","ref":690497698,"role":""},{"type":"way","ref":627893684,"role":""},{"type":"way","ref":295132741,"role":""},{"type":"way","ref":301903120,"role":""},{"type":"way","ref":672541156,"role":""},{"type":"way","ref":126264330,"role":""},{"type":"way","ref":280440853,"role":""},{"type":"way","ref":838499667,"role":""},{"type":"way","ref":838499663,"role":""},{"type":"way","ref":690497623,"role":""},{"type":"way","ref":301902946,"role":""},{"type":"way","ref":280460715,"role":""},{"type":"way","ref":972534369,"role":""},{"type":"way","ref":695680702,"role":""},{"type":"way","ref":690497860,"role":""},{"type":"way","ref":295410363,"role":""},{"type":"way","ref":823864063,"role":""},{"type":"way","ref":663172088,"role":""},{"type":"way","ref":659950322,"role":""},{"type":"way","ref":659950323,"role":""},{"type":"way","ref":230180094,"role":""},{"type":"way","ref":690497912,"role":""},{"type":"way","ref":39588765,"role":""}],"tags":{"distance":"13 km","name":"Abdijenroute","network":"lcn","old_name":"Spoorlijn 58","operator":"Toerisme West-Vlaanderen","railway":"abandoned","route":"bicycle","type":"route","wikipedia":"nl:Spoorlijn 58"}}]}
|
||||||
|
)
|
||||||
|
|
||||||
|
Utils.injectJsonDownloadForTests(
|
||||||
|
"https://www.openstreetmap.org/api/0.6/way/687866206/full",
|
||||||
|
{"version":"0.6","generator":"CGImap 0.8.5 (2601512 spike-07.openstreetmap.org)","copyright":"OpenStreetMap and contributors","attribution":"http://www.openstreetmap.org/copyright","license":"http://opendatacommons.org/licenses/odbl/1-0/","elements":[{"type":"node","id":5273988959,"lat":51.1811406,"lon":3.2427712,"timestamp":"2021-07-29T21:14:53Z","version":6,"changeset":108847202,"user":"kaart_fietser","uid":11022240,"tags":{"network:type":"node_network","rwn_ref":"32"}},{"type":"node","id":6448669326,"lat":51.1811346,"lon":3.242891,"timestamp":"2019-05-04T22:44:12Z","version":1,"changeset":69891295,"user":"Pieter Vander Vennet","uid":3818858,"tags":{"barrier":"bollard"}},{"type":"way","id":687866206,"timestamp":"2019-05-06T20:52:20Z","version":2,"changeset":69951497,"user":"noelbov","uid":8054928,"nodes":[6448669326,5273988959],"tags":{"highway":"cycleway","name":"Abdijenroute","railway":"abandoned","surface":"asphalt"}}]}
|
||||||
|
)
|
||||||
|
|
||||||
|
Utils.injectJsonDownloadForTests(
|
||||||
|
"https://www.openstreetmap.org/api/0.6/way/690497698/full" ,
|
||||||
|
{"version":"0.6","generator":"CGImap 0.8.5 (3023311 spike-07.openstreetmap.org)","copyright":"OpenStreetMap and contributors","attribution":"http://www.openstreetmap.org/copyright","license":"http://opendatacommons.org/licenses/odbl/1-0/","elements":[{"type":"node","id":170497152,"lat":51.1832353,"lon":3.2498759,"timestamp":"2018-04-24T00:29:37Z","version":7,"changeset":58357376,"user":"Pieter Vander Vennet","uid":3818858},{"type":"node","id":2988218625,"lat":51.1835053,"lon":3.2503067,"timestamp":"2018-09-24T21:48:46Z","version":2,"changeset":62895918,"user":"A67-A67","uid":553736},{"type":"node","id":5273988967,"lat":51.182659,"lon":3.249004,"timestamp":"2017-12-09T18:40:21Z","version":1,"changeset":54493533,"user":"CacherB","uid":1999108},{"type":"way","id":690497698,"timestamp":"2021-07-29T21:14:53Z","version":3,"changeset":108847202,"user":"kaart_fietser","uid":11022240,"nodes":[2988218625,170497152,5273988967],"tags":{"highway":"cycleway","lit":"no","name":"Abdijenroute","oneway":"no","railway":"abandoned","surface":"compacted"}}]}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
super("relationsplithandler", [
|
super("relationsplithandler", [
|
||||||
["split 295132739",
|
["split 295132739",
|
||||||
() => RelationSplitHandlerSpec.split().then(_ => console.log("OK"))]
|
() => RelationSplitHandlerSpec.split().then(_ => console.log("OK"))]
|
||||||
|
|
274
test/SplitAction.spec.ts
Normal file
274
test/SplitAction.spec.ts
Normal file
File diff suppressed because one or more lines are too long
|
@ -8,7 +8,8 @@ import OsmObjectSpec from "./OsmObject.spec";
|
||||||
import ScriptUtils from "../scripts/ScriptUtils";
|
import ScriptUtils from "../scripts/ScriptUtils";
|
||||||
import UnitsSpec from "./Units.spec";
|
import UnitsSpec from "./Units.spec";
|
||||||
import RelationSplitHandlerSpec from "./RelationSplitHandler.spec";
|
import RelationSplitHandlerSpec from "./RelationSplitHandler.spec";
|
||||||
|
import SplitActionSpec from "./SplitAction.spec";
|
||||||
|
import {Utils} from "../Utils";
|
||||||
|
|
||||||
|
|
||||||
ScriptUtils.fixUtils()
|
ScriptUtils.fixUtils()
|
||||||
|
@ -21,9 +22,19 @@ const allTests = [
|
||||||
new ThemeSpec(),
|
new ThemeSpec(),
|
||||||
new UtilsSpec(),
|
new UtilsSpec(),
|
||||||
new UnitsSpec(),
|
new UnitsSpec(),
|
||||||
new RelationSplitHandlerSpec()
|
new RelationSplitHandlerSpec(),
|
||||||
|
new SplitActionSpec()
|
||||||
]
|
]
|
||||||
|
|
||||||
|
Utils.externalDownloadFunction = async (url) => {
|
||||||
|
console.error("Fetching ", url, "blocked in tests, use Utils.injectJsonDownloadForTests")
|
||||||
|
const data = await ScriptUtils.DownloadJSON(url)
|
||||||
|
console.log("\n\n ----------- \nBLOCKED DATA\n Utils.injectJsonDownloadForTests(\n" +
|
||||||
|
" ", JSON.stringify(url),", \n",
|
||||||
|
" ", JSON.stringify(data), "\n )\n------------------\n\n")
|
||||||
|
throw "Detected internet access for URL " + url + ", please inject it with Utils.injectJsonDownloadForTests"
|
||||||
|
}
|
||||||
|
|
||||||
let args = [...process.argv]
|
let args = [...process.argv]
|
||||||
args.splice(0, 2)
|
args.splice(0, 2)
|
||||||
args = args.map(a => a.toLowerCase())
|
args = args.map(a => a.toLowerCase())
|
||||||
|
@ -40,9 +51,9 @@ if(testsToRun.length == 0){
|
||||||
|
|
||||||
for (let i = 0; i < testsToRun.length; i++) {
|
for (let i = 0; i < testsToRun.length; i++) {
|
||||||
const test = testsToRun[i];
|
const test = testsToRun[i];
|
||||||
ScriptUtils.erasableLog(" Running test", i, "/", allTests.length)
|
console.log(" Running test", i, "/", allTests.length, test.name)
|
||||||
allFailures.push(...(test.Run() ?? []))
|
allFailures.push(...(test.Run() ?? []))
|
||||||
|
console.log("OK!")
|
||||||
}
|
}
|
||||||
if (allFailures.length > 0) {
|
if (allFailures.length > 0) {
|
||||||
for (const failure of allFailures) {
|
for (const failure of allFailures) {
|
||||||
|
@ -50,4 +61,4 @@ if (allFailures.length > 0) {
|
||||||
}
|
}
|
||||||
throw "Some test failed"
|
throw "Some test failed"
|
||||||
}
|
}
|
||||||
console.log("All tests successful: ", allTests.map(t => t.name).join(", "))
|
console.log("All tests successful: ", testsToRun.map(t => t.name).join(", "))
|
||||||
|
|
Loading…
Reference in a new issue