Add explicit api_url to config and testserver-mode indication, add id rewrite when changes are applied to fix image upload on a new object, various formatting fixes; version bump

This commit is contained in:
Pieter Vander Vennet 2024-07-17 18:42:39 +02:00
parent 39376c0012
commit a4db7178f4
16 changed files with 277 additions and 192 deletions

View file

@ -137,7 +137,7 @@
{ {
"id": "minimap", "id": "minimap",
"labels": [ "labels": [
"ignore_docs" "ignore_docs"
], ],
"description": "Shows a small map with the feature. Added by default to every popup", "description": "Shows a small map with the feature. Added by default to every popup",
"render": { "render": {

View file

@ -1,6 +1,6 @@
{ {
"name": "mapcomplete", "name": "mapcomplete",
"version": "0.44.7", "version": "0.44.8",
"repository": "https://github.com/pietervdvn/MapComplete", "repository": "https://github.com/pietervdvn/MapComplete",
"description": "A small website to edit OSM easily", "description": "A small website to edit OSM easily",
"bugs": "https://github.com/pietervdvn/MapComplete/issues", "bugs": "https://github.com/pietervdvn/MapComplete/issues",
@ -19,20 +19,23 @@
"#": "This client-id is registered by 'MapComplete' on OpenStreetMap.org", "#": "This client-id is registered by 'MapComplete' on OpenStreetMap.org",
"oauth_client_id": "K93H1d8ve7p-tVLE1ZwsQ4lAFLQk8INx5vfTLMu5DWk", "oauth_client_id": "K93H1d8ve7p-tVLE1ZwsQ4lAFLQk8INx5vfTLMu5DWk",
"oauth_secret": "NBWGhWDrD3QDB35xtVuxv4aExnmIt4FA_WgeLtwxasg", "oauth_secret": "NBWGhWDrD3QDB35xtVuxv4aExnmIt4FA_WgeLtwxasg",
"url": "https://www.openstreetmap.org" "url": "https://www.openstreetmap.org",
"api_url": "https://api.openstreetmap.org"
},
"disabled:oauth_credentials": {
"##": "DEV",
"#": "This client-id is registered by 'MapComplete' on https://master.apis.dev.openstreetmap.org/",
"oauth_client_id": "BAPPMRuojjFsY__0APmScr1hbebYj1GlDbV6S5TsUbo",
"oauth_secret": "Lq1UKYAySRfQfwgFctGvlGrfxbGHQwggheE52HIGrO8",
"url": "https://master.apis.dev.openstreetmap.org",
"api_url": "https://master.apis.dev.openstreetmap.org"
}, },
"mvt_layer_server": "https://cache.mapcomplete.org/public.{type}_{layer}/{z}/{x}/{y}.pbf", "mvt_layer_server": "https://cache.mapcomplete.org/public.{type}_{layer}/{z}/{x}/{y}.pbf",
"#summary_server": "Should be the endpoint; appending status.json should work", "#summary_server": "Should be the endpoint; appending status.json should work",
"summary_server": "https://cache.mapcomplete.org/", "summary_server": "https://cache.mapcomplete.org/",
"geoip_server": "https://ipinfo.mapcomplete.org/", "geoip_server": "https://ipinfo.mapcomplete.org/",
"error_server": "https://report.mapcomplete.org/report", "error_server": "https://report.mapcomplete.org/report",
"disabled:oauth_credentials": {
"##": "DEV",
"#": "This client-id is registered by 'MapComplete' on https://master.apis.dev.openstreetmap.org/",
"oauth_client_id": "BAPPMRuojjFsY__0APmScr1hbebYj1GlDbV6S5TsUbo",
"oauth_secret": "Lq1UKYAySRfQfwgFctGvlGrfxbGHQwggheE52HIGrO8",
"url": "https://master.apis.dev.openstreetmap.org"
},
"api_keys": { "api_keys": {
"#": "Various API-keys for various services. Feel free to reuse those in another MapComplete-hosted version", "#": "Various API-keys for various services. Feel free to reuse those in another MapComplete-hosted version",
"imgur": "7070e7167f0a25a", "imgur": "7070e7167f0a25a",

View file

@ -21,6 +21,9 @@ class HandleErrors extends Script {
const path = args[0] const path = args[0]
const lines = readFileSync(path, "utf8").split("\n") const lines = readFileSync(path, "utf8").split("\n")
let createdChangesets = new Set<string>()
let refusedFiles: Set<string> = new Set<string>()
for (const line of lines) { for (const line of lines) {
if (!line?.trim()) { if (!line?.trim()) {
continue continue
@ -41,7 +44,8 @@ class HandleErrors extends Script {
} }
} = JSON.parse(line) } = JSON.parse(line)
const e = parsed.message const e = parsed.message
if (e.layout !== "grb") { if (e.layout === "grb") {
console.log("Filtering on GRB!")
continue continue
} }
console.log(e.username, e.layout, e.message, parsed.date) console.log(e.username, e.layout, e.message, parsed.date)
@ -52,43 +56,48 @@ class HandleErrors extends Script {
const neededIds = Changes.GetNeededIds(e.pendingChanges) const neededIds = Changes.GetNeededIds(e.pendingChanges)
// We _do not_ pass in the Changes object itself - we want the data from OSM directly in order to apply the changes // We _do not_ pass in the Changes object itself - we want the data from OSM directly in order to apply the changes
let osmObjects: { id: string, osmObj: OsmObject | "deleted" }[] = await Promise.all<{ const osmObjects: { id: string, osmObj: OsmObject | "deleted" }[] = await Promise.all<{
id: string; id: string;
osmObj: OsmObject | "deleted" osmObj: OsmObject | "deleted"
}>( }>(
neededIds.map(async id => ({ id, osmObj: await downloader.DownloadObjectAsync(id) })), neededIds.map(async id => ({ id, osmObj: await downloader.DownloadObjectAsync(id) }))
) )
const objects = osmObjects const objects = osmObjects
.filter((obj) => obj.osmObj !== "deleted") .filter((obj) => obj.osmObj !== "deleted")
.map((obj) => <OsmObject>obj.osmObj) .map((obj) => <OsmObject>obj.osmObj)
const perType = Array.from( const { toUpload, refused } = Changes.fragmentChanges(e.pendingChanges, objects)
Utils.Hist(
e.pendingChanges
.filter(
(descr) =>
descr.meta.changeType !== undefined && descr.meta.changeType !== null,
)
.map((descr) => descr.meta.changeType),
),
([key, count]) => ({
key: key,
value: count,
aggregate: true,
}),
)
const changes: { const changes: {
newObjects: OsmObject[] newObjects: OsmObject[]
modifiedObjects: OsmObject[] modifiedObjects: OsmObject[]
deletedObjects: OsmObject[] deletedObjects: OsmObject[]
} = new Changes({ } = new Changes({
dryRun: new ImmutableStore(true), dryRun: new ImmutableStore(true),
osmConnection, osmConnection
}).CreateChangesetObjects(e.pendingChanges, objects) }).CreateChangesetObjects(toUpload, objects)
const changeset = Changes.createChangesetFor("", changes)
const path = "error_changeset_" + parsed.index + "_" + e.layout + "_" + e.username + ".osc"
if(changeset === "<osmChange version='0.6' generator='Mapcomplete 0.44.7'></osmChange>"){
console.log("Changes for "+parsed.index+": empty changeset, not creating a file for it")
}else if (createdChangesets.has(changeset)) {
console.log("Changeset " + parsed.index + " is identical to previously seen changeset, not writing to file")
} else {
writeFileSync(path, changeset, "utf8")
createdChangesets.add(changeset)
}
const refusedContent = JSON.stringify(refused)
if (refusedFiles.has(refusedContent)) {
console.log("Refused changes for " + parsed.index + " is identical to previously seen changeset, not writing to file")
} else {
writeFileSync(path + ".refused.json", refusedContent, "utf8")
refusedFiles.add(refusedContent)
}
console.log("Written", path, "with " + e.pendingChanges.length + " changes")
const changeset = Changes.createChangesetFor("" + parsed.index, changes)
writeFileSync("error_changeset_"+parsed.index+".osc", changeset, "utf8")
} catch (e) { } catch (e) {
console.log("Parsing line failed:", e) console.log("Parsing line failed:", e)
} }

View file

@ -128,6 +128,9 @@ export class ImageUploadManager {
targetKey, targetKey,
tags?.data?.["_orig_theme"] tags?.data?.["_orig_theme"]
) )
if (!action) { if (!action) {
return return
} }
@ -169,13 +172,13 @@ export class ImageUploadManager {
return undefined return undefined
} }
} }
console.log("Uploading done, creating action for", featureId) console.log("Uploading image done, creating action for", featureId)
key = targetKey ?? key key = targetKey ?? key
this.increaseCountFor(this._uploadFinished, featureId)
const action = new LinkImageAction(featureId, key, value, properties, { const action = new LinkImageAction(featureId, key, value, properties, {
theme: theme ?? this._layout.id, theme: theme ?? this._layout.id,
changeType: "add-image", changeType: "add-image",
}) })
this.increaseCountFor(this._uploadFinished, featureId)
return action return action
} }

View file

@ -82,10 +82,9 @@ export default class MetaTagging {
this.updateCurrentSelectedElement() this.updateCurrentSelectedElement()
let lastUpdateMoment = new Date() let lastUpdateMoment = new Date()
const tags = state?.featureProperties?.getStore(feature.properties.id) const tags = state?.featureProperties?.getStore(feature.properties.id)
console.log("Binding an updater to", feature)
let updateCount = 0 let updateCount = 0
tags?.addCallbackD(() => { tags?.addCallbackD(() => {
console.log( console.debug(
"Received an update! Re-calculating the metatags, timediff:", "Received an update! Re-calculating the metatags, timediff:",
new Date().getTime() - lastUpdateMoment.getTime() new Date().getTime() - lastUpdateMoment.getTime()
) )

View file

@ -136,7 +136,7 @@ export class ChangeDescriptionTools {
mappings: Map<string, string> mappings: Map<string, string>
): ChangeDescription { ): ChangeDescription {
const key = change.type + "/" + change.id const key = change.type + "/" + change.id
console.log("Checking remapping ", change.id, mappings, change)
const wayHasChangedNode = ((change.changes ?? {})["nodes"] ?? []).some((id) => const wayHasChangedNode = ((change.changes ?? {})["nodes"] ?? []).some((id) =>
mappings.has("node/" + id) mappings.has("node/" + id)
) )
@ -199,4 +199,11 @@ export class ChangeDescriptionTools {
return r.asGeoJson().geometry return r.asGeoJson().geometry
} }
} }
public static rewriteAllIds(
changes: ChangeDescription[],
mappings: Map<string, string>
): ChangeDescription[] {
return changes.map(c =>ChangeDescriptionTools.rewriteIds(c, mappings))
}
} }

View file

@ -3,4 +3,5 @@ export interface AuthConfig {
oauth_client_id: string oauth_client_id: string
oauth_secret: string oauth_secret: string
url: string url: string
api_url: string
} }

View file

@ -53,7 +53,7 @@ export class Changes {
featureSwitches?: FeatureSwitchState featureSwitches?: FeatureSwitchState
}, },
leftRightSensitive: boolean = false, leftRightSensitive: boolean = false,
reportError?: (string: string | Error) => void, reportError?: (string: string | Error) => void
) { ) {
this._leftRightSensitive = leftRightSensitive this._leftRightSensitive = leftRightSensitive
// We keep track of all changes just as well // We keep track of all changes just as well
@ -68,7 +68,7 @@ export class Changes {
state.osmConnection, state.osmConnection,
state.featurePropertiesStore, state.featurePropertiesStore,
this, this,
(e) => this._reportError(e), (e) => this._reportError(e)
) )
this.historicalUserLocations = state.historicalUserLocations this.historicalUserLocations = state.historicalUserLocations
@ -82,7 +82,7 @@ export class Changes {
modifiedObjects: OsmObject[] modifiedObjects: OsmObject[]
newObjects: OsmObject[] newObjects: OsmObject[]
deletedObjects: OsmObject[] deletedObjects: OsmObject[]
}, }
): string { ): string {
const changedElements = allChanges.modifiedObjects ?? [] const changedElements = allChanges.modifiedObjects ?? []
const newElements = allChanges.newObjects ?? [] const newElements = allChanges.newObjects ?? []
@ -120,6 +120,7 @@ export class Changes {
}) })
return items return items
} }
const metatagsDocs: { const metatagsDocs: {
key?: string key?: string
value?: string value?: string
@ -132,46 +133,46 @@ export class Changes {
[ [
{ {
key: "comment", key: "comment",
docs: "The changeset comment. Will be a fixed string, mentioning the theme", docs: "The changeset comment. Will be a fixed string, mentioning the theme"
}, },
{ {
key: "theme", key: "theme",
docs: "The name of the theme that was used to create this change. ", docs: "The name of the theme that was used to create this change. "
}, },
{ {
key: "source", key: "source",
value: "survey", value: "survey",
docs: "The contributor had their geolocation enabled while making changes", docs: "The contributor had their geolocation enabled while making changes"
}, },
{ {
key: "change_within_{distance}", key: "change_within_{distance}",
docs: "If the contributor enabled their geolocation, this will hint how far away they were from the objects they edited. This gives an indication of proximity and if they truly surveyed or were armchair-mapping", docs: "If the contributor enabled their geolocation, this will hint how far away they were from the objects they edited. This gives an indication of proximity and if they truly surveyed or were armchair-mapping"
}, },
{ {
key: "change_over_{distance}", key: "change_over_{distance}",
docs: "If the contributor enabled their geolocation, this will hint how far away they were from the objects they edited. If they were over 5000m away, the might have been armchair-mapping", docs: "If the contributor enabled their geolocation, this will hint how far away they were from the objects they edited. If they were over 5000m away, the might have been armchair-mapping"
}, },
{ {
key: "created_by", key: "created_by",
value: "MapComplete <version>", value: "MapComplete <version>",
docs: "The piece of software used to create this changeset; will always start with MapComplete, followed by the version number", docs: "The piece of software used to create this changeset; will always start with MapComplete, followed by the version number"
}, },
{ {
key: "locale", key: "locale",
value: "en|nl|de|...", value: "en|nl|de|...",
docs: "The code of the language that the contributor used MapComplete in. Hints what language the user speaks.", docs: "The code of the language that the contributor used MapComplete in. Hints what language the user speaks."
}, },
{ {
key: "host", key: "host",
value: "https://mapcomplete.org/<theme>", value: "https://mapcomplete.org/<theme>",
docs: "The URL that the contributor used to make changes. One can see the used instance with this", docs: "The URL that the contributor used to make changes. One can see the used instance with this"
}, },
{ {
key: "imagery", key: "imagery",
docs: "The identifier of the used background layer, this will probably be an identifier from the [editor layer index](https://github.com/osmlab/editor-layer-index)", docs: "The identifier of the used background layer, this will probably be an identifier from the [editor layer index](https://github.com/osmlab/editor-layer-index)"
}, }
], ],
"default", "default"
), ),
...addSource(ChangeTagAction.metatags, "ChangeTag"), ...addSource(ChangeTagAction.metatags, "ChangeTag"),
...addSource(ChangeLocationAction.metatags, "ChangeLocation"), ...addSource(ChangeLocationAction.metatags, "ChangeLocation"),
@ -193,15 +194,15 @@ export class Changes {
metatagsDocs.map(({ key, value, docs, source, changeType, specialMotivation }) => [ metatagsDocs.map(({ key, value, docs, source, changeType, specialMotivation }) => [
key ?? changeType?.join(", ") ?? "", key ?? changeType?.join(", ") ?? "",
value, value,
[ [
docs, docs,
specialMotivation specialMotivation
? "This might give a reason per modified node or way" ? "This might give a reason per modified node or way"
: "", : ""
].join("\n"), ].join("\n"),
source, source
]), ])
), )
].join("\n\n") ].join("\n\n")
} }
@ -247,11 +248,13 @@ export class Changes {
public async applyAction(action: OsmChangeAction): Promise<void> { public async applyAction(action: OsmChangeAction): Promise<void> {
const changeDescriptions = await action.Perform(this) const changeDescriptions = await action.Perform(this)
changeDescriptions[0].meta.distanceToObject = this.calculateDistanceToChanges( const remapped = ChangeDescriptionTools.rewriteAllIds(changeDescriptions, this._changesetHandler._remappings)
remapped[0].meta.distanceToObject = this.calculateDistanceToChanges(
action, action,
changeDescriptions, remapped
) )
this.applyChanges(changeDescriptions) this.applyChanges(remapped)
} }
public applyChanges(changes: ChangeDescription[]) { public applyChanges(changes: ChangeDescription[]) {
@ -263,14 +266,30 @@ export class Changes {
public CreateChangesetObjects( public CreateChangesetObjects(
changes: ChangeDescription[], changes: ChangeDescription[],
downloadedOsmObjects: OsmObject[], downloadedOsmObjects: OsmObject[]
): { ): {
newObjects: OsmObject[] newObjects: OsmObject[]
modifiedObjects: OsmObject[] modifiedObjects: OsmObject[]
deletedObjects: OsmObject[] deletedObjects: OsmObject[]
} { } {
const objects: Map<string, OsmObject> = new Map<string, OsmObject>() /**
* This is a rather complicated method which does a lot of stuff.
*
* Our main important data is `state` and `objects` which will determine what is returned.
* First init all those states, then we actually apply the changes.
* At last, we sort them for easy handling, which is rather boring
*/
// ------------------ INIT -------------------------
/**
* Keeps track of every object what actually happened with it
*/
const states: Map<string, "unchanged" | "created" | "modified" | "deleted"> = new Map() const states: Map<string, "unchanged" | "created" | "modified" | "deleted"> = new Map()
/**
* Keeps track of the _new_ state of the objects, how they should end up on the database
*/
const objects: Map<string, OsmObject> = new Map<string, OsmObject>()
for (const o of downloadedOsmObjects) { for (const o of downloadedOsmObjects) {
objects.set(o.type + "/" + o.id, o) objects.set(o.type + "/" + o.id, o)
@ -282,6 +301,8 @@ export class Changes {
states.set(o.type + "/" + o.id, "unchanged") states.set(o.type + "/" + o.id, "unchanged")
} }
// -------------- APPLY INTERMEDIATE CHANGES -----------------
for (const change of changes) { for (const change of changes) {
let changed = false let changed = false
const id = change.type + "/" + change.id const id = change.type + "/" + change.id
@ -293,7 +314,7 @@ export class Changes {
} }
if (change.changes === undefined) { if (change.changes === undefined) {
// This object is a change to a newly created object. However, we have not seen the creation changedescription yet! // This object is a change to a newly created object. However, we have not seen the creation changedescription yet!
throw "Not a creation of the object" throw "Not a creation of the object: " + JSON.stringify(change)
} }
// This is a new object that should be created // This is a new object that should be created
states.set(id, "created") states.set(id, "created")
@ -399,10 +420,12 @@ export class Changes {
} }
} }
// ----------------- SORT OBJECTS -------------------
const result = { const result = {
newObjects: [], newObjects: [],
modifiedObjects: [], modifiedObjects: [],
deletedObjects: [], deletedObjects: []
} }
objects.forEach((v, id) => { objects.forEach((v, id) => {
@ -425,14 +448,14 @@ export class Changes {
result.modifiedObjects.length, result.modifiedObjects.length,
"modified;", "modified;",
result.deletedObjects, result.deletedObjects,
"deleted", "deleted"
) )
return result return result
} }
private calculateDistanceToChanges( private calculateDistanceToChanges(
change: OsmChangeAction, change: OsmChangeAction,
changeDescriptions: ChangeDescription[], changeDescriptions: ChangeDescription[]
) { ) {
const locations = this.historicalUserLocations?.features?.data const locations = this.historicalUserLocations?.features?.data
if (locations === undefined) { if (locations === undefined) {
@ -452,7 +475,7 @@ export class Changes {
.filter((feat) => feat.geometry.type === "Point") .filter((feat) => feat.geometry.type === "Point")
.filter((feat) => { .filter((feat) => {
const visitTime = new Date( const visitTime = new Date(
(<GeoLocationPointProperties>(<any>feat.properties)).date, (<GeoLocationPointProperties>(<any>feat.properties)).date
) )
// In seconds // In seconds
const diff = (now.getTime() - visitTime.getTime()) / 1000 const diff = (now.getTime() - visitTime.getTime()) / 1000
@ -499,9 +522,9 @@ export class Changes {
...recentLocationPoints.map((gpsPoint) => { ...recentLocationPoints.map((gpsPoint) => {
const otherCoor = GeoOperations.centerpointCoordinates(gpsPoint) const otherCoor = GeoOperations.centerpointCoordinates(gpsPoint)
return GeoOperations.distanceBetween(coor, otherCoor) return GeoOperations.distanceBetween(coor, otherCoor)
}), })
), )
), )
) )
} }
@ -538,19 +561,50 @@ export class Changes {
} }
} }
public static fragmentChanges(pending: ChangeDescription[], objects: OsmObject[]): {
refused: ChangeDescription[];
toUpload: ChangeDescription[]
} {
const refused: ChangeDescription[] = []
const toUpload: ChangeDescription[] = []
// All ids which have an 'update'
const createdIds =
new Set(pending.filter(cd => cd.type === "node" && cd.changes !== undefined).map(cd => cd.id))
pending.forEach(c => {
if (c.id < 0) {
if (createdIds.has(c.id)) {
toUpload.push(c)
} else {
reportError(`Got an orphaned change. The 'creation'-change description for ${c.type}/${c.id} got lost. Permanently dropping this change:`+JSON.stringify(c))
}
return
}
const matchFound = !!objects.find(o => o.id === c.id && o.type === c.type)
if (matchFound) {
toUpload.push(c)
} else {
refused.push(c)
}
})
return {refused, toUpload}
}
/** /**
* Upload the selected changes to OSM. This is typically called with changes for a single theme * Upload the selected changes to OSM. This is typically called with changes for a single theme
* @return pending changes which could not be uploaded for some reason; undefined or empty array if successful * @return pending changes which could not be uploaded for some reason; undefined or empty array if successful
*/ */
private async flushSelectChanges( private async flushSelectChanges(
pending: ChangeDescription[], pending: ChangeDescription[],
openChangeset: UIEventSource<number>, openChangeset: UIEventSource<number>
): Promise<ChangeDescription[]> { ): Promise<ChangeDescription[]> {
const neededIds = Changes.GetNeededIds(pending) const neededIds = Changes.GetNeededIds(pending)
// We _do not_ pass in the Changes object itself - we want the data from OSM directly in order to apply the changes // We _do not_ pass in the Changes object itself - we want the data from OSM directly in order to apply the changes
const downloader = new OsmObjectDownloader(this.backend, undefined) const downloader = new OsmObjectDownloader(this.backend, undefined)
let osmObjects = await Promise.all<{ id: string; osmObj: OsmObject | "deleted" }>( let osmObjects = await Promise.all<{ id: string; osmObj: OsmObject | "deleted" }>(
neededIds.map(id => this.getOsmObject(id, downloader)), neededIds.map(id => this.getOsmObject(id, downloader))
) )
osmObjects = Utils.NoNull(osmObjects) osmObjects = Utils.NoNull(osmObjects)
@ -579,21 +633,21 @@ export class Changes {
pending pending
.filter( .filter(
(descr) => (descr) =>
descr.meta.changeType !== undefined && descr.meta.changeType !== null, descr.meta.changeType !== undefined && descr.meta.changeType !== null
) )
.map((descr) => descr.meta.changeType), .map((descr) => descr.meta.changeType)
), ),
([key, count]) => ({ ([key, count]) => ({
key: key, key: key,
value: count, value: count,
aggregate: true, aggregate: true
}), })
) )
const motivations = pending const motivations = pending
.filter((descr) => descr.meta.specialMotivation !== undefined) .filter((descr) => descr.meta.specialMotivation !== undefined)
.map((descr) => ({ .map((descr) => ({
key: descr.meta.changeType + ":" + descr.type + "/" + descr.id, key: descr.meta.changeType + ":" + descr.type + "/" + descr.id,
value: descr.meta.specialMotivation, value: descr.meta.specialMotivation
})) }))
const distances = Utils.NoNull(pending.map((descr) => descr.meta.distanceToObject)) const distances = Utils.NoNull(pending.map((descr) => descr.meta.distanceToObject))
@ -624,9 +678,9 @@ export class Changes {
return { return {
key, key,
value: count, value: count,
aggregate: true, aggregate: true
} }
}), })
) )
// This method is only called with changedescriptions for this theme // This method is only called with changedescriptions for this theme
@ -639,32 +693,21 @@ export class Changes {
const metatags: ChangesetTag[] = [ const metatags: ChangesetTag[] = [
{ {
key: "comment", key: "comment",
value: comment, value: comment
}, },
{ {
key: "theme", key: "theme",
value: theme, value: theme
}, },
...perType, ...perType,
...motivations, ...motivations,
...perBinMessage, ...perBinMessage
] ]
const refused: ChangeDescription[] = []
let toUpload: ChangeDescription[] = []
pending.forEach(c => { let {toUpload, refused} = Changes.fragmentChanges(
if (c.id < 0) { pending, objects
toUpload.push(c) )
return
}
const matchFound = !!objects.find(o => o.id === c.id && o.type === c.type)
if (matchFound) {
toUpload.push(c)
} else {
refused.push(c)
}
})
await this._changesetHandler.UploadChangeset( await this._changesetHandler.UploadChangeset(
(csId, remappings) => { (csId, remappings) => {
@ -681,10 +724,10 @@ export class Changes {
return Changes.createChangesetFor("" + csId, changes) return Changes.createChangesetFor("" + csId, changes)
}, },
metatags, metatags,
openChangeset, openChangeset
) )
console.log("Upload successful! Refused changes are",refused) console.log("Upload successful! Refused changes are", refused)
return refused return refused
} }
@ -708,14 +751,14 @@ export class Changes {
try { try {
const openChangeset = UIEventSource.asInt( const openChangeset = UIEventSource.asInt(
this.state.osmConnection.GetPreference( this.state.osmConnection.GetPreference(
"current-open-changeset-" + theme, "current-open-changeset-" + theme
), )
) )
console.log( console.log(
"Using current-open-changeset-" + "Using current-open-changeset-" +
theme + theme +
" from the preferences, got " + " from the preferences, got " +
openChangeset.data, openChangeset.data
) )
const refused = await self.flushSelectChanges(pendingChanges, openChangeset) const refused = await self.flushSelectChanges(pendingChanges, openChangeset)
@ -730,7 +773,7 @@ export class Changes {
this.errors.ping() this.errors.ping()
return pendingChanges return pendingChanges
} }
}), })
) )
// We keep all the refused changes to try them again // We keep all the refused changes to try them again
@ -738,7 +781,7 @@ export class Changes {
} catch (e) { } catch (e) {
console.error( console.error(
"Could not handle changes - probably an old, pending changeset in localstorage with an invalid format; erasing those", "Could not handle changes - probably an old, pending changeset in localstorage with an invalid format; erasing those",
e, e
) )
this.errors.data.push(e) this.errors.data.push(e)
this.errors.ping() this.errors.ping()

View file

@ -94,6 +94,7 @@ export class ChangesetHandler {
return hasChange return hasChange
} }
/** /**
* The full logic to upload a change to one or more elements. * The full logic to upload a change to one or more elements.
* *
@ -187,7 +188,7 @@ export class ChangesetHandler {
await this.UpdateTags(csId, rewrittenTags) await this.UpdateTags(csId, rewrittenTags)
} catch (e) { } catch (e) {
if (this._reportError) { if (this._reportError) {
this._reportError("Could not reuse changeset "+csId+", might be closed: " + (e.stacktrace ?? ("" + e))) this._reportError("Could not reuse changeset " + csId + ", might be closed: " + (e.stacktrace ?? ("" + e)))
} }
console.warn("Could not upload, changeset is probably closed: ", e) console.warn("Could not upload, changeset is probably closed: ", e)
openChangeset.setData(undefined) openChangeset.setData(undefined)
@ -236,7 +237,7 @@ export class ChangesetHandler {
if (newMetaTag === undefined) { if (newMetaTag === undefined) {
extraMetaTags.push({ extraMetaTags.push({
key: key, key: key,
value: oldCsTags[key], value: oldCsTags[key]
}) })
continue continue
} }
@ -373,11 +374,11 @@ export class ChangesetHandler {
["locale", Locale.language.data], ["locale", Locale.language.data],
["host", `${window.location.origin}${window.location.pathname}`], ["host", `${window.location.origin}${window.location.pathname}`],
["source", setSourceAsSurvey ? "survey" : undefined], ["source", setSourceAsSurvey ? "survey" : undefined],
["imagery", this.changes.state["backgroundLayer"]?.data?.id], ["imagery", this.changes.state["backgroundLayer"]?.data?.id]
].map(([key, value]) => ({ ].map(([key, value]) => ({
key, key,
value, value,
aggregate: false, aggregate: false
})) }))
} }
@ -409,7 +410,7 @@ export class ChangesetHandler {
changesetId: number, changesetId: number,
changesetXML: string changesetXML: string
): Promise<Map<string, string>> { ): Promise<Map<string, string>> {
const response = await this.osmConnection.post( const response = await this.osmConnection.post<XMLDocument>(
"changeset/" + changesetId + "/upload", "changeset/" + changesetId + "/upload",
changesetXML, changesetXML,
{ "Content-Type": "text/xml" } { "Content-Type": "text/xml" }

View file

@ -214,7 +214,7 @@ export class OsmConnection {
this.auth.xhr( this.auth.xhr(
{ {
method: "GET", method: "GET",
path: "/api/0.6/user/details", path: "/api/0.6/user/details"
}, },
(err, details: XMLDocument) => { (err, details: XMLDocument) => {
if (err != null) { if (err != null) {
@ -326,9 +326,9 @@ export class OsmConnection {
method, method,
headers: header, headers: header,
content, content,
path: `/api/0.6/${path}`, path: `/api/0.6/${path}`
}, },
function (err, response) { function(err, response) {
if (err !== null) { if (err !== null) {
error(err) error(err)
} else { } else {
@ -339,7 +339,7 @@ export class OsmConnection {
}) })
} }
public async post<T extends string>( public async post<T = string>(
path: string, path: string,
content?: string, content?: string,
header?: Record<string, string>, header?: Record<string, string>,
@ -408,7 +408,7 @@ export class OsmConnection {
"notes.json", "notes.json",
content, content,
{ {
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
}, },
true true
) )
@ -449,7 +449,7 @@ export class OsmConnection {
file: gpx, file: gpx,
description: options.description, description: options.description,
tags: options.labels?.join(",") ?? "", tags: options.labels?.join(",") ?? "",
visibility: options.visibility, visibility: options.visibility
} }
if (!contents.description) { if (!contents.description) {
@ -457,9 +457,9 @@ export class OsmConnection {
} }
const extras = { const extras = {
file: file:
'; filename="' + "; filename=\"" +
(options.filename ?? "gpx_track_mapcomplete_" + new Date().toISOString()) + (options.filename ?? "gpx_track_mapcomplete_" + new Date().toISOString()) +
'"\r\nContent-Type: application/gpx+xml', "\"\r\nContent-Type: application/gpx+xml"
} }
const boundary = "987654" const boundary = "987654"
@ -467,7 +467,7 @@ export class OsmConnection {
let body = "" let body = ""
for (const key in contents) { for (const key in contents) {
body += "--" + boundary + "\r\n" body += "--" + boundary + "\r\n"
body += 'Content-Disposition: form-data; name="' + key + '"' body += "Content-Disposition: form-data; name=\"" + key + "\""
if (extras[key] !== undefined) { if (extras[key] !== undefined) {
body += extras[key] body += extras[key]
} }
@ -478,7 +478,7 @@ export class OsmConnection {
const response = await this.post("gpx/create", body, { const response = await this.post("gpx/create", body, {
"Content-Type": "multipart/form-data; boundary=" + boundary, "Content-Type": "multipart/form-data; boundary=" + boundary,
"Content-Length": "" + body.length, "Content-Length": "" + body.length
}) })
const parsed = JSON.parse(response) const parsed = JSON.parse(response)
console.log("Uploaded GPX track", parsed) console.log("Uploaded GPX track", parsed)
@ -499,9 +499,9 @@ export class OsmConnection {
{ {
method: "POST", method: "POST",
path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}`, path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}`
}, },
function (err) { function(err) {
if (err !== null) { if (err !== null) {
error(err) error(err)
} else { } else {
@ -516,7 +516,7 @@ export class OsmConnection {
* To be called by land.html * To be called by land.html
*/ */
public finishLogin(callback: (previousURL: string) => void) { public finishLogin(callback: (previousURL: string) => void) {
this.auth.authenticate(function () { this.auth.authenticate(function() {
// Fully authed at this point // Fully authed at this point
console.log("Authentication successful!") console.log("Authentication successful!")
const previousLocation = LocalStorageSource.Get("location_before_login") const previousLocation = LocalStorageSource.Get("location_before_login")
@ -534,6 +534,7 @@ export class OsmConnection {
: window.location.protocol + "//" + window.location.host + "/land.html", : window.location.protocol + "//" + window.location.host + "/land.html",
singlepage: true, // We always use 'singlePage', it is the most stable - including in PWA singlepage: true, // We always use 'singlePage', it is the most stable - including in PWA
auto: true, auto: true,
apiUrl: this._oauth_config.api_url ?? this._oauth_config.url
}) })
} }

View file

@ -3,10 +3,11 @@ import polygon_features from "../../assets/polygon-features.json"
import { OsmFeature, OsmId, OsmTags, WayId } from "../../Models/OsmFeature" import { OsmFeature, OsmId, OsmTags, WayId } from "../../Models/OsmFeature"
import OsmToGeoJson from "osmtogeojson" import OsmToGeoJson from "osmtogeojson"
import { Feature, LineString, Polygon } from "geojson" import { Feature, LineString, Polygon } from "geojson"
import Constants from "../../Models/Constants"
export abstract class OsmObject { export abstract class OsmObject {
private static defaultBackend = "https://api.openstreetmap.org/" private static defaultBackend = "https://api.openstreetmap.org/"
protected static backendURL = OsmObject.defaultBackend private static backendURL = Constants.osmAuthConfig.url ?? OsmObject.defaultBackend
public static polygonFeatures = OsmObject.constructPolygonFeatures() public static polygonFeatures = OsmObject.constructPolygonFeatures()
type: "node" | "way" | "relation" type: "node" | "way" | "relation"
id: number id: number

View file

@ -635,6 +635,9 @@ export default class SimpleMetaTaggers {
isLazy: true, isLazy: true,
}, },
(feature: Feature, layer: LayerConfig, tagsStore: UIEventSource<OsmTags>) => { (feature: Feature, layer: LayerConfig, tagsStore: UIEventSource<OsmTags>) => {
if(tagsStore === undefined){
return
}
Utils.AddLazyPropertyAsync(feature.properties, "_currency", async () => { Utils.AddLazyPropertyAsync(feature.properties, "_currency", async () => {
// Wait until _country is actually set // Wait until _country is actually set
const tags = await tagsStore.AsPromise((tags) => !!tags._country) const tags = await tagsStore.AsPromise((tags) => !!tags._country)

View file

@ -179,7 +179,8 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches {
let testingDefaultValue = false let testingDefaultValue = false
if ( if (
!Utils.runningFromConsole && !Utils.runningFromConsole &&
(location.hostname === "localhost" || location.hostname === "127.0.0.1") (location.hostname === "localhost" || location.hostname === "127.0.0.1") &&
!Constants.osmAuthConfig.url.startsWith("https://master.apis.dev.openstreetmap.org")
) { ) {
testingDefaultValue = true testingDefaultValue = true
} }

View file

@ -158,7 +158,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
this.featureSwitches = new FeatureSwitchState(layout) this.featureSwitches = new FeatureSwitchState(layout)
this.guistate = new MenuState( this.guistate = new MenuState(
this.featureSwitches.featureSwitchWelcomeMessage.data, this.featureSwitches.featureSwitchWelcomeMessage.data,
layout.id layout.id,
) )
this.map = new UIEventSource<MlMap>(undefined) this.map = new UIEventSource<MlMap>(undefined)
const geolocationState = new GeoLocationState() const geolocationState = new GeoLocationState()
@ -174,14 +174,14 @@ export default class ThemeViewState implements SpecialVisualizationState {
oauth_token: QueryParameters.GetQueryParameter( oauth_token: QueryParameters.GetQueryParameter(
"oauth_token", "oauth_token",
undefined, undefined,
"Used to complete the login" "Used to complete the login",
), ),
}) })
this.userRelatedState = new UserRelatedState( this.userRelatedState = new UserRelatedState(
this.osmConnection, this.osmConnection,
layout, layout,
this.featureSwitches, this.featureSwitches,
this.mapProperties this.mapProperties,
) )
this.userRelatedState.fixateNorth.addCallbackAndRunD((fixated) => { this.userRelatedState.fixateNorth.addCallbackAndRunD((fixated) => {
this.mapProperties.allowRotating.setData(fixated !== "yes") this.mapProperties.allowRotating.setData(fixated !== "yes")
@ -192,13 +192,13 @@ export default class ThemeViewState implements SpecialVisualizationState {
geolocationState, geolocationState,
this.selectedElement, this.selectedElement,
this.mapProperties, this.mapProperties,
this.userRelatedState.gpsLocationHistoryRetentionTime this.userRelatedState.gpsLocationHistoryRetentionTime,
) )
this.geolocationControl = new GeolocationControlState(this.geolocation, this.mapProperties) this.geolocationControl = new GeolocationControlState(this.geolocation, this.mapProperties)
this.availableLayers = AvailableRasterLayers.layersAvailableAt( this.availableLayers = AvailableRasterLayers.layersAvailableAt(
this.mapProperties.location, this.mapProperties.location,
this.osmConnection.isLoggedIn this.osmConnection.isLoggedIn,
) )
const self = this const self = this
@ -206,7 +206,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
this.osmConnection, this.osmConnection,
layout.layers, layout.layers,
layout.id, layout.id,
this.featureSwitches.featureSwitchLayerDefault this.featureSwitches.featureSwitchLayerDefault,
) )
{ {
@ -215,7 +215,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
const isDisplayed = QueryParameters.GetBooleanQueryParameter( const isDisplayed = QueryParameters.GetBooleanQueryParameter(
"overlay-" + rasterInfo.id, "overlay-" + rasterInfo.id,
rasterInfo.defaultState ?? true, rasterInfo.defaultState ?? true,
"Whether or not overlay layer " + rasterInfo.id + " is shown" "Whether or not overlay layer " + rasterInfo.id + " is shown",
) )
const state = { isDisplayed } const state = { isDisplayed }
overlayLayerStates.set(rasterInfo.id, state) overlayLayerStates.set(rasterInfo.id, state)
@ -240,7 +240,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
this.osmConnection.Backend(), this.osmConnection.Backend(),
(id) => self.layerState.filteredLayers.get(id).isDisplayed, (id) => self.layerState.filteredLayers.get(id).isDisplayed,
mvtAvailableLayers, mvtAvailableLayers,
this.fullNodeDatabase this.fullNodeDatabase,
) )
let currentViewIndex = 0 let currentViewIndex = 0
@ -258,7 +258,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
id: "current_view_" + currentViewIndex, id: "current_view_" + currentViewIndex,
}), }),
] ]
}) }),
) )
this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds) this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds)
@ -276,19 +276,19 @@ export default class ThemeViewState implements SpecialVisualizationState {
featureSwitches: this.featureSwitches, featureSwitches: this.featureSwitches,
}, },
layout?.isLeftRightSensitive() ?? false, layout?.isLeftRightSensitive() ?? false,
(e) => this.reportError(e) (e) => this.reportError(e),
) )
this.historicalUserLocations = this.geolocation.historicalUserLocations this.historicalUserLocations = this.geolocation.historicalUserLocations
this.newFeatures = new NewGeometryFromChangesFeatureSource( this.newFeatures = new NewGeometryFromChangesFeatureSource(
this.changes, this.changes,
layoutSource, layoutSource,
this.featureProperties this.featureProperties,
) )
layoutSource.addSource(this.newFeatures) layoutSource.addSource(this.newFeatures)
const perLayer = new PerLayerFeatureSourceSplitter( const perLayer = new PerLayerFeatureSourceSplitter(
Array.from(this.layerState.filteredLayers.values()).filter( Array.from(this.layerState.filteredLayers.values()).filter(
(l) => l.layerDef?.source !== null (l) => l.layerDef?.source !== null,
), ),
new ChangeGeometryApplicator(this.indexedFeatures, this.changes), new ChangeGeometryApplicator(this.indexedFeatures, this.changes),
{ {
@ -299,10 +299,10 @@ export default class ThemeViewState implements SpecialVisualizationState {
"Got ", "Got ",
features.length, features.length,
"leftover features, such as", "leftover features, such as",
features[0].properties features[0].properties,
) )
}, },
} },
) )
this.perLayer = perLayer.perLayer this.perLayer = perLayer.perLayer
} }
@ -342,12 +342,12 @@ export default class ThemeViewState implements SpecialVisualizationState {
this.lastClickObject = new LastClickFeatureSource( this.lastClickObject = new LastClickFeatureSource(
this.layout, this.layout,
this.mapProperties.lastClickLocation, this.mapProperties.lastClickLocation,
this.userRelatedState.addNewFeatureMode this.userRelatedState.addNewFeatureMode,
) )
this.osmObjectDownloader = new OsmObjectDownloader( this.osmObjectDownloader = new OsmObjectDownloader(
this.osmConnection.Backend(), this.osmConnection.Backend(),
this.changes this.changes,
) )
this.perLayerFiltered = this.showNormalDataOn(this.map) this.perLayerFiltered = this.showNormalDataOn(this.map)
@ -358,7 +358,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
currentZoom: this.mapProperties.zoom, currentZoom: this.mapProperties.zoom,
layerState: this.layerState, layerState: this.layerState,
bounds: this.visualFeedbackViewportBounds, bounds: this.visualFeedbackViewportBounds,
} },
) )
this.hasDataInView = new NoElementsInViewDetector(this).hasFeatureInView this.hasDataInView = new NoElementsInViewDetector(this).hasFeatureInView
this.imageUploadManager = new ImageUploadManager( this.imageUploadManager = new ImageUploadManager(
@ -366,12 +366,12 @@ export default class ThemeViewState implements SpecialVisualizationState {
Imgur.singleton, Imgur.singleton,
this.featureProperties, this.featureProperties,
this.osmConnection, this.osmConnection,
this.changes this.changes,
) )
this.favourites = new FavouritesFeatureSource(this) this.favourites = new FavouritesFeatureSource(this)
this.featureSummary = this.setupSummaryLayer( this.featureSummary = this.setupSummaryLayer(
new LayerConfig(<LayerConfigJson>summaryLayer, "summaryLayer", true) new LayerConfig(<LayerConfigJson>summaryLayer, "summaryLayer", true),
) )
this.toCacheSavers = this.initSaveToLocalStorage() this.toCacheSavers = this.initSaveToLocalStorage()
this.initActors() this.initActors()
@ -411,7 +411,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
LayoutSource.fromCacheZoomLevel, LayoutSource.fromCacheZoomLevel,
fs, fs,
this.featureProperties, this.featureProperties,
fs.layer.layerDef.maxAgeOfCache fs.layer.layerDef.maxAgeOfCache,
) )
toLocalStorage.set(layerId, storage) toLocalStorage.set(layerId, storage)
}) })
@ -424,7 +424,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
const doShowLayer = this.mapProperties.zoom.map( const doShowLayer = this.mapProperties.zoom.map(
(z) => (z) =>
(fs.layer.isDisplayed?.data ?? true) && z >= (fs.layer.layerDef?.minzoom ?? 0), (fs.layer.isDisplayed?.data ?? true) && z >= (fs.layer.layerDef?.minzoom ?? 0),
[fs.layer.isDisplayed] [fs.layer.isDisplayed],
) )
if (!doShowLayer.data && this.featureSwitches.featureSwitchFilter.data === false) { if (!doShowLayer.data && this.featureSwitches.featureSwitchFilter.data === false) {
@ -441,7 +441,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
fs.layer, fs.layer,
fs, fs,
(id) => this.featureProperties.getStore(id), (id) => this.featureProperties.getStore(id),
this.layerState.globalFilters this.layerState.globalFilters,
) )
filteringFeatureSource.set(layerName, filtered) filteringFeatureSource.set(layerName, filtered)
@ -469,7 +469,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
public showCurrentLocationOn(map: Store<MlMap>): ShowDataLayer { public showCurrentLocationOn(map: Store<MlMap>): ShowDataLayer {
const id = "gps_location" const id = "gps_location"
const flayerGps = this.layerState.filteredLayers.get(id) const flayerGps = this.layerState.filteredLayers.get(id)
if(flayerGps === undefined){ if (flayerGps === undefined) {
return return
} }
const features = this.geolocation.currentUserLocation const features = this.geolocation.currentUserLocation
@ -585,7 +585,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
return return
} }
this.selectClosestAtCenter(0) this.selectClosestAtCenter(0)
} },
) )
for (let i = 1; i < 9; i++) { for (let i = 1; i < 9; i++) {
@ -603,7 +603,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
onUp: true, onUp: true,
}, },
doc, doc,
() => this.selectClosestAtCenter(i - 1) () => this.selectClosestAtCenter(i - 1),
) )
} }
@ -620,7 +620,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
if (this.featureSwitches.featureSwitchBackgroundSelection.data) { if (this.featureSwitches.featureSwitchBackgroundSelection.data) {
this.guistate.backgroundLayerSelectionIsOpened.setData(true) this.guistate.backgroundLayerSelectionIsOpened.setData(true)
} }
} },
) )
Hotkeys.RegisterHotkey( Hotkeys.RegisterHotkey(
{ {
@ -632,14 +632,14 @@ export default class ThemeViewState implements SpecialVisualizationState {
if (this.featureSwitches.featureSwitchFilter.data) { if (this.featureSwitches.featureSwitchFilter.data) {
this.guistate.openFilterView() this.guistate.openFilterView()
} }
} },
) )
Hotkeys.RegisterHotkey( Hotkeys.RegisterHotkey(
{ shift: "O" }, { shift: "O" },
Translations.t.hotkeyDocumentation.selectMapnik, Translations.t.hotkeyDocumentation.selectMapnik,
() => { () => {
this.mapProperties.rasterLayer.setData(AvailableRasterLayers.osmCarto) this.mapProperties.rasterLayer.setData(AvailableRasterLayers.osmCarto)
} },
) )
const setLayerCategory = (category: EliCategory) => { const setLayerCategory = (category: EliCategory) => {
const available = this.availableLayers.data const available = this.availableLayers.data
@ -647,7 +647,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
const best = RasterLayerUtils.SelectBestLayerAccordingTo( const best = RasterLayerUtils.SelectBestLayerAccordingTo(
available, available,
category, category,
current.data current.data,
) )
console.log("Best layer for category", category, "is", best.properties.id) console.log("Best layer for category", category, "is", best.properties.id)
current.setData(best) current.setData(best)
@ -656,26 +656,26 @@ export default class ThemeViewState implements SpecialVisualizationState {
Hotkeys.RegisterHotkey( Hotkeys.RegisterHotkey(
{ nomod: "O" }, { nomod: "O" },
Translations.t.hotkeyDocumentation.selectOsmbasedmap, Translations.t.hotkeyDocumentation.selectOsmbasedmap,
() => setLayerCategory("osmbasedmap") () => setLayerCategory("osmbasedmap"),
) )
Hotkeys.RegisterHotkey( Hotkeys.RegisterHotkey(
{ nomod: "M" }, { nomod: "M" },
Translations.t.hotkeyDocumentation.selectMap, Translations.t.hotkeyDocumentation.selectMap,
() => setLayerCategory("map") () => setLayerCategory("map"),
) )
Hotkeys.RegisterHotkey( Hotkeys.RegisterHotkey(
{ nomod: "P" }, { nomod: "P" },
Translations.t.hotkeyDocumentation.selectAerial, Translations.t.hotkeyDocumentation.selectAerial,
() => setLayerCategory("photo") () => setLayerCategory("photo"),
) )
Hotkeys.RegisterHotkey( Hotkeys.RegisterHotkey(
{ nomod: "L" }, { nomod: "L" },
Translations.t.hotkeyDocumentation.geolocate, Translations.t.hotkeyDocumentation.geolocate,
() => { () => {
this.geolocationControl.handleClick() this.geolocationControl.handleClick()
} },
) )
return true return true
}) })
@ -687,7 +687,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
Translations.t.hotkeyDocumentation.translationMode, Translations.t.hotkeyDocumentation.translationMode,
() => { () => {
Locale.showLinkToWeblate.setData(!Locale.showLinkToWeblate.data) Locale.showLinkToWeblate.setData(!Locale.showLinkToWeblate.data)
} },
) )
} }
@ -698,7 +698,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
const normalLayers = this.layout.layers.filter( const normalLayers = this.layout.layers.filter(
(l) => (l) =>
Constants.priviliged_layers.indexOf(<any>l.id) < 0 && Constants.priviliged_layers.indexOf(<any>l.id) < 0 &&
!l.id.startsWith("note_import") !l.id.startsWith("note_import"),
) )
const maxzoom = Math.min(...normalLayers.map((l) => l.minzoom)) const maxzoom = Math.min(...normalLayers.map((l) => l.minzoom))
@ -706,7 +706,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
(l) => (l) =>
Constants.priviliged_layers.indexOf(<any>l.id) < 0 && Constants.priviliged_layers.indexOf(<any>l.id) < 0 &&
l.source.geojsonSource === undefined && l.source.geojsonSource === undefined &&
l.doCount l.doCount,
) )
const summaryTileSource = new SummaryTileSource( const summaryTileSource = new SummaryTileSource(
Constants.SummaryServer, Constants.SummaryServer,
@ -715,7 +715,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
this.mapProperties, this.mapProperties,
{ {
isActive: this.mapProperties.zoom.map((z) => z < maxzoom), isActive: this.mapProperties.zoom.map((z) => z < maxzoom),
} },
) )
const src = new SummaryTileSourceRewriter(summaryTileSource, this.layerState.filteredLayers) const src = new SummaryTileSourceRewriter(summaryTileSource, this.layerState.filteredLayers)
@ -737,12 +737,12 @@ export default class ThemeViewState implements SpecialVisualizationState {
gps_location_history: this.geolocation.historicalUserLocations, gps_location_history: this.geolocation.historicalUserLocations,
gps_track: this.geolocation.historicalUserLocationsTrack, gps_track: this.geolocation.historicalUserLocationsTrack,
selected_element: new StaticFeatureSource( selected_element: new StaticFeatureSource(
this.selectedElement.map((f) => (f === undefined ? empty : [f])) this.selectedElement.map((f) => (f === undefined ? empty : [f])),
), ),
range: new StaticFeatureSource( range: new StaticFeatureSource(
this.mapProperties.maxbounds.map((bbox) => this.mapProperties.maxbounds.map((bbox) =>
bbox === undefined ? empty : <Feature[]>[bbox.asGeoJson({ id: "range" })] bbox === undefined ? empty : <Feature[]>[bbox.asGeoJson({ id: "range" })],
) ),
), ),
current_view: this.currentView, current_view: this.currentView,
favourite: this.favourites, favourite: this.favourites,
@ -757,7 +757,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
ShowDataLayer.showRange( ShowDataLayer.showRange(
this.map, this.map,
new StaticFeatureSource([bbox.asGeoJson({ id: "range" })]), new StaticFeatureSource([bbox.asGeoJson({ id: "range" })]),
this.featureSwitches.featureSwitchIsTesting this.featureSwitches.featureSwitchIsTesting,
) )
} }
const currentViewLayer = this.layout.layers.find((l) => l.id === "current_view") const currentViewLayer = this.layout.layers.find((l) => l.id === "current_view")
@ -771,7 +771,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
currentViewLayer, currentViewLayer,
this.layout, this.layout,
this.osmObjectDownloader, this.osmObjectDownloader,
this.featureProperties this.featureProperties,
) )
}) })
} }
@ -815,20 +815,20 @@ export default class ThemeViewState implements SpecialVisualizationState {
const lastClickLayerConfig = new LayerConfig( const lastClickLayerConfig = new LayerConfig(
<LayerConfigJson>last_click_layerconfig, <LayerConfigJson>last_click_layerconfig,
"last_click" "last_click",
) )
const lastClickFiltered = const lastClickFiltered =
lastClickLayerConfig.isShown === undefined lastClickLayerConfig.isShown === undefined
? specialLayers.last_click ? specialLayers.last_click
: specialLayers.last_click.features.mapD((fs) => : specialLayers.last_click.features.mapD((fs) =>
fs.filter((f) => { fs.filter((f) => {
const matches = lastClickLayerConfig.isShown.matchesProperties( const matches = lastClickLayerConfig.isShown.matchesProperties(
f.properties f.properties,
) )
console.debug("LastClick ", f, "matches", matches) console.debug("LastClick ", f, "matches", matches)
return matches return matches
}) }),
) )
new ShowDataLayer(this.map, { new ShowDataLayer(this.map, {
features: new StaticFeatureSource(lastClickFiltered), features: new StaticFeatureSource(lastClickFiltered),
layer: lastClickLayerConfig, layer: lastClickLayerConfig,
@ -873,7 +873,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
this.mapProperties.rasterLayer, this.mapProperties.rasterLayer,
this.availableLayers, this.availableLayers,
this.featureSwitches.backgroundLayerId, this.featureSwitches.backgroundLayerId,
this.userRelatedState.preferredBackgroundLayer this.userRelatedState.preferredBackgroundLayer,
) )
} }
@ -883,29 +883,37 @@ export default class ThemeViewState implements SpecialVisualizationState {
} }
public async reportError(message: string | Error | XMLHttpRequest) { public async reportError(message: string | Error | XMLHttpRequest) {
console.log(">>> Reporting error to", Constants.ErrorReportServer, message) const isTesting = this.featureSwitchIsTesting.data
console.log(isTesting ? ">>> _Not_ reporting error to report server as testmode is on" : ">>> Reporting error to", Constants.ErrorReportServer, message)
if (isTesting) {
return
}
if ("" + message === "[object XMLHttpRequest]") { if ("" + message === "[object XMLHttpRequest]") {
const req = <XMLHttpRequest>message const req = <XMLHttpRequest>message
message = "XMLHttpRequest with status code " + req.status + ", " + req.statusText message = "XMLHttpRequest with status code " + req.status + ", " + req.statusText
} }
let stacktrace: string = new Error().stack const stacktrace: string = new Error().stack
await fetch(Constants.ErrorReportServer, { try {
method: "POST", await fetch(Constants.ErrorReportServer, {
body: JSON.stringify({ method: "POST",
stacktrace, body: JSON.stringify({
message: "" + message, stacktrace,
layout: this.layout.id, message: "" + message,
version: Constants.vNumber, layout: this.layout.id,
language: this.userRelatedState.language.data, version: Constants.vNumber,
username: this.osmConnection.userDetails.data?.name, language: this.userRelatedState.language.data,
userid: this.osmConnection.userDetails.data?.uid, username: this.osmConnection.userDetails.data?.name,
pendingChanges: this.changes.pendingChanges.data, userid: this.osmConnection.userDetails.data?.uid,
previousChanges: this.changes.allChanges.data, pendingChanges: this.changes.pendingChanges.data,
changeRewrites: Utils.MapToObj(this.changes._changesetHandler._remappings) previousChanges: this.changes.allChanges.data,
}), changeRewrites: Utils.MapToObj(this.changes._changesetHandler._remappings),
}) }),
})
} catch (e) {
console.error("Could not upload an error report")
}
} }
} }

View file

@ -70,7 +70,7 @@
try{ try{
_map?.remove() _map?.remove()
}catch (e) { }catch (e) {
console.log("Could not remove map due to", e) console.debug("Could not remove map due to", e)
} }
if(autorecovery){ if(autorecovery){
requestAnimationFrame(() => { requestAnimationFrame(() => {

View file

@ -299,6 +299,11 @@
<If condition={state.featureSwitchIsTesting}> <If condition={state.featureSwitchIsTesting}>
<div class="alert w-fit">Testmode</div> <div class="alert w-fit">Testmode</div>
</If> </If>
{#if state.osmConnection.Backend().startsWith("https://master.apis.dev.openstreetmap.org")}
<div class="thanks">
Testserver
</div>
{/if}
<If condition={state.featureSwitches.featureSwitchFakeUser}> <If condition={state.featureSwitches.featureSwitchFakeUser}>
<div class="alert w-fit">Faking a user (Testmode)</div> <div class="alert w-fit">Faking a user (Testmode)</div>
</If> </If>