forked from MapComplete/MapComplete
refactoring: more state splitting, basic layoutFeatureSource
This commit is contained in:
parent
8e2f04c0d0
commit
b94a8f5745
54 changed files with 1067 additions and 1969 deletions
|
@ -6,19 +6,18 @@ import { ChangeDescription, ChangeDescriptionTools } from "./Actions/ChangeDescr
|
|||
import { Utils } from "../../Utils"
|
||||
import { LocalStorageSource } from "../Web/LocalStorageSource"
|
||||
import SimpleMetaTagger from "../SimpleMetaTagger"
|
||||
import FeatureSource from "../FeatureSource/FeatureSource"
|
||||
import { ElementStorage } from "../ElementStorage"
|
||||
import FeatureSource, { IndexedFeatureSource } from "../FeatureSource/FeatureSource"
|
||||
import { GeoLocationPointProperties } from "../State/GeoLocationState"
|
||||
import { GeoOperations } from "../GeoOperations"
|
||||
import { ChangesetHandler, ChangesetTag } from "./ChangesetHandler"
|
||||
import { OsmConnection } from "./OsmConnection"
|
||||
import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore"
|
||||
|
||||
/**
|
||||
* Handles all changes made to OSM.
|
||||
* Needs an authenticator via OsmConnection
|
||||
*/
|
||||
export class Changes {
|
||||
public readonly name = "Newly added features"
|
||||
/**
|
||||
* All the newly created features as featureSource + all the modified features
|
||||
*/
|
||||
|
@ -26,7 +25,7 @@ export class Changes {
|
|||
public readonly pendingChanges: UIEventSource<ChangeDescription[]> =
|
||||
LocalStorageSource.GetParsed<ChangeDescription[]>("pending-changes", [])
|
||||
public readonly allChanges = new UIEventSource<ChangeDescription[]>(undefined)
|
||||
public readonly state: { allElements: ElementStorage; osmConnection: OsmConnection }
|
||||
public readonly state: { allElements: IndexedFeatureSource; osmConnection: OsmConnection }
|
||||
public readonly extraComment: UIEventSource<string> = new UIEventSource(undefined)
|
||||
|
||||
private readonly historicalUserLocations: FeatureSource
|
||||
|
@ -38,7 +37,9 @@ export class Changes {
|
|||
|
||||
constructor(
|
||||
state?: {
|
||||
allElements: ElementStorage
|
||||
dryRun: UIEventSource<boolean>
|
||||
allElements: IndexedFeatureSource
|
||||
featurePropertiesStore: FeaturePropertiesStore
|
||||
osmConnection: OsmConnection
|
||||
historicalUserLocations: FeatureSource
|
||||
},
|
||||
|
@ -50,8 +51,10 @@ export class Changes {
|
|||
// If a pending change contains a negative ID, we save that
|
||||
this._nextId = Math.min(-1, ...(this.pendingChanges.data?.map((pch) => pch.id) ?? []))
|
||||
this.state = state
|
||||
this._changesetHandler = state?.osmConnection?.CreateChangesetHandler(
|
||||
state.allElements,
|
||||
this._changesetHandler = new ChangesetHandler(
|
||||
state.dryRun,
|
||||
state.osmConnection,
|
||||
state.featurePropertiesStore,
|
||||
this
|
||||
)
|
||||
this.historicalUserLocations = state.historicalUserLocations
|
||||
|
@ -187,7 +190,7 @@ export class Changes {
|
|||
|
||||
const changedObjectCoordinates: [number, number][] = []
|
||||
|
||||
const feature = this.state.allElements.ContainingFeatures.get(change.mainObjectId)
|
||||
const feature = this.state.allElements.featuresById.data.get(change.mainObjectId)
|
||||
if (feature !== undefined) {
|
||||
changedObjectCoordinates.push(GeoOperations.centerpointCoordinates(feature))
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import escapeHtml from "escape-html"
|
||||
import UserDetails, { OsmConnection } from "./OsmConnection"
|
||||
import { UIEventSource } from "../UIEventSource"
|
||||
import { ElementStorage } from "../ElementStorage"
|
||||
import Locale from "../../UI/i18n/Locale"
|
||||
import Constants from "../../Models/Constants"
|
||||
import { Changes } from "./Changes"
|
||||
|
@ -14,12 +13,11 @@ export interface ChangesetTag {
|
|||
}
|
||||
|
||||
export class ChangesetHandler {
|
||||
private readonly allElements: ElementStorage
|
||||
private readonly allElements: { addAlias: (id0: String, id1: string) => void }
|
||||
private osmConnection: OsmConnection
|
||||
private readonly changes: Changes
|
||||
private readonly _dryRun: UIEventSource<boolean>
|
||||
private readonly userDetails: UIEventSource<UserDetails>
|
||||
private readonly auth: any
|
||||
private readonly backend: string
|
||||
|
||||
/**
|
||||
|
@ -28,20 +26,11 @@ export class ChangesetHandler {
|
|||
*/
|
||||
private readonly _remappings = new Map<string, string>()
|
||||
|
||||
/**
|
||||
* Use 'osmConnection.CreateChangesetHandler' instead
|
||||
* @param dryRun
|
||||
* @param osmConnection
|
||||
* @param allElements
|
||||
* @param changes
|
||||
* @param auth
|
||||
*/
|
||||
constructor(
|
||||
dryRun: UIEventSource<boolean>,
|
||||
osmConnection: OsmConnection,
|
||||
allElements: ElementStorage,
|
||||
changes: Changes,
|
||||
auth
|
||||
allElements: { addAlias: (id0: String, id1: string) => void },
|
||||
changes: Changes
|
||||
) {
|
||||
this.osmConnection = osmConnection
|
||||
this.allElements = allElements
|
||||
|
@ -49,7 +38,6 @@ export class ChangesetHandler {
|
|||
this._dryRun = dryRun
|
||||
this.userDetails = osmConnection.userDetails
|
||||
this.backend = osmConnection._oauth_config.url
|
||||
this.auth = auth
|
||||
|
||||
if (dryRun) {
|
||||
console.log("DRYRUN ENABLED")
|
||||
|
@ -61,7 +49,7 @@ export class ChangesetHandler {
|
|||
*
|
||||
* ChangesetHandler.removeDuplicateMetaTags([{key: "k", value: "v"}, {key: "k0", value: "v0"}, {key: "k", value:"v"}] // => [{key: "k", value: "v"}, {key: "k0", value: "v0"}]
|
||||
*/
|
||||
public static removeDuplicateMetaTags(extraMetaTags: ChangesetTag[]): ChangesetTag[] {
|
||||
private static removeDuplicateMetaTags(extraMetaTags: ChangesetTag[]): ChangesetTag[] {
|
||||
const r: ChangesetTag[] = []
|
||||
const seen = new Set<string>()
|
||||
for (const extraMetaTag of extraMetaTags) {
|
||||
|
@ -82,7 +70,7 @@ export class ChangesetHandler {
|
|||
* @param rewriteIds
|
||||
* @private
|
||||
*/
|
||||
static rewriteMetaTags(extraMetaTags: ChangesetTag[], rewriteIds: Map<string, string>) {
|
||||
private static rewriteMetaTags(extraMetaTags: ChangesetTag[], rewriteIds: Map<string, string>) {
|
||||
let hasChange = false
|
||||
for (const tag of extraMetaTags) {
|
||||
const match = tag.key.match(/^([a-zA-Z0-9_]+):(node\/-[0-9])$/)
|
||||
|
@ -198,7 +186,7 @@ export class ChangesetHandler {
|
|||
* @param rewriteIds: the mapping of ids
|
||||
* @param oldChangesetMeta: the metadata-object of the already existing changeset
|
||||
*/
|
||||
public RewriteTagsOf(
|
||||
private RewriteTagsOf(
|
||||
extraMetaTags: ChangesetTag[],
|
||||
rewriteIds: Map<string, string>,
|
||||
oldChangesetMeta: {
|
||||
|
@ -318,28 +306,14 @@ export class ChangesetHandler {
|
|||
}
|
||||
|
||||
private async CloseChangeset(changesetId: number = undefined): Promise<void> {
|
||||
const self = this
|
||||
return new Promise<void>(function (resolve, reject) {
|
||||
if (changesetId === undefined) {
|
||||
return
|
||||
}
|
||||
self.auth.xhr(
|
||||
{
|
||||
method: "PUT",
|
||||
path: "/api/0.6/changeset/" + changesetId + "/close",
|
||||
},
|
||||
function (err, response) {
|
||||
if (response == null) {
|
||||
console.log("err", err)
|
||||
}
|
||||
console.log("Closed changeset ", changesetId)
|
||||
resolve()
|
||||
}
|
||||
)
|
||||
})
|
||||
if (changesetId === undefined) {
|
||||
return
|
||||
}
|
||||
await this.osmConnection.put("changeset/" + changesetId + "/close")
|
||||
console.log("Closed changeset ", changesetId)
|
||||
}
|
||||
|
||||
async GetChangesetMeta(csId: number): Promise<{
|
||||
private async GetChangesetMeta(csId: number): Promise<{
|
||||
id: number
|
||||
open: boolean
|
||||
uid: number
|
||||
|
@ -358,34 +332,16 @@ export class ChangesetHandler {
|
|||
private async UpdateTags(csId: number, tags: ChangesetTag[]) {
|
||||
tags = ChangesetHandler.removeDuplicateMetaTags(tags)
|
||||
|
||||
const self = this
|
||||
return new Promise<string>(function (resolve, reject) {
|
||||
tags = Utils.NoNull(tags).filter(
|
||||
(tag) =>
|
||||
tag.key !== undefined &&
|
||||
tag.value !== undefined &&
|
||||
tag.key !== "" &&
|
||||
tag.value !== ""
|
||||
)
|
||||
const metadata = tags.map((kv) => `<tag k="${kv.key}" v="${escapeHtml(kv.value)}"/>`)
|
||||
|
||||
self.auth.xhr(
|
||||
{
|
||||
method: "PUT",
|
||||
path: "/api/0.6/changeset/" + csId,
|
||||
options: { header: { "Content-Type": "text/xml" } },
|
||||
content: [`<osm><changeset>`, metadata, `</changeset></osm>`].join(""),
|
||||
},
|
||||
function (err, response) {
|
||||
if (response === undefined) {
|
||||
console.error("Updating the tags of changeset " + csId + " failed:", err)
|
||||
reject(err)
|
||||
} else {
|
||||
resolve(response)
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
tags = Utils.NoNull(tags).filter(
|
||||
(tag) =>
|
||||
tag.key !== undefined &&
|
||||
tag.value !== undefined &&
|
||||
tag.key !== "" &&
|
||||
tag.value !== ""
|
||||
)
|
||||
const metadata = tags.map((kv) => `<tag k="${kv.key}" v="${escapeHtml(kv.value)}"/>`)
|
||||
const content = [`<osm><changeset>`, metadata, `</changeset></osm>`].join("")
|
||||
return this.osmConnection.put("changeset/" + csId, content, { "Content-Type": "text/xml" })
|
||||
}
|
||||
|
||||
private defaultChangesetTags(): ChangesetTag[] {
|
||||
|
@ -413,57 +369,35 @@ export class ChangesetHandler {
|
|||
* @constructor
|
||||
* @private
|
||||
*/
|
||||
private OpenChangeset(changesetTags: ChangesetTag[]): Promise<number> {
|
||||
const self = this
|
||||
return new Promise<number>(function (resolve, reject) {
|
||||
const metadata = changesetTags
|
||||
.map((cstag) => [cstag.key, cstag.value])
|
||||
.filter((kv) => (kv[1] ?? "") !== "")
|
||||
.map((kv) => `<tag k="${kv[0]}" v="${escapeHtml(kv[1])}"/>`)
|
||||
.join("\n")
|
||||
private async OpenChangeset(changesetTags: ChangesetTag[]): Promise<number> {
|
||||
const metadata = changesetTags
|
||||
.map((cstag) => [cstag.key, cstag.value])
|
||||
.filter((kv) => (kv[1] ?? "") !== "")
|
||||
.map((kv) => `<tag k="${kv[0]}" v="${escapeHtml(kv[1])}"/>`)
|
||||
.join("\n")
|
||||
|
||||
self.auth.xhr(
|
||||
{
|
||||
method: "PUT",
|
||||
path: "/api/0.6/changeset/create",
|
||||
options: { header: { "Content-Type": "text/xml" } },
|
||||
content: [`<osm><changeset>`, metadata, `</changeset></osm>`].join(""),
|
||||
},
|
||||
function (err, response) {
|
||||
if (response === undefined) {
|
||||
console.error("Opening a changeset failed:", err)
|
||||
reject(err)
|
||||
} else {
|
||||
resolve(Number(response))
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
const csId = await this.osmConnection.put(
|
||||
"changeset/create",
|
||||
[`<osm><changeset>`, metadata, `</changeset></osm>`].join(""),
|
||||
{ "Content-Type": "text/xml" }
|
||||
)
|
||||
return Number(csId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a changesetXML
|
||||
*/
|
||||
private UploadChange(changesetId: number, changesetXML: string): Promise<Map<string, string>> {
|
||||
const self = this
|
||||
return new Promise(function (resolve, reject) {
|
||||
self.auth.xhr(
|
||||
{
|
||||
method: "POST",
|
||||
options: { header: { "Content-Type": "text/xml" } },
|
||||
path: "/api/0.6/changeset/" + changesetId + "/upload",
|
||||
content: changesetXML,
|
||||
},
|
||||
function (err, response) {
|
||||
if (response == null) {
|
||||
console.error("Uploading an actual change failed", err)
|
||||
reject(err)
|
||||
}
|
||||
const changes = self.parseUploadChangesetResponse(response)
|
||||
console.log("Uploaded changeset ", changesetId)
|
||||
resolve(changes)
|
||||
}
|
||||
)
|
||||
})
|
||||
private async UploadChange(
|
||||
changesetId: number,
|
||||
changesetXML: string
|
||||
): Promise<Map<string, string>> {
|
||||
const response = await this.osmConnection.post(
|
||||
"changeset/" + changesetId + "/upload",
|
||||
changesetXML,
|
||||
{ "Content-Type": "text/xml" }
|
||||
)
|
||||
const changes = this.parseUploadChangesetResponse(response)
|
||||
console.log("Uploaded changeset ", changesetId)
|
||||
return changes
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,8 @@
|
|||
import osmAuth from "osm-auth"
|
||||
import { Store, Stores, UIEventSource } from "../UIEventSource"
|
||||
import { OsmPreferences } from "./OsmPreferences"
|
||||
import { ChangesetHandler } from "./ChangesetHandler"
|
||||
import { ElementStorage } from "../ElementStorage"
|
||||
import Svg from "../../Svg"
|
||||
import Img from "../../UI/Base/Img"
|
||||
import { Utils } from "../../Utils"
|
||||
import { OsmObject } from "./OsmObject"
|
||||
import { Changes } from "./Changes"
|
||||
|
||||
export default class UserDetails {
|
||||
public loggedIn = false
|
||||
|
@ -148,16 +143,6 @@ export class OsmConnection {
|
|||
}
|
||||
}
|
||||
|
||||
public CreateChangesetHandler(allElements: ElementStorage, changes: Changes) {
|
||||
return new ChangesetHandler(
|
||||
this._dryRun,
|
||||
<any>/*casting is needed to make the tests work*/ this,
|
||||
allElements,
|
||||
changes,
|
||||
this.auth
|
||||
)
|
||||
}
|
||||
|
||||
public GetPreference(
|
||||
key: string,
|
||||
defaultValue: string = undefined,
|
||||
|
@ -288,6 +273,57 @@ export class OsmConnection {
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Interact with the API.
|
||||
*
|
||||
* @param path: the path to query, without host and without '/api/0.6'. Example 'notes/1234/close'
|
||||
*/
|
||||
public async interact(
|
||||
path: string,
|
||||
method: "GET" | "POST" | "PUT" | "DELETE",
|
||||
header?: Record<string, string | number>,
|
||||
content?: string
|
||||
): Promise<any> {
|
||||
return new Promise((ok, error) => {
|
||||
this.auth.xhr(
|
||||
{
|
||||
method,
|
||||
options: {
|
||||
header,
|
||||
},
|
||||
content,
|
||||
path: `/api/0.6/${path}`,
|
||||
},
|
||||
function (err, response) {
|
||||
if (err !== null) {
|
||||
error(err)
|
||||
} else {
|
||||
ok(response)
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
public async post(
|
||||
path: string,
|
||||
content?: string,
|
||||
header?: Record<string, string | number>
|
||||
): Promise<any> {
|
||||
return await this.interact(path, "POST", header, content)
|
||||
}
|
||||
public async put(
|
||||
path: string,
|
||||
content?: string,
|
||||
header?: Record<string, string | number>
|
||||
): Promise<any> {
|
||||
return await this.interact(path, "PUT", header, content)
|
||||
}
|
||||
|
||||
public async get(path: string, header?: Record<string, string | number>): Promise<any> {
|
||||
return await this.interact(path, "GET", header)
|
||||
}
|
||||
|
||||
public closeNote(id: number | string, text?: string): Promise<void> {
|
||||
let textSuffix = ""
|
||||
if ((text ?? "") !== "") {
|
||||
|
@ -299,21 +335,7 @@ export class OsmConnection {
|
|||
ok()
|
||||
})
|
||||
}
|
||||
return new Promise((ok, error) => {
|
||||
this.auth.xhr(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/0.6/notes/${id}/close${textSuffix}`,
|
||||
},
|
||||
function (err, _) {
|
||||
if (err !== null) {
|
||||
error(err)
|
||||
} else {
|
||||
ok()
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
return this.post(`notes/${id}/close${textSuffix}`)
|
||||
}
|
||||
|
||||
public reopenNote(id: number | string, text?: string): Promise<void> {
|
||||
|
@ -327,24 +349,10 @@ export class OsmConnection {
|
|||
if ((text ?? "") !== "") {
|
||||
textSuffix = "?text=" + encodeURIComponent(text)
|
||||
}
|
||||
return new Promise((ok, error) => {
|
||||
this.auth.xhr(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/0.6/notes/${id}/reopen${textSuffix}`,
|
||||
},
|
||||
function (err, _) {
|
||||
if (err !== null) {
|
||||
error(err)
|
||||
} else {
|
||||
ok()
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
return this.post(`notes/${id}/reopen${textSuffix}`)
|
||||
}
|
||||
|
||||
public openNote(lat: number, lon: number, text: string): Promise<{ id: number }> {
|
||||
public async openNote(lat: number, lon: number, text: string): Promise<{ id: number }> {
|
||||
if (this._dryRun.data) {
|
||||
console.warn("Dryrun enabled - not actually opening note with text ", text)
|
||||
return new Promise<{ id: number }>((ok) => {
|
||||
|
@ -356,29 +364,13 @@ export class OsmConnection {
|
|||
}
|
||||
const auth = this.auth
|
||||
const content = { lat, lon, text }
|
||||
return new Promise((ok, error) => {
|
||||
auth.xhr(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/0.6/notes.json`,
|
||||
options: {
|
||||
header: { "Content-Type": "application/json" },
|
||||
},
|
||||
content: JSON.stringify(content),
|
||||
},
|
||||
function (err, response: string) {
|
||||
console.log("RESPONSE IS", response)
|
||||
if (err !== null) {
|
||||
error(err)
|
||||
} else {
|
||||
const parsed = JSON.parse(response)
|
||||
const id = parsed.properties.id
|
||||
console.log("OPENED NOTE", id)
|
||||
ok({ id })
|
||||
}
|
||||
}
|
||||
)
|
||||
const response = await this.post("notes.json", JSON.stringify(content), {
|
||||
"Content-Type": "application/json",
|
||||
})
|
||||
const parsed = JSON.parse(response)
|
||||
const id = parsed.properties.id
|
||||
console.log("OPENED NOTE", id)
|
||||
return id
|
||||
}
|
||||
|
||||
public async uploadGpxTrack(
|
||||
|
@ -434,31 +426,13 @@ export class OsmConnection {
|
|||
}
|
||||
body += "--" + boundary + "--\r\n"
|
||||
|
||||
return new Promise((ok, error) => {
|
||||
auth.xhr(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/0.6/gpx/create`,
|
||||
options: {
|
||||
header: {
|
||||
"Content-Type": "multipart/form-data; boundary=" + boundary,
|
||||
"Content-Length": body.length,
|
||||
},
|
||||
},
|
||||
content: body,
|
||||
},
|
||||
function (err, response: string) {
|
||||
console.log("RESPONSE IS", response)
|
||||
if (err !== null) {
|
||||
error(err)
|
||||
} else {
|
||||
const parsed = JSON.parse(response)
|
||||
console.log("Uploaded GPX track", parsed)
|
||||
ok({ id: parsed })
|
||||
}
|
||||
}
|
||||
)
|
||||
const response = await this.post("gpx/create", body, {
|
||||
"Content-Type": "multipart/form-data; boundary=" + boundary,
|
||||
"Content-Length": body.length,
|
||||
})
|
||||
const parsed = JSON.parse(response)
|
||||
console.log("Uploaded GPX track", parsed)
|
||||
return { id: parsed }
|
||||
}
|
||||
|
||||
public addCommentToNote(id: number | string, text: string): Promise<void> {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { TagsFilter } from "../Tags/TagsFilter"
|
||||
import RelationsTracker from "./RelationsTracker"
|
||||
import { Utils } from "../../Utils"
|
||||
import { ImmutableStore, Store } from "../UIEventSource"
|
||||
import { BBox } from "../BBox"
|
||||
|
@ -15,14 +14,12 @@ export class Overpass {
|
|||
private readonly _timeout: Store<number>
|
||||
private readonly _extraScripts: string[]
|
||||
private readonly _includeMeta: boolean
|
||||
private _relationTracker: RelationsTracker
|
||||
|
||||
constructor(
|
||||
filter: TagsFilter,
|
||||
extraScripts: string[],
|
||||
interpreterUrl: string,
|
||||
timeout?: Store<number>,
|
||||
relationTracker?: RelationsTracker,
|
||||
includeMeta = true
|
||||
) {
|
||||
this._timeout = timeout ?? new ImmutableStore<number>(90)
|
||||
|
@ -34,7 +31,6 @@ export class Overpass {
|
|||
this._filter = optimized
|
||||
this._extraScripts = extraScripts
|
||||
this._includeMeta = includeMeta
|
||||
this._relationTracker = relationTracker
|
||||
}
|
||||
|
||||
public async queryGeoJson(bounds: BBox): Promise<[FeatureCollection, Date]> {
|
||||
|
@ -57,7 +53,6 @@ export class Overpass {
|
|||
}
|
||||
|
||||
public async ExecuteQuery(query: string): Promise<[FeatureCollection, Date]> {
|
||||
const self = this
|
||||
const json = await Utils.downloadJson(this.buildUrl(query))
|
||||
|
||||
if (json.elements.length === 0 && json.remark !== undefined) {
|
||||
|
@ -68,7 +63,6 @@ export class Overpass {
|
|||
console.warn("No features for", json)
|
||||
}
|
||||
|
||||
self._relationTracker?.RegisterRelations(json)
|
||||
const geojson = osmtogeojson(json)
|
||||
const osmTime = new Date(json.osm3s.timestamp_osm_base)
|
||||
return [<any>geojson, osmTime]
|
||||
|
@ -104,7 +98,6 @@ export class Overpass {
|
|||
/**
|
||||
* Constructs the actual script to execute on Overpass with geocoding
|
||||
* 'PostCall' can be used to set an extra range, see 'AsOverpassTurboLink'
|
||||
*
|
||||
*/
|
||||
public buildScriptInArea(
|
||||
area: { osm_type: "way" | "relation"; osm_id: number },
|
||||
|
@ -142,7 +135,7 @@ export class Overpass {
|
|||
* Little helper method to quickly open overpass-turbo in the browser
|
||||
*/
|
||||
public static AsOverpassTurboLink(tags: TagsFilter) {
|
||||
const overpass = new Overpass(tags, [], "", undefined, undefined, false)
|
||||
const overpass = new Overpass(tags, [], "", undefined, false)
|
||||
const script = overpass.buildScript("", "({{bbox}})", true)
|
||||
const url = "http://overpass-turbo.eu/?Q="
|
||||
return url + encodeURIComponent(script)
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
import { UIEventSource } from "../UIEventSource"
|
||||
|
||||
export interface Relation {
|
||||
id: number
|
||||
type: "relation"
|
||||
members: {
|
||||
type: "way" | "node" | "relation"
|
||||
ref: number
|
||||
role: string
|
||||
}[]
|
||||
tags: any
|
||||
// Alias for tags; tags == properties
|
||||
properties: any
|
||||
}
|
||||
|
||||
export default class RelationsTracker {
|
||||
public knownRelations = new UIEventSource<Map<string, { role: string; relation: Relation }[]>>(
|
||||
new Map(),
|
||||
"Relation memberships"
|
||||
)
|
||||
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* Gets an overview of the relations - except for multipolygons. We don't care about those
|
||||
* @param overpassJson
|
||||
* @constructor
|
||||
*/
|
||||
private static GetRelationElements(overpassJson: any): Relation[] {
|
||||
const relations = overpassJson.elements.filter(
|
||||
(element) => element.type === "relation" && element.tags.type !== "multipolygon"
|
||||
)
|
||||
for (const relation of relations) {
|
||||
relation.properties = relation.tags
|
||||
}
|
||||
return relations
|
||||
}
|
||||
|
||||
public RegisterRelations(overpassJson: any): void {
|
||||
this.UpdateMembershipTable(RelationsTracker.GetRelationElements(overpassJson))
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a mapping of {memberId --> {role in relation, id of relation} }
|
||||
* @param relations
|
||||
* @constructor
|
||||
*/
|
||||
private UpdateMembershipTable(relations: Relation[]): void {
|
||||
const memberships = this.knownRelations.data
|
||||
let changed = false
|
||||
for (const relation of relations) {
|
||||
for (const member of relation.members) {
|
||||
const role = {
|
||||
role: member.role,
|
||||
relation: relation,
|
||||
}
|
||||
const key = member.type + "/" + member.ref
|
||||
if (!memberships.has(key)) {
|
||||
memberships.set(key, [])
|
||||
}
|
||||
const knownRelations = memberships.get(key)
|
||||
|
||||
const alreadyExists = knownRelations.some((knownRole) => {
|
||||
return knownRole.role === role.role && knownRole.relation === role.relation
|
||||
})
|
||||
if (!alreadyExists) {
|
||||
knownRelations.push(role)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
this.knownRelations.ping()
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue