diff --git a/Logic/Actors/GeoLocationHandler.ts b/Logic/Actors/GeoLocationHandler.ts index e3e2455bf..01e09a717 100644 --- a/Logic/Actors/GeoLocationHandler.ts +++ b/Logic/Actors/GeoLocationHandler.ts @@ -230,7 +230,7 @@ export default class GeoLocationHandler extends VariableUiElement { navigator?.permissions ?.query({name: "geolocation"}) ?.then(function (status) { - console.log("Geolocation is already", status); + console.log("Geolocation permission is ", status.state); if (status.state === "granted") { self.StartGeolocating(forceZoom); } @@ -289,7 +289,6 @@ export default class GeoLocationHandler extends VariableUiElement { private StartGeolocating(zoomToGPS = true) { const self = this; - console.log("Starting geolocation"); this._lastUserRequest = zoomToGPS ? new Date() : new Date(0); if (self._permission.data === "denied") { @@ -301,8 +300,6 @@ export default class GeoLocationHandler extends VariableUiElement { this.MoveToCurrentLoction(16); } - console.log("Searching location using GPS"); - if (self._isActive.data) { return; } diff --git a/Logic/Actors/OverpassFeatureSource.ts b/Logic/Actors/OverpassFeatureSource.ts index e788c7616..1e7ace7cd 100644 --- a/Logic/Actors/OverpassFeatureSource.ts +++ b/Logic/Actors/OverpassFeatureSource.ts @@ -184,8 +184,8 @@ export default class OverpassFeatureSource implements FeatureSource, FeatureSour return; } this.runningQuery.setData(true); - overpass.queryGeoJson(queryBounds, - function (data, date) { + overpass.queryGeoJson(queryBounds). + then(([data, date]) => { self._previousBounds.get(z).push(queryBounds); self.retries.setData(0); 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) } self.runningQuery.setData(false); - }, - function (reason) { + }) + .catch((reason) => { self.retries.data++; self.ForceRefresh(); 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.runningQuery.setData(false); diff --git a/Logic/Actors/TitleHandler.ts b/Logic/Actors/TitleHandler.ts index b5bc6f151..49661f289 100644 --- a/Logic/Actors/TitleHandler.ts +++ b/Logic/Actors/TitleHandler.ts @@ -8,8 +8,6 @@ export default class TitleHandler { constructor(state) { const currentTitle: UIEventSource = state.selectedElement.map( selected => { - console.log("UPdating title") - const layout = state.layoutToUse.data const defaultTitle = Translations.WT(layout?.title)?.txt ?? "MapComplete" diff --git a/Logic/FeatureSource/FeaturePipeline.ts b/Logic/FeatureSource/FeaturePipeline.ts index 2fe254eb1..c6206eac4 100644 --- a/Logic/FeatureSource/FeaturePipeline.ts +++ b/Logic/FeatureSource/FeaturePipeline.ts @@ -21,7 +21,7 @@ import {BBox} from "../GeoOperations"; import {TileHierarchyMerger} from "./TiledFeatureSource/TileHierarchyMerger"; import RelationsTracker from "../Osm/RelationsTracker"; import {NewGeometryFromChangesFeatureSource} from "./Sources/NewGeometryFromChangesFeatureSource"; -import ChangeGeometryApplicator from "./ChangeApplicator"; +import ChangeGeometryApplicator from "./Sources/ChangeGeometryApplicator"; 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 self.newDataLoadedSignal.stabilized(1000).addCallback(src => { - console.log("Got an update from ", src.name) self.updateAllMetaTagging() }) @@ -183,7 +182,6 @@ export default class FeaturePipeline implements FeatureSourceState { } private updateAllMetaTagging() { - console.log("Updating the meta tagging") const self = this; this.perLayerHierarchy.forEach(hierarchy => { hierarchy.loadedTiles.forEach(src => { diff --git a/Logic/FeatureSource/ChangeApplicator.ts b/Logic/FeatureSource/Sources/ChangeGeometryApplicator.ts similarity index 87% rename from Logic/FeatureSource/ChangeApplicator.ts rename to Logic/FeatureSource/Sources/ChangeGeometryApplicator.ts index bab2225bf..a35879d96 100644 --- a/Logic/FeatureSource/ChangeApplicator.ts +++ b/Logic/FeatureSource/Sources/ChangeGeometryApplicator.ts @@ -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 */ +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 { public readonly features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<{ feature: any; freshness: Date }[]>([]); 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 const change = changesForFeature[changesForFeature.length - 1] copy.feature.geometry = ChangeDescriptionTools.getGeojsonGeometry(change) + console.log("Applying a geometry change onto ", feature, change, copy) newFeatures.push(copy) } this.features.setData(newFeatures) diff --git a/Logic/FeatureSource/TiledFeatureSource/TiledFromLocalStorageSource.ts b/Logic/FeatureSource/TiledFeatureSource/TiledFromLocalStorageSource.ts index 0e4e091a3..e88a1d82d 100644 --- a/Logic/FeatureSource/TiledFeatureSource/TiledFromLocalStorageSource.ts +++ b/Logic/FeatureSource/TiledFeatureSource/TiledFromLocalStorageSource.ts @@ -27,7 +27,7 @@ export default class TiledFromLocalStorageSource implements TileHierarchy Utils.tile_from_index(i).join("/")).join(", ")) const zLevels = indexes.map(i => i % 100) const indexesSet = new Set(indexes) diff --git a/Logic/Osm/Actions/RelationSplitHandler.ts b/Logic/Osm/Actions/RelationSplitHandler.ts index dfca2d0fa..42562c035 100644 --- a/Logic/Osm/Actions/RelationSplitHandler.ts +++ b/Logic/Osm/Actions/RelationSplitHandler.ts @@ -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. */ export default class RelationSplitHandler extends OsmChangeAction { + private readonly _input: RelationSplitInput; constructor(input: RelationSplitInput) { super() + this._input = input; } async CreateChangeDescriptions(changes: Changes): Promise { - return []; + return new InPlaceReplacedmentRTSH(this._input).CreateChangeDescriptions(changes) } diff --git a/Logic/Osm/Actions/SplitAction.ts b/Logic/Osm/Actions/SplitAction.ts index cee912dca..085aab413 100644 --- a/Logic/Osm/Actions/SplitAction.ts +++ b/Logic/Osm/Actions/SplitAction.ts @@ -15,6 +15,11 @@ export default class SplitAction extends OsmChangeAction { private readonly wayId: string; private readonly _splitPointsCoordinates: [number, number] []// lon, lat + /** + * + * @param wayId + * @param splitPointCoordinates: lon, lat + */ constructor(wayId: string, splitPointCoordinates: [number, number][]) { super() this.wayId = wayId; @@ -39,12 +44,11 @@ export default class SplitAction extends OsmChangeAction { } async CreateChangeDescriptions(changes: Changes): Promise { - const self = this; - const originalElement = await OsmObject.DownloadObjectAsync(this.wayId) + const originalElement = await OsmObject.DownloadObjectAsync(this.wayId) const originalNodes = originalElement.nodes; // 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. // [ { 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[] { const wayGeoJson = osmWay.asGeoJson() // 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: { + // lon, lat coordinates: [number, number], isSplitPoint: boolean, originalIndex?: number, // Original index @@ -180,6 +185,7 @@ export default class SplitAction extends OsmChangeAction { // - `dist`: distance between pt and the closest point, // `location`: distance along the line between start and the closest point. let projected = GeoOperations.nearestPoint(wayGeoJson, c) + // c is lon lat return ({ coordinates: c, isSplitPoint: true, @@ -233,8 +239,12 @@ export default class SplitAction extends OsmChangeAction { if (distToNext > distToPrev) { closest = prevPoint } - // 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; allPoints.splice(i, 1) diff --git a/Logic/Osm/Changes.ts b/Logic/Osm/Changes.ts index aac1f08e9..83545b30a 100644 --- a/Logic/Osm/Changes.ts +++ b/Logic/Osm/Changes.ts @@ -14,7 +14,7 @@ import {LocalStorageSource} from "../Web/LocalStorageSource"; 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" /** * All the newly created features as featureSource + all the modified features @@ -30,6 +30,8 @@ export class Changes { constructor() { // We keep track of all changes just as well 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, @@ -77,7 +79,7 @@ export class Changes { * Returns a new ID and updates the value for the next ID */ public getNewID() { - return Changes._nextId--; + return this._nextId--; } /** diff --git a/Logic/Osm/OsmObject.ts b/Logic/Osm/OsmObject.ts index a10ec8d0c..8027a18d9 100644 --- a/Logic/Osm/OsmObject.ts +++ b/Logic/Osm/OsmObject.ts @@ -63,17 +63,26 @@ export abstract class OsmObject { if (idN < 0) { 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[] { const objects: OsmObject[] = []; const allNodes: Map = new Map() + for (const element of elements) { const type = element.type; const idN = element.id; @@ -226,6 +236,11 @@ export abstract class OsmObject { osmObject.SaveExtraData(element, []) break; } + + if (osmObject !== undefined && OsmObject.backendURL !== OsmObject.defaultBackend) { + osmObject.tags["_backend"] = OsmObject.backendURL + } + osmObject?.LoadData(element) objects.push(osmObject) } @@ -237,7 +252,7 @@ export abstract class OsmObject { public abstract asGeoJson(): any; - abstract SaveExtraData(element: any, allElements: any[]); + abstract SaveExtraData(element: any, allElements: OsmObject[]); /** * Generates the changeset-XML for tags @@ -260,37 +275,6 @@ export abstract class OsmObject { return tags; } - /** - * Downloads the object, a full download for ways and relations - * @constructor - */ - async Download(): Promise { - 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; protected VersionXML() { @@ -363,7 +347,7 @@ export class OsmNode extends OsmObject { export class OsmWay extends OsmObject { - nodes: number[]; + nodes: number[] = []; // The coordinates of the way, [lat, lon][] coordinates: [number, number][] = [] lat: number; @@ -400,6 +384,10 @@ export class OsmWay extends OsmObject { nodeDict.set(node.id, node) } + if (element.nodes === undefined) { + console.log("PANIC") + } + for (const nodeId of element.nodes) { const node = nodeDict.get(nodeId) if (node === undefined) { @@ -419,8 +407,8 @@ export class OsmWay extends OsmObject { } public asGeoJson() { - let coordinates : ([number, number][] | [number, number][][]) = this.coordinates.map(c => <[number, number]>c.reverse()); - if(this.isPolygon()){ + let coordinates: ([number, number][] | [number, number][][]) = this.coordinates.map(c => [c[1], c[0]]); + if (this.isPolygon()) { coordinates = [coordinates] } return { diff --git a/Logic/Osm/OsmPreferences.ts b/Logic/Osm/OsmPreferences.ts index 291b49a4f..fadd9c105 100644 --- a/Logic/Osm/OsmPreferences.ts +++ b/Logic/Osm/OsmPreferences.ts @@ -145,14 +145,14 @@ export class OsmPreferences { private SetPreference(k: string, v: string) { 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; } if (this.preferences.data[k] === v) { 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 === "") { this.auth.xhr({ @@ -161,10 +161,10 @@ export class OsmPreferences { options: {header: {'Content-Type': 'text/plain'}}, }, function (error) { if (error) { - console.log("Could not remove preference", error); + console.warn("Could not remove preference", error); return; } - console.log("Preference ", k, "removed!"); + console.debug("Preference ", k, "removed!"); }); return; diff --git a/Logic/Osm/Overpass.ts b/Logic/Osm/Overpass.ts index 2f82889e3..de6b65287 100644 --- a/Logic/Osm/Overpass.ts +++ b/Logic/Osm/Overpass.ts @@ -31,7 +31,7 @@ export class Overpass { 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 + "]") @@ -40,24 +40,18 @@ export class Overpass { query = Overpass.testUrl; } const self = this; - Utils.downloadJson(query) - .then(json => { - if (json.elements === [] && ((json.remarks ?? json.remark).indexOf("runtime error") >= 0)) { - console.log("Timeout or other runtime error"); - onFail("Runtime error (timeout)") - return; - } + const json = await Utils.downloadJson(query) + + if (json.elements === [] && ((json.remarks ?? json.remark).indexOf("runtime error") >= 0)) { + console.log("Timeout or other runtime error"); + throw("Runtime error (timeout)") + } - - self._relationTracker.RegisterRelations(json) - // @ts-ignore - const geojson = OsmToGeoJson.default(json); - const osmTime = new Date(json.osm3s.timestamp_osm_base); - - continuation(geojson, osmTime); - }).catch(e => { - onFail(e); - }) + self._relationTracker.RegisterRelations(json) + // @ts-ignore + const geojson = OsmToGeoJson.default(json); + const osmTime = new Date(json.osm3s.timestamp_osm_base); + return [geojson, osmTime]; } buildQuery(bbox: string): string { diff --git a/Models/ThemeConfig/Json/LayoutConfigJson.ts b/Models/ThemeConfig/Json/LayoutConfigJson.ts index 3c312ce2e..856ab2736 100644 --- a/Models/ThemeConfig/Json/LayoutConfigJson.ts +++ b/Models/ThemeConfig/Json/LayoutConfigJson.ts @@ -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. * 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; diff --git a/Models/ThemeConfig/LayoutConfig.ts b/Models/ThemeConfig/LayoutConfig.ts index 112810112..dd0d472ed 100644 --- a/Models/ThemeConfig/LayoutConfig.ts +++ b/Models/ThemeConfig/LayoutConfig.ts @@ -87,7 +87,7 @@ export default class LayoutConfig { this.startZoom = json.startZoom; this.startLat = json.startLat; this.startLon = json.startLon; - this.widenFactor = json.widenFactor ?? 0.05; + this.widenFactor = json.widenFactor ?? 1.5; this.roamingRenderings = (json.roamingRenderings ?? []).map((tr, i) => { if (typeof tr === "string") { if (SharedTagRenderings.SharedTagRendering.get(tr) !== undefined) { diff --git a/Utils.ts b/Utils.ts index f3e82d574..9f1373498 100644 --- a/Utils.ts +++ b/Utils.ts @@ -259,10 +259,10 @@ export class Utils { } static tile_bounds_lon_lat(z: number, x: number, y: number): [[number, number], [number, number]] { - return [[Utils.tile2long(x, z),Utils.tile2lat(y, z)], [Utils.tile2long(x + 1, z), Utils.tile2lat(y + 1, z)]] + return [[Utils.tile2long(x, z), Utils.tile2lat(y, z)], [Utils.tile2long(x + 1, z), Utils.tile2lat(y + 1, z)]] } - - static tile_index(z: number, x: number, y: number):number{ + + static tile_index(z: number, x: number, y: number): number { return ((x * (2 << z)) + y) * 100 + z } @@ -271,7 +271,7 @@ export class Utils { * @param index * @returns 'zxy' */ - static tile_from_index(index: number) : [number, number, number]{ + static tile_from_index(index: number): [number, number, number] { const z = index % 100; const factor = 2 << z index = Math.floor(index / 100) @@ -356,35 +356,43 @@ export class Utils { return result; } + private static injectedDownloads = {} + + public static injectJsonDownloadForTests(url: string, data) { + Utils.injectedDownloads[url] = data + } + public static downloadJson(url: string): Promise { + + 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) { return this.externalDownloadFunction(url) } - return new Promise( - (resolve, reject) => { - try { - const xhr = new XMLHttpRequest(); - xhr.onload = () => { - if (xhr.status == 200) { - try { - resolve(JSON.parse(xhr.response)) - } catch (e) { - reject("Not a valid json: " + xhr.response) - } - } else { - reject(xhr.statusText) + return new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + xhr.onload = () => { + if (xhr.status == 200) { + try { + console.log("Got a response! Parsing now...") + resolve(JSON.parse(xhr.response)) + } catch (e) { + reject("Not a valid json: " + xhr.response) } - }; - xhr.open('GET', url); - xhr.setRequestHeader("accept", "application/json") - xhr.send(); - } catch (e) { - reject(e) - } + } else { + reject(xhr.statusText) + } + }; + xhr.open('GET', url); + xhr.setRequestHeader("accept", "application/json") + xhr.send(); } ) - } /** @@ -486,12 +494,12 @@ export class Utils { } static sortKeys(o: any) { - const copy = {} + const copy = {} let keys = Object.keys(o) keys = keys.sort() for (const key of keys) { let v = o[key] - if(typeof v === "object"){ + if (typeof v === "object") { v = Utils.sortKeys(v) } copy[key] = v diff --git a/assets/themes/cyclestreets/cyclestreets.json b/assets/themes/cyclestreets/cyclestreets.json index e9d9b4e95..9d09f0c7a 100644 --- a/assets/themes/cyclestreets/cyclestreets.json +++ b/assets/themes/cyclestreets/cyclestreets.json @@ -34,7 +34,7 @@ "startZoom": 14, "startLon": 3.2228, "maintainer": "MapComplete", - "widenfactor": 0.01, + "widenfactor": 2, "roamingRenderings": [ { "question": { diff --git a/test/RelationSplitHandler.spec.ts b/test/RelationSplitHandler.spec.ts index 8a5174da3..0b5261f13 100644 --- a/test/RelationSplitHandler.spec.ts +++ b/test/RelationSplitHandler.spec.ts @@ -3,6 +3,7 @@ import {InPlaceReplacedmentRTSH} from "../Logic/Osm/Actions/RelationSplitHandler import {OsmObject, OsmRelation} from "../Logic/Osm/OsmObject"; import {Changes} from "../Logic/Osm/Changes"; import {equal} from "assert"; +import {Utils} from "../Utils"; export default class RelationSplitHandlerSpec extends T { @@ -58,6 +59,28 @@ export default class RelationSplitHandlerSpec extends T { } 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", [ ["split 295132739", () => RelationSplitHandlerSpec.split().then(_ => console.log("OK"))] diff --git a/test/SplitAction.spec.ts b/test/SplitAction.spec.ts new file mode 100644 index 000000000..6e4ed77a9 --- /dev/null +++ b/test/SplitAction.spec.ts @@ -0,0 +1,274 @@ +import T from "./TestHelper"; +import {Changes} from "../Logic/Osm/Changes"; +import SplitAction from "../Logic/Osm/Actions/SplitAction"; +import {equal} from "assert"; +import {Utils} from "../Utils"; + +export default class SplitActionSpec extends T { + + + private static async split(): Promise { + + Utils.injectJsonDownloadForTests( + "https://www.openstreetmap.org/api/0.6/way/295132739/full", + { + "version": "0.6", + "generator": "CGImap 0.8.5 (3138407 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": 170497153, + "lat": 51.1825167, + "lon": 3.2487885, + "timestamp": "2011-11-18T16:33:43Z", + "version": 5, + "changeset": 9865255, + "user": "TripleBee", + "uid": 497177 + }, { + "type": "node", + "id": 170497155, + "lat": 51.1817632, + "lon": 3.2472706, + "timestamp": "2011-11-18T16:33:43Z", + "version": 5, + "changeset": 9865255, + "user": "TripleBee", + "uid": 497177 + }, { + "type": "node", + "id": 170497157, + "lat": 51.1815203, + "lon": 3.2465569, + "timestamp": "2011-11-18T16:33:43Z", + "version": 5, + "changeset": 9865255, + "user": "TripleBee", + "uid": 497177 + }, { + "type": "node", + "id": 170497158, + "lat": 51.1812261, + "lon": 3.2454261, + "timestamp": "2011-11-18T16:33:43Z", + "version": 5, + "changeset": 9865255, + "user": "TripleBee", + "uid": 497177 + }, { + "type": "node", + "id": 170497160, + "lat": 51.1810957, + "lon": 3.2443030, + "timestamp": "2011-11-18T16:33:43Z", + "version": 5, + "changeset": 9865255, + "user": "TripleBee", + "uid": 497177 + }, { + "type": "node", + "id": 1507524573, + "lat": 51.1810778, + "lon": 3.2437148, + "timestamp": "2011-11-18T16:33:36Z", + "version": 1, + "changeset": 9865255, + "user": "TripleBee", + "uid": 497177 + }, { + "type": "node", + "id": 1507524582, + "lat": 51.1821130, + "lon": 3.2481284, + "timestamp": "2011-11-18T16:33:37Z", + "version": 1, + "changeset": 9865255, + "user": "TripleBee", + "uid": 497177 + }, { + "type": "node", + "id": 1507524610, + "lat": 51.1811645, + "lon": 3.2450828, + "timestamp": "2011-11-18T16:33:38Z", + "version": 1, + "changeset": 9865255, + "user": "TripleBee", + "uid": 497177 + }, { + "type": "node", + "id": 1575932830, + "lat": 51.1811153, + "lon": 3.2431503, + "timestamp": "2019-05-04T22:44:13Z", + "version": 2, + "changeset": 69891295, + "user": "Pieter Vander Vennet", + "uid": 3818858 + }, { + "type": "node", + "id": 3208166178, + "lat": 51.1810837, + "lon": 3.2439090, + "timestamp": "2014-11-27T20:23:10Z", + "version": 1, + "changeset": 27076816, + "user": "JanFi", + "uid": 672253 + }, { + "type": "node", + "id": 3208166179, + "lat": 51.1812062, + "lon": 3.2453151, + "timestamp": "2014-11-27T20:23:10Z", + "version": 1, + "changeset": 27076816, + "user": "JanFi", + "uid": 672253 + }, { + "type": "node", + "id": 4524321710, + "lat": 51.1820656, + "lon": 3.2480253, + "timestamp": "2017-12-09T18:56:37Z", + "version": 2, + "changeset": 54493928, + "user": "CacherB", + "uid": 1999108 + }, { + "type": "node", + "id": 5273988967, + "lat": 51.1826590, + "lon": 3.2490040, + "timestamp": "2017-12-09T18:40:21Z", + "version": 1, + "changeset": 54493533, + "user": "CacherB", + "uid": 1999108 + }, { + "type": "node", + "id": 6448669326, + "lat": 51.1811346, + "lon": 3.2428910, + "timestamp": "2019-05-04T22:44:12Z", + "version": 1, + "changeset": 69891295, + "user": "Pieter Vander Vennet", + "uid": 3818858, + "tags": {"barrier": "bollard"} + }, { + "type": "way", + "id": 295132739, + "timestamp": "2021-07-29T21:14:53Z", + "version": 17, + "changeset": 108847202, + "user": "kaart_fietser", + "uid": 11022240, + "nodes": [5273988967, 170497153, 1507524582, 4524321710, 170497155, 170497157, 170497158, 3208166179, 1507524610, 170497160, 3208166178, 1507524573, 1575932830, 6448669326], + "tags": { + "highway": "cycleway", + "name": "Abdijenroute", + "railway": "abandoned", + "surface": "compacted" + } + }] + }) + Utils.injectJsonDownloadForTests( + "https://www.openstreetmap.org/api/0.6/way/295132739/relations", + // Mimick that there are no relations relation is missing + { + "version": "0.6", + "generator": "CGImap 0.8.5 (2935793 spike-07.openstreetmap.org)", + "copyright": "OpenStreetMap and contributors", + "attribution": "http://www.openstreetmap.org/copyright", + "license": "http://opendatacommons.org/licenses/odbl/1-0/", + "elements": [] + } + ) + + // Lets split road https://www.openstreetmap.org/way/295132739 + const id = "way/295132739" + const splitPoint: [number, number] = [3.246733546257019, 51.181710380278176] + const splitter = new SplitAction(id, [splitPoint]) + const changeDescription = await splitter.CreateChangeDescriptions(new Changes()) + + equal(changeDescription[0].type, "node") + equal(changeDescription[0].id, -1) + equal(changeDescription[0].changes["lat"], 51.181710380278176) + equal(changeDescription[0].changes["lon"], 3.246733546257019) + + equal(changeDescription[1].type, "way") + equal(changeDescription[1].id, -2) + equal(changeDescription[1].changes["coordinates"].length, 6) + equal(changeDescription[1].changes["coordinates"][5][0], splitPoint[0]) + equal(changeDescription[1].changes["coordinates"][5][1], splitPoint[1]) + + equal(changeDescription[2].type, "way") + equal(changeDescription[2].id, 295132739) + equal(changeDescription[2].changes["coordinates"].length, 10) + equal(changeDescription[2].changes["coordinates"][0][0], splitPoint[0]) + equal(changeDescription[2].changes["coordinates"][0][1], splitPoint[1]) + } + + + private static async SplitHoutkaai() : Promise{ + + Utils.injectJsonDownloadForTests( + "https://www.openstreetmap.org/api/0.6/way/61435323/full" , + {"version":"0.6","generator":"CGImap 0.8.5 (53092 spike-08.openstreetmap.org)","copyright":"OpenStreetMap and contributors","attribution":"http://www.openstreetmap.org/copyright","license":"http://opendatacommons.org/licenses/odbl/1-0/","elements":[{"type":"node","id":766990983,"lat":51.2170219,"lon":3.2022337,"timestamp":"2021-04-26T15:48:22Z","version":6,"changeset":103647857,"user":"M!dgard","uid":763799},{"type":"node","id":766990985,"lat":51.2169574,"lon":3.2017548,"timestamp":"2016-07-05T22:41:12Z","version":6,"changeset":40511250,"user":"M!dgard","uid":763799},{"type":"node","id":8669018379,"lat":51.2169592,"lon":3.2017683,"timestamp":"2021-04-26T15:48:22Z","version":1,"changeset":103647857,"user":"M!dgard","uid":763799},{"type":"way","id":61435323,"timestamp":"2021-08-21T12:24:13Z","version":7,"changeset":110026637,"user":"Thibault Rommel","uid":5846458,"nodes":[766990983,8669018379,766990985],"tags":{"bicycle":"yes","bridge":"yes","cycleway":"shared_lane","highway":"unclassified","layer":"1","maxspeed":"50","name":"Houtkaai","surface":"asphalt","zone:traffic":"BE-VLG:urban"}}]} + ) + Utils.injectJsonDownloadForTests( + "https://www.openstreetmap.org/api/0.6/way/61435323/relations" , + {"version":"0.6","generator":"CGImap 0.8.5 (3622541 spike-06.openstreetmap.org)","copyright":"OpenStreetMap and contributors","attribution":"http://www.openstreetmap.org/copyright","license":"http://opendatacommons.org/licenses/odbl/1-0/","elements":[{"type":"relation","id":1723870,"timestamp":"2021-09-18T06:29:31Z","version":183,"changeset":111362343,"user":"emvee","uid":5211,"members":[{"type":"way","ref":261428947,"role":""},{"type":"way","ref":162774622,"role":""},{"type":"way","ref":317060244,"role":""},{"type":"way","ref":81155378,"role":""},{"type":"way","ref":99749583,"role":""},{"type":"way","ref":131332113,"role":""},{"type":"way","ref":949518831,"role":""},{"type":"way","ref":99749584,"role":""},{"type":"way","ref":129133519,"role":""},{"type":"way","ref":73241312,"role":""},{"type":"way","ref":785514256,"role":""},{"type":"way","ref":58509643,"role":""},{"type":"way","ref":73241332,"role":""},{"type":"way","ref":58509653,"role":""},{"type":"way","ref":100044097,"role":""},{"type":"way","ref":946999067,"role":""},{"type":"way","ref":73241327,"role":""},{"type":"way","ref":58509617,"role":""},{"type":"way","ref":58509627,"role":""},{"type":"way","ref":69990655,"role":""},{"type":"way","ref":73241311,"role":""},{"type":"way","ref":123142336,"role":""},{"type":"way","ref":249671053,"role":""},{"type":"way","ref":73241324,"role":""},{"type":"way","ref":66706953,"role":""},{"type":"way","ref":112679357,"role":""},{"type":"way","ref":112679358,"role":""},{"type":"way","ref":53105113,"role":""},{"type":"way","ref":66706952,"role":""},{"type":"way","ref":64083661,"role":""},{"type":"way","ref":53105162,"role":""},{"type":"way","ref":249671070,"role":""},{"type":"way","ref":249671064,"role":""},{"type":"way","ref":101498587,"role":""},{"type":"way","ref":69001236,"role":""},{"type":"way","ref":101498585,"role":""},{"type":"way","ref":70909444,"role":""},{"type":"way","ref":73241314,"role":""},{"type":"way","ref":69001235,"role":""},{"type":"way","ref":113150200,"role":""},{"type":"way","ref":137305843,"role":""},{"type":"way","ref":936827687,"role":""},{"type":"way","ref":936827688,"role":""},{"type":"way","ref":112952373,"role":""},{"type":"way","ref":930798379,"role":""},{"type":"way","ref":930798378,"role":""},{"type":"way","ref":112951439,"role":""},{"type":"way","ref":445541591,"role":""},{"type":"way","ref":103843896,"role":""},{"type":"way","ref":23734118,"role":""},{"type":"way","ref":103840557,"role":""},{"type":"way","ref":433852210,"role":""},{"type":"way","ref":313604670,"role":""},{"type":"way","ref":103839402,"role":""},{"type":"way","ref":23736061,"role":""},{"type":"way","ref":73241328,"role":""},{"type":"way","ref":295392689,"role":""},{"type":"way","ref":297168171,"role":""},{"type":"way","ref":297168170,"role":""},{"type":"way","ref":433852205,"role":""},{"type":"way","ref":295392695,"role":""},{"type":"way","ref":663268954,"role":""},{"type":"way","ref":663267598,"role":""},{"type":"way","ref":292478843,"role":""},{"type":"way","ref":981853853,"role":""},{"type":"way","ref":663270140,"role":""},{"type":"way","ref":981853854,"role":""},{"type":"way","ref":295392703,"role":""},{"type":"way","ref":663304916,"role":""},{"type":"way","ref":297169116,"role":""},{"type":"way","ref":295400810,"role":""},{"type":"way","ref":981853855,"role":""},{"type":"way","ref":663304806,"role":""},{"type":"way","ref":516452870,"role":""},{"type":"way","ref":66459239,"role":""},{"type":"way","ref":791430504,"role":""},{"type":"way","ref":178926037,"role":""},{"type":"way","ref":864799431,"role":""},{"type":"way","ref":178926107,"role":""},{"type":"way","ref":663320459,"role":""},{"type":"way","ref":62033993,"role":""},{"type":"way","ref":62283023,"role":""},{"type":"way","ref":62283057,"role":""},{"type":"way","ref":62283032,"role":""},{"type":"way","ref":490551085,"role":""},{"type":"way","ref":435318979,"role":""},{"type":"way","ref":371750677,"role":""},{"type":"way","ref":371750670,"role":""},{"type":"way","ref":371750673,"role":""},{"type":"way","ref":371750675,"role":""},{"type":"way","ref":459885691,"role":""},{"type":"way","ref":371750669,"role":""},{"type":"way","ref":371750668,"role":""},{"type":"way","ref":371750667,"role":""},{"type":"way","ref":428848639,"role":""},{"type":"way","ref":371750666,"role":""},{"type":"way","ref":371750665,"role":""},{"type":"way","ref":825496473,"role":""},{"type":"way","ref":371750664,"role":""},{"type":"way","ref":371750662,"role":""},{"type":"way","ref":371750663,"role":""},{"type":"way","ref":371750660,"role":""},{"type":"way","ref":371750658,"role":""},{"type":"way","ref":40507374,"role":""},{"type":"way","ref":165878356,"role":""},{"type":"way","ref":165878355,"role":""},{"type":"way","ref":8494219,"role":""},{"type":"way","ref":5023947,"role":""},{"type":"way","ref":5023939,"role":""},{"type":"way","ref":26718843,"role":""},{"type":"way","ref":79437029,"role":""},{"type":"way","ref":87522151,"role":""},{"type":"way","ref":26718848,"role":""},{"type":"way","ref":233169831,"role":""},{"type":"way","ref":85934460,"role":""},{"type":"way","ref":145892210,"role":""},{"type":"way","ref":79434764,"role":""},{"type":"way","ref":127079185,"role":""},{"type":"way","ref":67794715,"role":""},{"type":"way","ref":85934250,"role":""},{"type":"way","ref":421566302,"role":""},{"type":"way","ref":123445537,"role":""},{"type":"way","ref":308077683,"role":""},{"type":"way","ref":308077684,"role":""},{"type":"way","ref":972955357,"role":""},{"type":"way","ref":308077682,"role":""},{"type":"way","ref":659880052,"role":""},{"type":"way","ref":308077681,"role":""},{"type":"way","ref":66364130,"role":""},{"type":"way","ref":51086959,"role":""},{"type":"way","ref":51086961,"role":""},{"type":"way","ref":102154586,"role":""},{"type":"way","ref":102154589,"role":""},{"type":"way","ref":703008376,"role":""},{"type":"way","ref":703008375,"role":""},{"type":"way","ref":54435150,"role":""},{"type":"way","ref":115913100,"role":""},{"type":"way","ref":79433785,"role":""},{"type":"way","ref":51204355,"role":""},{"type":"way","ref":422395066,"role":""},{"type":"way","ref":116628138,"role":""},{"type":"way","ref":690189323,"role":""},{"type":"way","ref":132068368,"role":""},{"type":"way","ref":690220771,"role":""},{"type":"way","ref":690220772,"role":""},{"type":"way","ref":690226744,"role":""},{"type":"way","ref":690226745,"role":""},{"type":"way","ref":60253953,"role":""},{"type":"way","ref":690195774,"role":""},{"type":"way","ref":688104939,"role":""},{"type":"way","ref":422395064,"role":"forward"},{"type":"way","ref":422309497,"role":"forward"},{"type":"way","ref":25677204,"role":"forward"},{"type":"way","ref":51570941,"role":""},{"type":"way","ref":807329786,"role":""},{"type":"way","ref":165500495,"role":""},{"type":"way","ref":689494106,"role":""},{"type":"way","ref":131476435,"role":""},{"type":"way","ref":689493508,"role":""},{"type":"way","ref":12126873,"role":""},{"type":"way","ref":32789519,"role":""},{"type":"way","ref":27288122,"role":""},{"type":"way","ref":116717060,"role":""},{"type":"way","ref":176380249,"role":""},{"type":"way","ref":116717052,"role":""},{"type":"way","ref":176380250,"role":""},{"type":"way","ref":421998791,"role":""},{"type":"way","ref":34562745,"role":""},{"type":"way","ref":130473931,"role":""},{"type":"way","ref":136487196,"role":""},{"type":"way","ref":23792223,"role":""},{"type":"way","ref":23775021,"role":""},{"type":"way","ref":560506339,"role":""},{"type":"way","ref":337945886,"role":""},{"type":"way","ref":61435332,"role":""},{"type":"way","ref":61435323,"role":""},{"type":"way","ref":509668834,"role":""},{"type":"way","ref":130473917,"role":""},{"type":"way","ref":369929894,"role":""},{"type":"way","ref":805247467,"role":"forward"},{"type":"way","ref":840210016,"role":"forward"},{"type":"way","ref":539026983,"role":"forward"},{"type":"way","ref":539037793,"role":"forward"},{"type":"way","ref":244428576,"role":"forward"},{"type":"way","ref":243333119,"role":"forward"},{"type":"way","ref":243333108,"role":"forward"},{"type":"way","ref":243333106,"role":"forward"},{"type":"way","ref":243333110,"role":"forward"},{"type":"way","ref":230511503,"role":"forward"},{"type":"way","ref":510520445,"role":"forward"},{"type":"way","ref":688103605,"role":"forward"},{"type":"way","ref":668577053,"role":"forward"},{"type":"way","ref":4332489,"role":"forward"},{"type":"way","ref":668577051,"role":"forward"},{"type":"way","ref":185476761,"role":"forward"},{"type":"way","ref":100774483,"role":"forward"},{"type":"way","ref":668672434,"role":"backward"},{"type":"way","ref":488558133,"role":"backward"},{"type":"way","ref":13943237,"role":"forward"},{"type":"way","ref":840241791,"role":"forward"},{"type":"way","ref":805247468,"role":"forward"},{"type":"way","ref":539040946,"role":"forward"},{"type":"way","ref":539026103,"role":"forward"},{"type":"way","ref":539037781,"role":"forward"},{"type":"way","ref":28942112,"role":"forward"},{"type":"way","ref":699841535,"role":"forward"},{"type":"way","ref":635374201,"role":"forward"},{"type":"way","ref":28942118,"role":"forward"},{"type":"way","ref":185476755,"role":"forward"},{"type":"way","ref":78794903,"role":"forward"},{"type":"way","ref":688103599,"role":"forward"},{"type":"way","ref":688103600,"role":"backward"},{"type":"way","ref":32699077,"role":"backward"},{"type":"way","ref":249092420,"role":"backward"},{"type":"way","ref":540048295,"role":""},{"type":"way","ref":13942938,"role":""},{"type":"way","ref":827705395,"role":""},{"type":"way","ref":72492953,"role":""},{"type":"way","ref":61435342,"role":""},{"type":"way","ref":95106180,"role":""},{"type":"way","ref":182691326,"role":""},{"type":"way","ref":180915274,"role":""},{"type":"way","ref":61435340,"role":""},{"type":"way","ref":95506626,"role":""},{"type":"way","ref":183330864,"role":""},{"type":"way","ref":318631002,"role":""},{"type":"way","ref":4332470,"role":""},{"type":"way","ref":318631014,"role":""},{"type":"way","ref":337969633,"role":""},{"type":"way","ref":668566903,"role":""},{"type":"way","ref":668566904,"role":""},{"type":"way","ref":248228679,"role":""},{"type":"way","ref":419296358,"role":""},{"type":"way","ref":601005356,"role":""},{"type":"way","ref":497802656,"role":""},{"type":"way","ref":948484806,"role":""},{"type":"way","ref":756223825,"role":""},{"type":"way","ref":23206884,"role":""},{"type":"way","ref":157436856,"role":""},{"type":"way","ref":829398288,"role":""},{"type":"way","ref":829398289,"role":""},{"type":"way","ref":674490354,"role":""},{"type":"way","ref":131704173,"role":""},{"type":"way","ref":120976014,"role":""},{"type":"way","ref":38864144,"role":""},{"type":"way","ref":38864143,"role":""},{"type":"way","ref":32147475,"role":""},{"type":"way","ref":962256846,"role":""},{"type":"way","ref":32147479,"role":""},{"type":"way","ref":32147481,"role":""},{"type":"way","ref":49486734,"role":""},{"type":"way","ref":829394351,"role":""},{"type":"way","ref":829394349,"role":""},{"type":"way","ref":235193261,"role":""},{"type":"way","ref":130495866,"role":""},{"type":"way","ref":978366962,"role":""},{"type":"way","ref":39588752,"role":""},{"type":"way","ref":436528651,"role":""},{"type":"way","ref":27370335,"role":""},{"type":"way","ref":157558803,"role":""},{"type":"way","ref":39590466,"role":""},{"type":"way","ref":157558804,"role":""},{"type":"way","ref":27370165,"role":""},{"type":"way","ref":970841665,"role":""}],"tags":{"name":"Euroroute R1 - part Belgium","name:de":"Europaradweg R1 - Abschnitt Belgien","name:nl":"Euroroute R1 - deel Belgiƫ","network":"icn","ref":"R1","route":"bicycle","type":"route"}},{"type":"relation","id":1757007,"timestamp":"2020-10-13T01:31:44Z","version":10,"changeset":92380204,"user":"Diabolix","uid":2123963,"members":[{"type":"way","ref":509668834,"role":""},{"type":"way","ref":61435323,"role":""},{"type":"way","ref":61435332,"role":""},{"type":"way","ref":337945886,"role":""},{"type":"way","ref":560506339,"role":""},{"type":"way","ref":23775021,"role":""},{"type":"way","ref":23792223,"role":""}],"tags":{"network":"rcn","network:type":"node_network","ref":"4-36","route":"bicycle","type":"route"}},{"type":"relation","id":5150189,"timestamp":"2021-09-09T20:15:58Z","version":44,"changeset":110993632,"user":"JosV","uid":170722,"members":[{"type":"way","ref":13943237,"role":""},{"type":"way","ref":488558133,"role":""},{"type":"way","ref":369929894,"role":""},{"type":"way","ref":130473917,"role":""},{"type":"way","ref":509668834,"role":""},{"type":"way","ref":61435323,"role":""},{"type":"way","ref":61435332,"role":""},{"type":"way","ref":337945886,"role":""},{"type":"way","ref":560506339,"role":""},{"type":"way","ref":23775021,"role":""},{"type":"way","ref":23792223,"role":""},{"type":"way","ref":136487196,"role":""},{"type":"way","ref":130473931,"role":""},{"type":"way","ref":34562745,"role":""},{"type":"way","ref":421998791,"role":""},{"type":"way","ref":126996864,"role":""},{"type":"way","ref":126996861,"role":""},{"type":"way","ref":170989337,"role":""},{"type":"way","ref":72482534,"role":""},{"type":"way","ref":58913500,"role":""},{"type":"way","ref":72482539,"role":""},{"type":"way","ref":246969243,"role":""},{"type":"way","ref":153150902,"role":""},{"type":"way","ref":116748588,"role":""},{"type":"way","ref":72482544,"role":""},{"type":"way","ref":72482542,"role":""},{"type":"way","ref":337013552,"role":""},{"type":"way","ref":132790401,"role":""},{"type":"way","ref":105166767,"role":""},{"type":"way","ref":720356345,"role":""},{"type":"way","ref":197829999,"role":""},{"type":"way","ref":105166552,"role":""},{"type":"way","ref":61979075,"role":""},{"type":"way","ref":197830184,"role":""},{"type":"way","ref":61979070,"role":""},{"type":"way","ref":948826013,"role":""},{"type":"way","ref":197830182,"role":""},{"type":"way","ref":672535497,"role":""},{"type":"way","ref":672535498,"role":""},{"type":"way","ref":948826015,"role":""},{"type":"way","ref":11378674,"role":""},{"type":"way","ref":672535496,"role":""},{"type":"way","ref":70023921,"role":""},{"type":"way","ref":948826017,"role":""},{"type":"way","ref":197830260,"role":""},{"type":"way","ref":152210843,"role":""},{"type":"way","ref":33748055,"role":""},{"type":"way","ref":344701437,"role":""},{"type":"way","ref":422150672,"role":""},{"type":"way","ref":156228338,"role":""},{"type":"way","ref":422150674,"role":""},{"type":"way","ref":223674432,"role":""},{"type":"way","ref":223674437,"role":""},{"type":"way","ref":156228327,"role":""},{"type":"way","ref":223674372,"role":""},{"type":"way","ref":592937889,"role":""},{"type":"way","ref":592937890,"role":""},{"type":"way","ref":422099666,"role":""},{"type":"way","ref":422100304,"role":""},{"type":"way","ref":948826022,"role":""},{"type":"way","ref":15092930,"role":""},{"type":"way","ref":948826024,"role":""},{"type":"way","ref":105182226,"role":""},{"type":"way","ref":133606215,"role":""},{"type":"way","ref":533395656,"role":""},{"type":"way","ref":187115987,"role":""},{"type":"way","ref":105182230,"role":""},{"type":"way","ref":105182232,"role":""},{"type":"way","ref":196011634,"role":""},{"type":"way","ref":153273480,"role":""},{"type":"way","ref":153273481,"role":""},{"type":"way","ref":881767783,"role":""},{"type":"way","ref":153273479,"role":""},{"type":"way","ref":13462242,"role":""},{"type":"way","ref":498093425,"role":""},{"type":"way","ref":70009137,"role":""},{"type":"way","ref":12086805,"role":""},{"type":"way","ref":52523332,"role":""},{"type":"way","ref":70009138,"role":""},{"type":"way","ref":592937884,"role":""},{"type":"way","ref":15071942,"role":""},{"type":"way","ref":180798233,"role":""},{"type":"way","ref":70010670,"role":""},{"type":"way","ref":15802818,"role":""},{"type":"way","ref":15802809,"role":""},{"type":"way","ref":70011254,"role":""},{"type":"way","ref":671368756,"role":""},{"type":"way","ref":840241791,"role":""},{"type":"way","ref":369929367,"role":""},{"type":"way","ref":539038988,"role":""},{"type":"way","ref":80130513,"role":""},{"type":"way","ref":540214122,"role":""},{"type":"way","ref":765795083,"role":""},{"type":"way","ref":13943005,"role":""},{"type":"way","ref":72492950,"role":""},{"type":"way","ref":183330864,"role":""},{"type":"way","ref":318631002,"role":""},{"type":"way","ref":4332470,"role":""},{"type":"way","ref":318631014,"role":""},{"type":"way","ref":337969633,"role":""},{"type":"way","ref":668566903,"role":""},{"type":"way","ref":668566904,"role":""},{"type":"way","ref":248228679,"role":""},{"type":"way","ref":419296358,"role":""},{"type":"way","ref":601005356,"role":""},{"type":"way","ref":497802656,"role":""},{"type":"way","ref":948484806,"role":""},{"type":"way","ref":100323579,"role":""},{"type":"way","ref":100708215,"role":""},{"type":"way","ref":124559834,"role":""},{"type":"way","ref":124559835,"role":""},{"type":"way","ref":239484694,"role":""},{"type":"way","ref":972646812,"role":""},{"type":"way","ref":124559832,"role":""},{"type":"way","ref":361686157,"role":""},{"type":"way","ref":361686155,"role":""},{"type":"way","ref":239484693,"role":""},{"type":"way","ref":19861731,"role":""},{"type":"way","ref":967906429,"role":""},{"type":"way","ref":126402539,"role":""},{"type":"way","ref":94427058,"role":""},{"type":"way","ref":126402541,"role":""},{"type":"way","ref":313693839,"role":""},{"type":"way","ref":313693838,"role":""},{"type":"way","ref":970740536,"role":""},{"type":"way","ref":361719175,"role":""},{"type":"way","ref":663186012,"role":""},{"type":"way","ref":744625794,"role":""},{"type":"way","ref":94569877,"role":""},{"type":"way","ref":188973964,"role":""},{"type":"way","ref":948484822,"role":""},{"type":"way","ref":28857260,"role":""},{"type":"way","ref":948484821,"role":""},{"type":"way","ref":219185860,"role":""},{"type":"way","ref":948484818,"role":""},{"type":"way","ref":219185861,"role":""},{"type":"way","ref":229885580,"role":""},{"type":"way","ref":28857247,"role":""},{"type":"way","ref":128813937,"role":""},{"type":"way","ref":32148201,"role":""},{"type":"way","ref":829398290,"role":""},{"type":"way","ref":829398288,"role":""},{"type":"way","ref":157436856,"role":""},{"type":"way","ref":23206887,"role":""},{"type":"way","ref":657081380,"role":""},{"type":"way","ref":948484817,"role":""},{"type":"way","ref":657081379,"role":""},{"type":"way","ref":657083379,"role":""},{"type":"way","ref":657083378,"role":""},{"type":"way","ref":72492956,"role":""},{"type":"way","ref":183763716,"role":""},{"type":"way","ref":497802654,"role":""},{"type":"way","ref":497802655,"role":""},{"type":"way","ref":348402994,"role":""},{"type":"way","ref":497802653,"role":""},{"type":"way","ref":948484813,"role":""},{"type":"way","ref":272353449,"role":"forward"},{"type":"way","ref":497802652,"role":"forward"},{"type":"way","ref":948484811,"role":""},{"type":"way","ref":948484810,"role":""},{"type":"way","ref":136564089,"role":""},{"type":"way","ref":970740538,"role":""},{"type":"way","ref":970740539,"role":""},{"type":"way","ref":433455263,"role":""},{"type":"way","ref":23206893,"role":""},{"type":"way","ref":95506626,"role":""},{"type":"way","ref":61435340,"role":""},{"type":"way","ref":180915274,"role":""},{"type":"way","ref":182691326,"role":""},{"type":"way","ref":95106180,"role":""},{"type":"way","ref":61435342,"role":""},{"type":"way","ref":72492953,"role":""},{"type":"way","ref":827705395,"role":""},{"type":"way","ref":13942938,"role":""},{"type":"way","ref":540048295,"role":""},{"type":"way","ref":249092420,"role":""},{"type":"way","ref":32699077,"role":""},{"type":"way","ref":688103600,"role":""},{"type":"way","ref":654338684,"role":"forward"},{"type":"way","ref":11018710,"role":"forward"},{"type":"way","ref":510825612,"role":"forward"},{"type":"way","ref":70011248,"role":"forward"},{"type":"way","ref":654338685,"role":"forward"},{"type":"way","ref":14626290,"role":""},{"type":"way","ref":70011250,"role":""},{"type":"way","ref":12295471,"role":""},{"type":"way","ref":397097504,"role":""},{"type":"way","ref":12295484,"role":""},{"type":"way","ref":41990436,"role":""},{"type":"way","ref":70011252,"role":""},{"type":"way","ref":61503690,"role":""},{"type":"way","ref":182978284,"role":""},{"type":"way","ref":790820260,"role":"forward"},{"type":"way","ref":592937894,"role":"forward"},{"type":"way","ref":926028042,"role":"forward"},{"type":"way","ref":592937902,"role":"forward"},{"type":"way","ref":592937901,"role":"forward"},{"type":"way","ref":182978255,"role":"forward"},{"type":"way","ref":592937903,"role":"forward"},{"type":"way","ref":12123659,"role":"forward"},{"type":"way","ref":666877213,"role":"forward"},{"type":"way","ref":790820259,"role":"forward"},{"type":"way","ref":510825618,"role":""},{"type":"way","ref":13496412,"role":""},{"type":"way","ref":654338689,"role":""},{"type":"way","ref":740935312,"role":""},{"type":"way","ref":52288671,"role":""},{"type":"way","ref":52288667,"role":""},{"type":"way","ref":12123458,"role":""},{"type":"way","ref":508681905,"role":""},{"type":"way","ref":15071314,"role":""},{"type":"way","ref":61503700,"role":""},{"type":"way","ref":41989874,"role":""},{"type":"way","ref":328002077,"role":""},{"type":"way","ref":396377151,"role":""},{"type":"way","ref":396377150,"role":""},{"type":"way","ref":396377125,"role":""},{"type":"way","ref":328985990,"role":""},{"type":"way","ref":328985992,"role":""},{"type":"way","ref":328985993,"role":""},{"type":"way","ref":328985991,"role":""},{"type":"way","ref":632506298,"role":""},{"type":"way","ref":101191104,"role":""},{"type":"way","ref":499129522,"role":""},{"type":"way","ref":15071174,"role":""},{"type":"way","ref":297023609,"role":""},{"type":"way","ref":297023610,"role":""},{"type":"way","ref":297023608,"role":""},{"type":"way","ref":112695115,"role":""},{"type":"way","ref":584024902,"role":""},{"type":"way","ref":243543197,"role":""},{"type":"way","ref":101191119,"role":"forward"},{"type":"way","ref":173530022,"role":"forward"},{"type":"way","ref":265137637,"role":"forward"},{"type":"way","ref":160627684,"role":"forward"},{"type":"way","ref":657163351,"role":"forward"},{"type":"way","ref":160627682,"role":"forward"},{"type":"way","ref":160632906,"role":"forward"},{"type":"way","ref":176870850,"role":"forward"},{"type":"way","ref":173662701,"role":"forward"},{"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":588764361,"role":""},{"type":"way","ref":981365419,"role":""},{"type":"way","ref":188979882,"role":""},{"type":"way","ref":578030518,"role":""},{"type":"way","ref":124559857,"role":""},{"type":"way","ref":284568605,"role":""},{"type":"way","ref":126405025,"role":""},{"type":"way","ref":188978777,"role":""},{"type":"way","ref":272353445,"role":"forward"},{"type":"way","ref":221443952,"role":"forward"},{"type":"way","ref":172708119,"role":"forward"},{"type":"way","ref":173061662,"role":"forward"},{"type":"way","ref":441663456,"role":"forward"},{"type":"way","ref":160627680,"role":"forward"},{"type":"way","ref":176870852,"role":"forward"},{"type":"way","ref":39588762,"role":"forward"},{"type":"way","ref":172709466,"role":"forward"},{"type":"way","ref":598459103,"role":"forward"},{"type":"way","ref":688054392,"role":"forward"},{"type":"way","ref":155986859,"role":"forward"}],"tags":{"name":"Groene Gordel Brugge","network":"lcn","ref":"GGB","route":"bicycle","type":"route"}},{"type":"relation","id":8369765,"timestamp":"2021-08-23T14:22:45Z","version":19,"changeset":110120188,"user":"Pieter Vander Vennet","uid":3818858,"members":[{"type":"way","ref":539038988,"role":""},{"type":"way","ref":369929367,"role":""},{"type":"way","ref":840241791,"role":""},{"type":"way","ref":488558133,"role":""},{"type":"way","ref":369929894,"role":""},{"type":"way","ref":130473917,"role":""},{"type":"way","ref":509668834,"role":""},{"type":"way","ref":61435323,"role":""},{"type":"way","ref":61435332,"role":""},{"type":"way","ref":337945886,"role":""},{"type":"way","ref":560506339,"role":""},{"type":"way","ref":23775021,"role":""},{"type":"way","ref":23792223,"role":""},{"type":"way","ref":136487196,"role":""},{"type":"way","ref":130473931,"role":""},{"type":"way","ref":34562745,"role":""},{"type":"way","ref":421998791,"role":""},{"type":"way","ref":176380250,"role":""},{"type":"way","ref":116717052,"role":""},{"type":"way","ref":176380249,"role":""},{"type":"way","ref":116717060,"role":""},{"type":"way","ref":27288122,"role":""},{"type":"way","ref":32789519,"role":""},{"type":"way","ref":12126873,"role":""},{"type":"way","ref":689493508,"role":""},{"type":"way","ref":131476435,"role":""},{"type":"way","ref":689494106,"role":""},{"type":"way","ref":165500495,"role":""},{"type":"way","ref":807329786,"role":""},{"type":"way","ref":51570941,"role":""},{"type":"way","ref":422309497,"role":""},{"type":"way","ref":240869981,"role":""},{"type":"way","ref":240869873,"role":""},{"type":"way","ref":240869980,"role":""},{"type":"way","ref":165503767,"role":""},{"type":"way","ref":165503764,"role":""},{"type":"way","ref":421566315,"role":""},{"type":"way","ref":165503768,"role":""},{"type":"way","ref":245236630,"role":""},{"type":"way","ref":658500046,"role":"forward"},{"type":"way","ref":646903393,"role":"forward"},{"type":"way","ref":245236632,"role":"forward"},{"type":"way","ref":245236633,"role":"forward"},{"type":"way","ref":90485426,"role":""},{"type":"way","ref":596073878,"role":""},{"type":"way","ref":10898401,"role":"backward"},{"type":"way","ref":658500044,"role":"forward"},{"type":"way","ref":474253371,"role":"forward"},{"type":"way","ref":474253369,"role":"forward"},{"type":"way","ref":474253376,"role":"forward"},{"type":"way","ref":165845350,"role":"backward"},{"type":"way","ref":130697218,"role":""},{"type":"way","ref":61565721,"role":""},{"type":"way","ref":497202210,"role":""},{"type":"way","ref":130697226,"role":""},{"type":"way","ref":227617858,"role":""},{"type":"way","ref":227617857,"role":""},{"type":"way","ref":681804956,"role":""},{"type":"way","ref":165881675,"role":""},{"type":"way","ref":806146504,"role":""},{"type":"way","ref":806146505,"role":""},{"type":"way","ref":659762284,"role":""}],"tags":{"alt_name":"Fietssnelweg F30 Brugge - Oostende","bicycle:type":"utility","cycle_highway":"yes","cycle_network":"BE-VLG:cycle_highway","name":"F30 Fietssnelweg Brugge - Oostende","network":"ncn","operator":"Provincie West-Vlaanderen","ref":"F30","route":"bicycle","state":"proposed","type":"route","website":"https://fietssnelwegen.be/f30","wikidata":"Q107485732"}},{"type":"relation","id":13060733,"timestamp":"2021-09-19T18:08:57Z","version":5,"changeset":111419581,"user":"L'imaginaire","uid":654234,"members":[{"type":"way","ref":23792223,"role":""},{"type":"way","ref":23775021,"role":""},{"type":"way","ref":560506339,"role":""},{"type":"way","ref":337945886,"role":""},{"type":"way","ref":61435332,"role":""},{"type":"way","ref":61435323,"role":""},{"type":"way","ref":509668834,"role":""},{"type":"way","ref":839596136,"role":""},{"type":"way","ref":840488274,"role":""},{"type":"way","ref":839596137,"role":""},{"type":"way","ref":146172188,"role":""},{"type":"way","ref":749212030,"role":""},{"type":"way","ref":799479035,"role":""},{"type":"way","ref":130473928,"role":""},{"type":"way","ref":61414103,"role":""},{"type":"way","ref":539672618,"role":""},{"type":"way","ref":799479034,"role":""},{"type":"way","ref":539672617,"role":""},{"type":"way","ref":539672616,"role":""},{"type":"way","ref":539671786,"role":""},{"type":"way","ref":172317285,"role":""},{"type":"way","ref":35328157,"role":""},{"type":"way","ref":249119335,"role":""},{"type":"way","ref":584214875,"role":""},{"type":"way","ref":584217798,"role":""},{"type":"way","ref":676801473,"role":""},{"type":"way","ref":456588356,"role":""},{"type":"way","ref":456589109,"role":""},{"type":"way","ref":456588496,"role":""},{"type":"way","ref":487199906,"role":""},{"type":"way","ref":299450868,"role":""},{"type":"way","ref":165548222,"role":""},{"type":"way","ref":4329135,"role":""},{"type":"way","ref":4329771,"role":""},{"type":"way","ref":155149803,"role":""},{"type":"way","ref":305625031,"role":""},{"type":"way","ref":100842624,"role":""},{"type":"way","ref":18102445,"role":""},{"type":"way","ref":541116658,"role":""},{"type":"way","ref":591094005,"role":""},{"type":"way","ref":591094004,"role":""},{"type":"way","ref":184684947,"role":""},{"type":"way","ref":34945088,"role":""},{"type":"way","ref":235195315,"role":""},{"type":"way","ref":497849660,"role":""}],"tags":{"colour":"#e40613","cycle_network":"BE-VLG:icoonroutes","description":"segment 2 van de Kunststedenroute","fixme":"incomplete","from":"Oostende","name":"Kunststedenroute - 02 - Oostende - Brugge","network":"ncn","operator":"Toerisme Vlaanderen","ref":"Kunst","route":"bicycle","to":"Brugge","type":"route","website":"https://www.vlaanderenmetdefiets.be/routes/kunststeden.html","wikidata":"Q106529274","wikipedia":"nl:LF Kunststedenroute"}}]} + ) + Utils.injectJsonDownloadForTests( + "https://www.openstreetmap.org/api/0.6/way/61435332/full" , + {"version":"0.6","generator":"CGImap 0.8.5 (3819319 spike-06.openstreetmap.org)","copyright":"OpenStreetMap and contributors","attribution":"http://www.openstreetmap.org/copyright","license":"http://opendatacommons.org/licenses/odbl/1-0/","elements":[{"type":"node","id":766990985,"lat":51.2169574,"lon":3.2017548,"timestamp":"2016-07-05T22:41:12Z","version":6,"changeset":40511250,"user":"M!dgard","uid":763799},{"type":"node","id":3450208876,"lat":51.2169482,"lon":3.2016802,"timestamp":"2016-07-05T22:41:11Z","version":2,"changeset":40511250,"user":"M!dgard","uid":763799},{"type":"way","id":61435332,"timestamp":"2021-08-21T12:24:13Z","version":8,"changeset":110026637,"user":"Thibault Rommel","uid":5846458,"nodes":[766990985,3450208876],"tags":{"bicycle":"yes","cycleway":"shared_lane","highway":"unclassified","maxspeed":"50","name":"Houtkaai","surface":"asphalt","zone:traffic":"BE-VLG:urban"}}]} + ) + Utils.injectJsonDownloadForTests( + "https://www.openstreetmap.org/api/0.6/way/509668834/full" , + {"version":"0.6","generator":"CGImap 0.8.5 (3735280 spike-06.openstreetmap.org)","copyright":"OpenStreetMap and contributors","attribution":"http://www.openstreetmap.org/copyright","license":"http://opendatacommons.org/licenses/odbl/1-0/","elements":[{"type":"node","id":131917824,"lat":51.2170327,"lon":3.2023577,"timestamp":"2019-09-16T09:48:28Z","version":17,"changeset":74521581,"user":"Peter Elderson","uid":7103674,"tags":{"network:type":"node_network","rcn_ref":"4","rcn_region":"Brugse Ommeland"}},{"type":"node","id":766990983,"lat":51.2170219,"lon":3.2022337,"timestamp":"2021-04-26T15:48:22Z","version":6,"changeset":103647857,"user":"M!dgard","uid":763799},{"type":"way","id":509668834,"timestamp":"2021-08-21T12:24:13Z","version":5,"changeset":110026637,"user":"Thibault Rommel","uid":5846458,"nodes":[131917824,766990983],"tags":{"bicycle":"yes","cycleway":"shared_lane","highway":"residential","lit":"yes","maxspeed":"30","name":"Houtkaai","sidewalk":"both","surface":"paving_stones","zone:maxspeed":"BE:30","zone:traffic":"BE-VLG:urban"}}]} + ) + + + const id = "way/61435323" + const splitPoint: [number, number] = [ 3.2021324336528774, 51.2170001600597] + const splitter = new SplitAction(id, [splitPoint]) + const changeDescription = await splitter.CreateChangeDescriptions(new Changes()) + + // Should be a new node + equal(changeDescription[0].type ,"node") + equal(changeDescription[3].type , "relation") + } + + private static async splitWithPointReuse(): Promise { + // Lets split road near an already existing point https://www.openstreetmap.org/way/295132739 + const id = "way/295132739" + const splitPoint: [number, number] = [3.2451081275939937, 51.18116898253599] + const splitter = new SplitAction(id, [splitPoint]) + const changeDescription = await splitter.CreateChangeDescriptions(new Changes()) + + equal(2, changeDescription.length) + const ch0 = changeDescription[0] + const ch1 = changeDescription[1] + const nodes0: number[] = ch0.changes["nodes"] + const nodes1: number[] = ch1.changes["nodes"] + equal(ch0.type, "way") + equal(ch1.type, "way") + equal(nodes0[nodes0.length - 1], nodes1[0]) + equal(3208166179, nodes1[0]) + } + + constructor() { + super("splitaction", [ + ["split 295132739", + () => SplitActionSpec.split().then(_ => console.log("OK"))], + ["split 295132739 on already existing node", + () => SplitActionSpec.splitWithPointReuse().then(_ => console.log("OK"))], + ["split 61435323 on already existing node", + () => SplitActionSpec.SplitHoutkaai().then(_ => console.log("OK"))] + ]); + } +} \ No newline at end of file diff --git a/test/TestAll.ts b/test/TestAll.ts index bd0ec5657..1a25a0afc 100644 --- a/test/TestAll.ts +++ b/test/TestAll.ts @@ -8,7 +8,8 @@ import OsmObjectSpec from "./OsmObject.spec"; import ScriptUtils from "../scripts/ScriptUtils"; import UnitsSpec from "./Units.spec"; import RelationSplitHandlerSpec from "./RelationSplitHandler.spec"; - +import SplitActionSpec from "./SplitAction.spec"; +import {Utils} from "../Utils"; ScriptUtils.fixUtils() @@ -21,9 +22,19 @@ const allTests = [ new ThemeSpec(), new UtilsSpec(), 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] args.splice(0, 2) args = args.map(a => a.toLowerCase()) @@ -34,15 +45,15 @@ if (args.length > 0) { testsToRun = allTests.filter(t => args.indexOf(t.name) >= 0) } -if(testsToRun.length == 0){ +if (testsToRun.length == 0) { throw "No tests found" } -for (let i = 0; i < testsToRun.length; i++){ +for (let i = 0; i < testsToRun.length; 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() ?? [])) - + console.log("OK!") } if (allFailures.length > 0) { for (const failure of allFailures) { @@ -50,4 +61,4 @@ if (allFailures.length > 0) { } 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(", "))