More work on splitting roads, WIP; refactoring tests

This commit is contained in:
Pieter Vander Vennet 2021-09-22 05:02:09 +02:00
parent e374bb355c
commit 1f93923820
62 changed files with 1163 additions and 823 deletions

View file

@ -1,3 +1,5 @@
import {OsmNode, OsmRelation, OsmWay} from "../OsmObject";
/**
* Represents a single change to an object
*/
@ -29,8 +31,8 @@ export interface ChangeDescription {
lat: number,
lon: number
} | {
// Coordinates are only used for rendering. They should be lon, lat
locations: [number, number][]
// Coordinates are only used for rendering. They should be LAT, LON
coordinates: [number, number][]
nodes: number[],
} | {
members: { type: "node" | "way" | "relation", ref: number, role: string }[]
@ -40,6 +42,26 @@ export interface ChangeDescription {
Set to delete the object
*/
doDelete?: boolean
}
export class ChangeDescriptionTools{
public static getGeojsonGeometry(change: ChangeDescription): any{
switch (change.type) {
case "node":
const n = new OsmNode(change.id)
n.lat = change.changes["lat"]
n.lon = change.changes["lon"]
return n.asGeoJson().geometry
case "way":
const w = new OsmWay(change.id)
w.nodes = change.changes["nodes"]
w.coordinates = change.changes["coordinates"].map(coor => coor.reverse())
return w.asGeoJson().geometry
case "relation":
const r = new OsmRelation(change.id)
r.members = change.changes["members"]
return r.asGeoJson().geometry
}
}
}

View file

@ -37,7 +37,7 @@ export default class ChangeTagAction extends OsmChangeAction {
return {k: key.trim(), v: value.trim()};
}
CreateChangeDescriptions(changes: Changes): ChangeDescription [] {
async CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]> {
const changedTags: { k: string, v: string }[] = this._tagsFilter.asChange(this._currentTags).map(ChangeTagAction.checkChange)
const typeId = this._elementId.split("/")
const type = typeId[0]

View file

@ -27,7 +27,7 @@ export default class CreateNewNodeAction extends OsmChangeAction {
this._reusePointDistance = options?.reusePointWithinMeters ?? 1
}
CreateChangeDescriptions(changes: Changes): ChangeDescription[] {
async CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]> {
const id = changes.getNewID()
const properties = {
id: "node/" + id
@ -97,7 +97,7 @@ export default class CreateNewNodeAction extends OsmChangeAction {
type: "way",
id: this._snapOnto.id,
changes: {
locations: locations,
coordinates: locations,
nodes: ids
}
}

View file

@ -159,7 +159,7 @@ export default class DeleteAction {
canBeDeleted: false,
reason: t.notEnoughExperience
})
return;
return true; // unregister this caller!
}
if (!useTheInternet) {
@ -167,13 +167,14 @@ export default class DeleteAction {
}
// All right! We have arrived at a point that we should query OSM again to check that the point isn't a part of ways or relations
OsmObject.DownloadReferencingRelations(id).addCallbackAndRunD(rels => {
OsmObject.DownloadReferencingRelations(id).then(rels => {
hasRelations.setData(rels.length > 0)
})
OsmObject.DownloadReferencingWays(id).addCallbackAndRunD(ways => {
OsmObject.DownloadReferencingWays(id).then(ways => {
hasWays.setData(ways.length > 0)
})
return true; // unregister to only run once
})

View file

@ -17,7 +17,7 @@ export default abstract class OsmChangeAction {
return this.CreateChangeDescriptions(changes)
}
protected abstract CreateChangeDescriptions(changes: Changes): ChangeDescription[]
protected abstract CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]>
}

View file

@ -0,0 +1,142 @@
import OsmChangeAction from "./OsmChangeAction";
import {Changes} from "../Changes";
import {ChangeDescription} from "./ChangeDescription";
import {OsmObject, OsmRelation, OsmWay} from "../OsmObject";
export interface RelationSplitInput {
relation: OsmRelation,
originalWayId: number,
allWayIdsInOrder: number[],
originalNodes: number[],
allWaysNodesInOrder: number[][]
}
/**
* 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 {
constructor(input: RelationSplitInput) {
super()
}
async CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]> {
return [];
}
}
/**
* A simple strategy to split relations:
* -> Download the way members just before and just after the original way
* -> Make sure they are still aligned
*
* Note that the feature might appear multiple times.
*/
export class InPlaceReplacedmentRTSH extends OsmChangeAction {
private readonly _input: RelationSplitInput;
constructor(input: RelationSplitInput) {
super();
this._input = input;
}
/**
* Returns which node should border the member at the given index
*/
private async targetNodeAt(i: number, first: boolean) {
const member = this._input.relation.members[i]
if (member === undefined) {
return undefined
}
if (member.type === "node") {
return member.ref
}
if (member.type === "way") {
const osmWay = <OsmWay>await OsmObject.DownloadObjectAsync("way/" + member.ref)
const nodes = osmWay.nodes
if (first) {
return nodes[0]
} else {
return nodes[nodes.length - 1]
}
}
if (member.type === "relation") {
return undefined
}
return undefined;
}
async CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]> {
const wayId = this._input.originalWayId
const relation = this._input.relation
const members = relation.members
const originalNodes = this._input.originalNodes;
const firstNode = originalNodes[0]
const lastNode = originalNodes[originalNodes.length - 1]
const newMembers: { type: "node" | "way" | "relation", ref: number, role: string }[] = []
for (let i = 0; i < members.length; i++) {
const member = members[i];
if (member.type !== "way" || member.ref !== wayId) {
newMembers.push(member)
continue;
}
const nodeIdBefore = await this.targetNodeAt(i - 1, false)
const nodeIdAfter = await this.targetNodeAt(i + 1, true)
const firstNodeMatches = nodeIdBefore === undefined || nodeIdBefore === firstNode
const lastNodeMatches =nodeIdAfter === undefined || nodeIdAfter === lastNode
if (firstNodeMatches && lastNodeMatches) {
// We have a classic situation, forward situation
for (const wId of this._input.allWayIdsInOrder) {
newMembers.push({
ref: wId,
type: "way",
role: member.role
})
}
continue;
}
const firstNodeMatchesRev = nodeIdBefore === undefined || nodeIdBefore === lastNode
const lastNodeMatchesRev =nodeIdAfter === undefined || nodeIdAfter === firstNode
if (firstNodeMatchesRev || lastNodeMatchesRev) {
// We (probably) have a reversed situation, backward situation
for (let i1 = this._input.allWayIdsInOrder.length - 1; i1 >= 0; i1--){
// Iterate BACKWARDS
const wId = this._input.allWayIdsInOrder[i1];
newMembers.push({
ref: wId,
type: "way",
role: member.role
})
}
continue;
}
// Euhm, allright... Something weird is going on, but let's not care too much
// Lets pretend this is forward going
for (const wId of this._input.allWayIdsInOrder) {
newMembers.push({
ref: wId,
type: "way",
role: member.role
})
}
}
return [{
id: relation.id,
type: "relation",
changes: {members: newMembers}
}];
}
}

View file

@ -1,20 +0,0 @@
/**
* The logic to handle relations after a way within
*/
import OsmChangeAction from "./OsmChangeAction";
import {Changes} from "../Changes";
import {ChangeDescription} from "./ChangeDescription";
import {OsmRelation} from "../OsmObject";
export default class RelationSplitlHandler extends OsmChangeAction {
constructor(partOf: OsmRelation[], newWayIds: number[], originalNodes: number[]) {
super()
}
CreateChangeDescriptions(changes: Changes): ChangeDescription[] {
return [];
}
}

View file

@ -1,9 +1,9 @@
import {OsmRelation, OsmWay} from "../OsmObject";
import {OsmObject, OsmWay} from "../OsmObject";
import {Changes} from "../Changes";
import {GeoOperations} from "../../GeoOperations";
import OsmChangeAction from "./OsmChangeAction";
import {ChangeDescription} from "./ChangeDescription";
import RelationSplitlHandler from "./RelationSplitlHandler";
import RelationSplitHandler from "./RelationSplitHandler";
interface SplitInfo {
originalIndex?: number, // or negative for new elements
@ -12,17 +12,13 @@ interface SplitInfo {
}
export default class SplitAction extends OsmChangeAction {
private readonly roadObject: any;
private readonly osmWay: OsmWay;
private _partOf: OsmRelation[];
private readonly _splitPoints: any[];
private readonly wayId: string;
private readonly _splitPointsCoordinates: [number, number] []// lon, lat
constructor(osmWay: OsmWay, wayGeoJson: any, partOf: OsmRelation[], splitPoints: any[]) {
constructor(wayId: string, splitPointCoordinates: [number, number][]) {
super()
this.osmWay = osmWay;
this.roadObject = wayGeoJson;
this._partOf = partOf;
this._splitPoints = splitPoints;
this.wayId = wayId;
this._splitPointsCoordinates = splitPointCoordinates
}
private static SegmentSplitInfo(splitInfo: SplitInfo[]): SplitInfo[][] {
@ -42,26 +38,17 @@ export default class SplitAction extends OsmChangeAction {
return wayParts.filter(wp => wp.length > 0)
}
CreateChangeDescriptions(changes: Changes): ChangeDescription[] {
const splitPoints = this._splitPoints
// We mark the new split points with a new id
console.log(splitPoints)
for (const splitPoint of splitPoints) {
splitPoint.properties["_is_split_point"] = true
}
async CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]> {
const self = this;
const partOf = this._partOf
const originalElement = this.osmWay
const originalNodes = this.osmWay.nodes;
const originalElement = <OsmWay>await OsmObject.DownloadObjectAsync(this.wayId)
const originalNodes = originalElement.nodes;
// First, calculate splitpoints and remove points close to one another
const splitInfo = self.CalculateSplitCoordinates(splitPoints)
const splitInfo = self.CalculateSplitCoordinates(originalElement)
// Now we have a list with e.g.
// [ { originalIndex: 0}, {originalIndex: 1, doSplit: true}, {originalIndex: 2}, {originalIndex: undefined, doSplit: true}, {originalIndex: 3}]
// Lets change 'originalIndex' to the actual node id first:
// Lets change 'originalIndex' to the actual node id first (or assign a new id if needed):
for (const element of splitInfo) {
if (element.originalIndex >= 0) {
element.originalIndex = originalElement.nodes[element.originalIndex]
@ -102,25 +89,30 @@ export default class SplitAction extends OsmChangeAction {
})
}
const newWayIds: number[] = []
// The ids of all the ways (including the original)
const allWayIdsInOrder: number[] = []
const allWaysNodesInOrder: number[][] = []
// Lets create OsmWays based on them
for (const wayPart of wayParts) {
let isOriginal = wayPart === longest
if (isOriginal) {
// We change the actual element!
const nodeIds = wayPart.map(p => p.originalIndex)
changeDescription.push({
type: "way",
id: originalElement.id,
changes: {
locations: wayPart.map(p => p.lngLat),
nodes: wayPart.map(p => p.originalIndex)
coordinates: wayPart.map(p => p.lngLat),
nodes: nodeIds
}
})
allWayIdsInOrder.push(originalElement.id)
allWaysNodesInOrder.push(nodeIds)
} else {
let id = changes.getNewID();
newWayIds.push(id)
// Copy the tags from the original object onto the new
const kv = []
for (const k in originalElement.tags) {
if (!originalElement.tags.hasOwnProperty(k)) {
@ -131,22 +123,35 @@ export default class SplitAction extends OsmChangeAction {
}
kv.push({k: k, v: originalElement.tags[k]})
}
const nodeIds = wayPart.map(p => p.originalIndex)
changeDescription.push({
type: "way",
id: id,
tags: kv,
changes: {
locations: wayPart.map(p => p.lngLat),
nodes: wayPart.map(p => p.originalIndex)
coordinates: wayPart.map(p => p.lngLat),
nodes: nodeIds
}
})
}
allWayIdsInOrder.push(id)
allWaysNodesInOrder.push(nodeIds)
}
}
// At last, we still have to check that we aren't part of a relation...
// At least, the order of the ways is identical, so we can keep the same roles
changeDescription.push(...new RelationSplitlHandler(partOf, newWayIds, originalNodes).CreateChangeDescriptions(changes))
const relations = await OsmObject.DownloadReferencingRelations(this.wayId)
for (const relation of relations) {
const changDescrs = await new RelationSplitHandler({
relation: relation,
allWayIdsInOrder: allWayIdsInOrder,
originalNodes: originalNodes,
allWaysNodesInOrder: allWaysNodesInOrder,
originalWayId: originalElement.id
}).CreateChangeDescriptions(changes)
changeDescription.push(...changDescrs)
}
// And we have our objects!
// Time to upload
@ -158,75 +163,96 @@ export default class SplitAction extends OsmChangeAction {
* Calculates the actual points to split
* If another point is closer then ~5m, we reuse that point
*/
private CalculateSplitCoordinates(
splitPoints: any[],
toleranceInM = 5): SplitInfo[] {
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 allPoints: {
coordinates: [number, number],
isSplitPoint: boolean,
originalIndex?: number, // Original index
dist: number, // Distance from the nearest point on the original line
location: number // Distance from the start of the way
}[] = this._splitPointsCoordinates.map(c => {
// From the turf.js docs:
// The properties object will contain three values:
// - `index`: closest point was found on nth line part,
// - `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)
return ({
coordinates: c,
isSplitPoint: true,
dist: projected.properties.dist,
location: projected.properties.location
});
})
const allPoints = [...splitPoints];
// We have a bunch of coordinates here: [ [lat, lon], [lat, lon], ...] ...
const originalPoints: [number, number][] = this.roadObject.geometry.coordinates
// We project them onto the line (which should yield pretty much the same point
// We have a bunch of coordinates here: [ [lon, lon], [lat, lon], ...] ...
// We project them onto the line (which should yield pretty much the same point and add them to allPoints
for (let i = 0; i < originalPoints.length; i++) {
let originalPoint = originalPoints[i];
let projected = GeoOperations.nearestPoint(this.roadObject, originalPoint)
projected.properties["_is_split_point"] = false
projected.properties["_original_index"] = i
allPoints.push(projected)
let projected = GeoOperations.nearestPoint(wayGeoJson, originalPoint)
allPoints.push({
coordinates: originalPoint,
isSplitPoint: false,
location: projected.properties.location,
originalIndex: i,
dist: projected.properties.dist
})
}
// At this point, we have a list of both the split point and the old points, with some properties to discriminate between them
// We sort this list so that the new points are at the same location
allPoints.sort((a, b) => a.properties.location - b.properties.location)
allPoints.sort((a, b) => a.location - b.location)
// When this is done, we check that no now point is too close to an already existing point and no very small segments get created
/* for (let i = allPoints.length - 1; i > 0; i--) {
const point = allPoints[i];
if (point.properties._original_index !== undefined) {
// This point is already in OSM - we have to keep it!
continue;
}
if (i != allPoints.length - 1) {
const prevPoint = allPoints[i + 1]
const diff = Math.abs(point.properties.location - prevPoint.properties.location) * 1000
if (diff <= toleranceInM) {
// To close to the previous point! We delete this point...
allPoints.splice(i, 1)
// ... and mark the previous point as a split point
prevPoint.properties._is_split_point = true
continue;
}
}
if (i > 0) {
const nextPoint = allPoints[i - 1]
const diff = Math.abs(point.properties.location - nextPoint.properties.location) * 1000
if (diff <= toleranceInM) {
// To close to the next point! We delete this point...
allPoints.splice(i, 1)
// ... and mark the next point as a split point
nextPoint.properties._is_split_point = true
// noinspection UnnecessaryContinueJS
continue;
}
}
// We don't have to remove this point...
}*/
for (let i = allPoints.length - 2; i >= 1; i--) {
// We 'merge' points with already existing nodes if they are close enough to avoid closeby elements
// Note the loop bounds: we skip the first two and last two elements:
// The first and last element are always part of the original way and should be kept
// Furthermore, we run in reverse order as we'll delete elements on the go
const point = allPoints[i]
if (point.originalIndex !== undefined) {
// We keep the original points
continue
}
if (point.dist * 1000 >= toleranceInM) {
// No need to remove this one
continue
}
// At this point, 'dist' told us the point is pretty close to an already existing point.
// Lets see which (already existing) point is closer and mark it as splitpoint
const nextPoint = allPoints[i + 1]
const prevPoint = allPoints[i - 1]
const distToNext = nextPoint.location - point.location
const distToPrev = prevPoint.location - point.location
let closest = nextPoint
if (distToNext > distToPrev) {
closest = prevPoint
}
// Ok, we have a closest point!
closest.isSplitPoint = true;
allPoints.splice(i, 1)
}
const splitInfo: SplitInfo[] = []
let nextId = -1
let nextId = -1 // Note: these IDs are overwritten later on, no need to use a global counter here
for (const p of allPoints) {
let index = p.properties._original_index
let index = p.originalIndex
if (index === undefined) {
index = nextId;
nextId--;
}
const splitInfoElement = {
originalIndex: index,
lngLat: p.geometry.coordinates,
doSplit: p.properties._is_split_point
lngLat: p.coordinates,
doSplit: p.isSplitPoint
}
splitInfo.push(splitInfoElement)
}

View file

@ -21,12 +21,15 @@ export class Changes {
*/
public features = new UIEventSource<{ feature: any, freshness: Date }[]>([]);
public readonly pendingChanges = LocalStorageSource.GetParsed<ChangeDescription[]>("pending-changes", [])
public readonly pendingChanges: UIEventSource<ChangeDescription[]> = LocalStorageSource.GetParsed<ChangeDescription[]>("pending-changes", [])
public readonly allChanges = new UIEventSource<ChangeDescription[]>(undefined)
private readonly isUploading = new UIEventSource(false);
private readonly previouslyCreated: OsmObject[] = []
constructor() {
// We keep track of all changes just as well
this.allChanges.setData([...this.pendingChanges.data])
}
private static createChangesetFor(csId: string,
@ -146,10 +149,13 @@ export class Changes {
}
public applyAction(action: OsmChangeAction) {
const changes = action.Perform(this)
console.log("Received changes:", changes)
this.pendingChanges.data.push(...changes);
this.pendingChanges.ping();
action.Perform(this).then(changes => {
console.log("Received changes:", changes)
this.pendingChanges.data.push(...changes);
this.pendingChanges.ping();
this.allChanges.data.push(...changes)
this.allChanges.ping()
})
}
private CreateChangesetObjects(changes: ChangeDescription[], downloadedOsmObjects: OsmObject[]): {

View file

@ -1,6 +1,7 @@
import {Utils} from "../../Utils";
import * as polygon_features from "../../assets/polygon-features.json";
import {UIEventSource} from "../UIEventSource";
import {BBox} from "../GeoOperations";
export abstract class OsmObject {
@ -9,11 +10,12 @@ export abstract class OsmObject {
protected static backendURL = OsmObject.defaultBackend;
private static polygonFeatures = OsmObject.constructPolygonFeatures()
private static objectCache = new Map<string, UIEventSource<OsmObject>>();
private static referencingWaysCache = new Map<string, UIEventSource<OsmWay[]>>();
private static referencingRelationsCache = new Map<string, UIEventSource<OsmRelation[]>>();
private static historyCache = new Map<string, UIEventSource<OsmObject[]>>();
type: string;
id: number;
/**
* The OSM tags as simple object
*/
tags: {} = {};
version: number;
public changed: boolean = false;
@ -37,7 +39,7 @@ export abstract class OsmObject {
this.backendURL = url;
}
static DownloadObject(id: string, forceRefresh: boolean = false): UIEventSource<OsmObject> {
public static DownloadObject(id: string, forceRefresh: boolean = false): UIEventSource<OsmObject> {
let src: UIEventSource<OsmObject>;
if (OsmObject.objectCache.has(id)) {
src = OsmObject.objectCache.get(id)
@ -47,80 +49,62 @@ export abstract class OsmObject {
return src;
}
} else {
src = new UIEventSource<OsmObject>(undefined)
src = UIEventSource.FromPromise(OsmObject.DownloadObjectAsync(id))
}
OsmObject.objectCache.set(id, src);
return src;
}
static async DownloadObjectAsync(id: string): Promise<OsmObject> {
const splitted = id.split("/");
const type = splitted[0];
const idN = Number(splitted[1]);
if (idN < 0) {
return;
return undefined;
}
OsmObject.objectCache.set(id, src);
const newContinuation = (element: OsmObject) => {
src.setData(element)
}
switch (type) {
case("node"):
new OsmNode(idN).Download(newContinuation);
break;
return await new OsmNode(idN).Download();
case("way"):
new OsmWay(idN).Download(newContinuation);
break;
return await new OsmWay(idN).Download();
case("relation"):
new OsmRelation(idN).Download(newContinuation);
break;
return await new OsmRelation(idN).Download();
default:
throw "Invalid object type:" + type + id;
throw ("Invalid object type:" + type + id);
}
return src;
}
/**
* Downloads the ways that are using this node.
* Beware: their geometry will be incomplete!
*/
public static DownloadReferencingWays(id: string): UIEventSource<OsmWay[]> {
if (OsmObject.referencingWaysCache.has(id)) {
return OsmObject.referencingWaysCache.get(id);
}
const waysSrc = new UIEventSource<OsmWay[]>([])
OsmObject.referencingWaysCache.set(id, waysSrc);
Utils.downloadJson(`${OsmObject.backendURL}api/0.6/${id}/ways`)
.then(data => {
const ways = data.elements.map(wayInfo => {
public static DownloadReferencingWays(id: string): Promise<OsmWay[]> {
return Utils.downloadJson(`${OsmObject.backendURL}api/0.6/${id}/ways`).then(
data => {
return data.elements.map(wayInfo => {
const way = new OsmWay(wayInfo.id)
way.LoadData(wayInfo)
return way
})
waysSrc.setData(ways)
})
return waysSrc;
}
)
}
/**
* Downloads the relations that are using this feature.
* Beware: their geometry will be incomplete!
*/
public static DownloadReferencingRelations(id: string): UIEventSource<OsmRelation[]> {
if (OsmObject.referencingRelationsCache.has(id)) {
return OsmObject.referencingRelationsCache.get(id);
}
const relsSrc = new UIEventSource<OsmRelation[]>(undefined)
OsmObject.referencingRelationsCache.set(id, relsSrc);
Utils.downloadJson(`${OsmObject.backendURL}api/0.6/${id}/relations`)
.then(data => {
const rels = data.elements.map(wayInfo => {
const rel = new OsmRelation(wayInfo.id)
rel.LoadData(wayInfo)
rel.SaveExtraData(wayInfo)
return rel
})
relsSrc.setData(rels)
})
return relsSrc;
public static async DownloadReferencingRelations(id: string): Promise<OsmRelation[]> {
const data = await Utils.downloadJson(`${OsmObject.backendURL}api/0.6/${id}/relations`)
return data.elements.map(wayInfo => {
const rel = new OsmRelation(wayInfo.id)
rel.LoadData(wayInfo)
rel.SaveExtraData(wayInfo)
return rel
})
}
public static DownloadHistory(id: string): UIEventSource<OsmObject []> {
@ -158,18 +142,11 @@ export abstract class OsmObject {
}
// bounds should be: [[maxlat, minlon], [minlat, maxlon]] (same as Utils.tile_bounds)
public static LoadArea(bounds: [[number, number], [number, number]], callback: (objects: OsmObject[]) => void) {
const minlon = bounds[0][1]
const maxlon = bounds[1][1]
const minlat = bounds[1][0]
const maxlat = bounds[0][0];
const url = `${OsmObject.backendURL}api/0.6/map.json?bbox=${minlon},${minlat},${maxlon},${maxlat}`
Utils.downloadJson(url).then(data => {
const elements: any[] = data.elements;
const objects = OsmObject.ParseObjects(elements)
callback(objects);
})
public static async LoadArea(bbox: BBox): Promise<OsmObject[]> {
const url = `${OsmObject.backendURL}api/0.6/map.json?bbox=${bbox.minLon},${bbox.minLat},${bbox.maxLon},${bbox.maxLat}`
const data = await Utils.downloadJson(url)
const elements: any[] = data.elements;
return OsmObject.ParseObjects(elements);
}
public static DownloadAll(neededIds, forceRefresh = true): UIEventSource<OsmObject[]> {
@ -283,39 +260,34 @@ export abstract class OsmObject {
return tags;
}
Download(continuation: ((element: OsmObject, meta: OsmObjectMeta) => void)) {
/**
* 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}`;
Utils.downloadJson(url).then(data => {
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);
const meta = {
"_last_edit:contributor": element.user,
"_last_edit:contributor:uid": element.uid,
"_last_edit:changeset": element.changeset,
"_last_edit:timestamp": new Date(element.timestamp),
"_version_number": element.version
}
if (OsmObject.backendURL !== OsmObject.defaultBackend) {
self.tags["_backend"] = OsmObject.backendURL
meta["_backend"] = OsmObject.backendURL;
}
continuation(self, meta);
return this;
}
);
return this;
}
@ -389,18 +361,10 @@ export class OsmNode extends OsmObject {
}
}
export interface OsmObjectMeta {
"_last_edit:contributor": string,
"_last_edit:contributor:uid": number,
"_last_edit:changeset": number,
"_last_edit:timestamp": Date,
"_version_number": number
}
export class OsmWay extends OsmObject {
nodes: number[];
// The coordinates of the way, [lat, lon][]
coordinates: [number, number][] = []
lat: number;
lon: number;
@ -455,12 +419,16 @@ export class OsmWay extends OsmObject {
}
public asGeoJson() {
let coordinates : ([number, number][] | [number, number][][]) = this.coordinates.map(c => <[number, number]>c.reverse());
if(this.isPolygon()){
coordinates = [coordinates]
}
return {
"type": "Feature",
"properties": this.tags,
"geometry": {
"type": this.isPolygon() ? "Polygon" : "LineString",
"coordinates": this.coordinates.map(c => [c[1], c[0]])
"coordinates": coordinates
}
}
}
@ -511,7 +479,7 @@ ${members}${tags} </relation>
this.members = element.members;
}
asGeoJson() {
asGeoJson(): any {
throw "Not Implemented"
}
}