Add createNewWay-action, more work on GRB import theme, add import button

This commit is contained in:
pietervdvn 2021-10-29 16:38:33 +02:00
parent e4cd93ffb0
commit da65bbbc86
9 changed files with 341 additions and 100 deletions

View file

@ -57,13 +57,14 @@ export class ExtraFunction {
doc: "Gives a list of features from the specified layer which this feature (partly) overlaps with. " +
"If the current feature is a point, all features that embed the point are given. " +
"The returned value is `{ feat: GeoJSONFeature, overlap: number}[]` where `overlap` is the overlapping surface are (in m²) for areas, the overlapping length (in meter) if the current feature is a line or `undefined` if the current feature is a point.\n" +
"The resulting list is sorted in descending order by overlap. The feature with the most overlap will thus be the first in the list" +
"\n" +
"For example to get all objects which overlap or embed from a layer, use `_contained_climbing_routes_properties=feat.overlapWith('climbing_route')`",
args: ["...layerIds - one or more layer ids of the layer from which every feature is checked for overlap)"]
},
(params, feat) => {
return (...layerIds: string[]) => {
const result = []
const result : {feat:any, overlap: number}[]= []
const bbox = BBox.get(feat)
@ -79,6 +80,9 @@ export class ExtraFunction {
result.push(...GeoOperations.calculateOverlap(feat, otherLayer));
}
}
result.sort((a, b) => b.overlap - a.overlap)
return result;
}
}

View file

@ -16,7 +16,7 @@ export interface ChangeDescription {
/**
* The type of the change
*/
changeType: "answer" | "create" | "split" | "delete" | "move" | string
changeType: "answer" | "create" | "split" | "delete" | "move" | "import" | string | null
/**
* THe motivation for the change, e.g. 'deleted because does not exist anymore'
*/
@ -51,7 +51,8 @@ export interface ChangeDescription {
lat: number,
lon: number
} | {
// Coordinates are only used for rendering. They should be LAT, LON
/* Coordinates are only used for rendering. They should be LON, LAT
* */
coordinates: [number, number][]
nodes: number[],
} | {

View file

@ -8,20 +8,29 @@ import {GeoOperations} from "../../GeoOperations";
export default class CreateNewNodeAction extends OsmChangeAction {
/**
* Maps previously created points onto their assigned ID, to reuse the point if uplaoded
* "lat,lon" --> id
*/
private static readonly previouslyCreatedPoints = new Map<string, number>()
public newElementId: string = undefined
public newElementIdNumber: number = undefined
private readonly _basicTags: Tag[];
private readonly _lat: number;
private readonly _lon: number;
private readonly _snapOnto: OsmWay;
private readonly _reusePointDistance: number;
private meta: { changeType: "create" | "import"; theme: string };
private readonly _reusePreviouslyCreatedPoint: boolean;
constructor(basicTags: Tag[],
lat: number, lon: number,
options: {
snapOnto?: OsmWay,
reusePointWithinMeters?: number,
theme: string, changeType: "create" | "import" }) {
allowReuseOfPreviouslyCreatedPoints?: boolean,
snapOnto?: OsmWay,
reusePointWithinMeters?: number,
theme: string, changeType: "create" | "import" | null
}) {
super()
this._basicTags = basicTags;
this._lat = lat;
@ -31,18 +40,47 @@ export default class CreateNewNodeAction extends OsmChangeAction {
}
this._snapOnto = options?.snapOnto;
this._reusePointDistance = options?.reusePointWithinMeters ?? 1
this._reusePreviouslyCreatedPoint = options?.allowReuseOfPreviouslyCreatedPoints ?? (basicTags.length === 0)
this.meta = {
theme: options.theme,
changeType: options.changeType
}
}
public static registerIdRewrites(mappings: Map<string, string>) {
const toAdd: [string, number][] = []
this.previouslyCreatedPoints.forEach((oldId, key) => {
if (!mappings.has("node/" + oldId)) {
return;
}
const newId = Number(mappings.get("node/" + oldId).substr("node/".length))
toAdd.push([key, newId])
})
for (const [key, newId] of toAdd) {
CreateNewNodeAction.previouslyCreatedPoints.set(key, newId)
}
}
async CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]> {
if (this._reusePreviouslyCreatedPoint) {
const key = this._lat + "," + this._lon
const prev = CreateNewNodeAction.previouslyCreatedPoints
if (prev.has(key)) {
this.newElementIdNumber = prev.get(key)
this.newElementId = "node/" + this.newElementIdNumber
return []
}
}
const id = changes.getNewID()
const properties = {
id: "node/" + id
}
this.newElementId = "node/" + id
this.setElementId(id)
for (const kv of this._basicTags) {
if (typeof kv.value !== "string") {
throw "Invalid value: don't use a regex in a preset"
@ -84,8 +122,7 @@ export default class CreateNewNodeAction extends OsmChangeAction {
}
if (reusedPointId !== undefined) {
console.log("Reusing an existing point:", reusedPointId)
this.newElementId = "node/" + reusedPointId
this.setElementId(reusedPointId)
return [{
tags: new And(this._basicTags).asChange(properties),
type: "node",
@ -112,10 +149,20 @@ export default class CreateNewNodeAction extends OsmChangeAction {
coordinates: locations,
nodes: ids
},
meta:this.meta
meta: this.meta
}
]
}
private setElementId(id: number) {
this.newElementIdNumber = id;
this.newElementId = "node/"+id
if (!this._reusePreviouslyCreatedPoint) {
return
}
const key = this._lat + "," + this._lon
CreateNewNodeAction.previouslyCreatedPoints.set(key, id)
}
}

View file

@ -0,0 +1,74 @@
import {ChangeDescription} from "./ChangeDescription";
import OsmChangeAction from "./OsmChangeAction";
import {Changes} from "../Changes";
import {Tag} from "../../Tags/Tag";
import CreateNewNodeAction from "./CreateNewNodeAction";
import {And} from "../../Tags/And";
export default class CreateNewWayAction extends OsmChangeAction {
public newElementId: string = undefined
private readonly coordinates: ({ nodeId?: number, lat: number, lon: number })[];
private readonly tags: Tag[];
private readonly _options: { theme: string };
/***
* Creates a new way to upload to OSM
* @param tags: the tags to apply to the wya
* @param coordinates: the coordinates. Might have a nodeId, in this case, this node will be used
* @param options
*/
constructor(tags: Tag[], coordinates: ({ nodeId?: number, lat: number, lon: number })[], options: {
theme: string
}) {
super()
this.coordinates = coordinates;
this.tags = tags;
this._options = options;
}
protected async CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]> {
const newElements: ChangeDescription[] = []
const pointIds: number[] = []
for (const coordinate of this.coordinates) {
if (coordinate.nodeId !== undefined) {
pointIds.push(coordinate.nodeId)
continue
}
const newPoint = new CreateNewNodeAction([], coordinate.lat, coordinate.lon, {
changeType: null,
theme: this._options.theme
})
await changes.applyAction(newPoint)
pointIds.push(newPoint.newElementIdNumber)
}
// We have all created (or reused) all the points!
// Time to create the actual way
const id = changes.getNewID()
const newWay = <ChangeDescription> {
id,
type: "way",
meta:{
theme: this._options.theme,
changeType: "import"
},
tags: new And(this.tags).asChange({}),
changes: {
nodes: pointIds,
coordinates: this.coordinates.map(c => [c.lon, c.lat])
}
}
newElements.push(newWay)
this.newElementId = "way/"+id
return newElements
}
}

View file

@ -7,6 +7,7 @@ import {ChangeDescription} from "./Actions/ChangeDescription";
import {Utils} from "../../Utils";
import {LocalStorageSource} from "../Web/LocalStorageSource";
import SimpleMetaTagger from "../SimpleMetaTagger";
import CreateNewNodeAction from "./Actions/CreateNewNodeAction";
/**
* Handles all changes made to OSM.
@ -14,22 +15,20 @@ import SimpleMetaTagger from "../SimpleMetaTagger";
*/
export class Changes {
private _nextId: number = -1; // Newly assigned ID's are negative
public readonly name = "Newly added features"
/**
* All the newly created features as featureSource + all the modified features
*/
public features = new UIEventSource<{ feature: any, freshness: Date }[]>([]);
public readonly pendingChanges: UIEventSource<ChangeDescription[]> = LocalStorageSource.GetParsed<ChangeDescription[]>("pending-changes", [])
public readonly allChanges = new UIEventSource<ChangeDescription[]>(undefined)
private _nextId: number = -1; // Newly assigned ID's are negative
private readonly isUploading = new UIEventSource(false);
private readonly previouslyCreated: OsmObject[] = []
private readonly _leftRightSensitive: boolean;
constructor(leftRightSensitive : boolean = false) {
constructor(leftRightSensitive: boolean = false) {
this._leftRightSensitive = leftRightSensitive;
// We keep track of all changes just as well
this.allChanges.setData([...this.pendingChanges.data])
@ -114,21 +113,34 @@ export class Changes {
})
}
public async applyAction(action: OsmChangeAction): Promise<void> {
const changes = await action.Perform(this)
console.log("Received changes:", changes)
this.pendingChanges.data.push(...changes);
this.pendingChanges.ping();
this.allChanges.data.push(...changes)
this.allChanges.ping()
}
public registerIdRewrites(mappings: Map<string, string>): void {
CreateNewNodeAction.registerIdRewrites(mappings)
}
/**
* UPload the selected changes to OSM.
* Returns 'true' if successfull and if they can be removed
* @param pending
* @private
*/
private async flushSelectChanges(pending: ChangeDescription[]): Promise<boolean>{
private async flushSelectChanges(pending: ChangeDescription[]): Promise<boolean> {
const self = this;
const neededIds = Changes.GetNeededIds(pending)
const osmObjects = await Promise.all(neededIds.map(id => OsmObject.DownloadObjectAsync(id)));
if(this._leftRightSensitive){
if (this._leftRightSensitive) {
osmObjects.forEach(obj => SimpleMetaTagger.removeBothTagging(obj.tags))
}
console.log("Got the fresh objects!", osmObjects, "pending: ", pending)
const changes: {
newObjects: OsmObject[],
@ -137,35 +149,38 @@ export class Changes {
} = self.CreateChangesetObjects(pending, osmObjects)
if (changes.newObjects.length + changes.deletedObjects.length + changes.modifiedObjects.length === 0) {
console.log("No changes to be made")
return true
return true
}
const meta = pending[0].meta
const perType = Array.from(Utils.Hist(pending.map(descr => descr.meta.changeType)), ([key, count]) => ({
key: key,
value: count,
aggregate: true
}))
const perType = Array.from(
Utils.Hist(pending.filter(descr => descr.meta.changeType !== undefined && descr.meta.changeType !== null)
.map(descr => descr.meta.changeType)), ([key, count]) => (
{
key: key,
value: count,
aggregate: true
}))
const motivations = pending.filter(descr => descr.meta.specialMotivation !== undefined)
.map(descr => ({
key: descr.meta.changeType+":"+descr.type+"/"+descr.id,
value: descr.meta.specialMotivation
key: descr.meta.changeType + ":" + descr.type + "/" + descr.id,
value: descr.meta.specialMotivation
}))
const metatags = [{
key: "comment",
value: "Adding data with #MapComplete for theme #"+meta.theme
value: "Adding data with #MapComplete for theme #" + meta.theme
},
{
key:"theme",
value:meta.theme
key: "theme",
value: meta.theme
},
...perType,
...motivations
]
await State.state.osmConnection.changesetHandler.UploadChangeset(
(csId) => Changes.createChangesetFor(""+csId, changes),
(csId) => Changes.createChangesetFor("" + csId, changes),
metatags
)
@ -178,27 +193,27 @@ export class Changes {
try {
// At last, we build the changeset and upload
const pending = self.pendingChanges.data;
const pendingPerTheme = new Map<string, ChangeDescription[]>()
for (const changeDescription of pending) {
const theme = changeDescription.meta.theme
if(!pendingPerTheme.has(theme)){
if (!pendingPerTheme.has(theme)) {
pendingPerTheme.set(theme, [])
}
pendingPerTheme.get(theme).push(changeDescription)
}
const successes = await Promise.all(Array.from(pendingPerTheme, ([key , value]) => value)
const successes = await Promise.all(Array.from(pendingPerTheme, ([key, value]) => value)
.map(async pendingChanges => {
try{
try {
return await self.flushSelectChanges(pendingChanges);
}catch(e){
console.error("Could not upload some changes:",e)
} catch (e) {
console.error("Could not upload some changes:", e)
return false
}
}))
if(!successes.some(s => s == false)){
if (!successes.some(s => s == false)) {
// All changes successfull, we clear the data!
this.pendingChanges.setData([]);
}
@ -206,22 +221,13 @@ export class Changes {
} catch (e) {
console.error("Could not handle changes - probably an old, pending changeset in localstorage with an invalid format; erasing those", e)
self.pendingChanges.setData([])
}finally {
} finally {
self.isUploading.setData(false)
}
}
public async applyAction(action: OsmChangeAction): Promise<void> {
const changes = await action.Perform(this)
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[]): {
newObjects: OsmObject[],
modifiedObjects: OsmObject[]
@ -373,8 +379,4 @@ export class Changes {
return result
}
public registerIdRewrites(mappings: Map<string, string>): void {
}
}

View file

@ -4,20 +4,34 @@ import {UIEventSource} from "../../Logic/UIEventSource";
import Combine from "../Base/Combine";
import {VariableUiElement} from "../Base/VariableUIElement";
import Translations from "../i18n/Translations";
import State from "../../State";
import Constants from "../../Models/Constants";
import Toggle from "../Input/Toggle";
import CreateNewNodeAction from "../../Logic/Osm/Actions/CreateNewNodeAction";
import {Tag} from "../../Logic/Tags/Tag";
import Loading from "../Base/Loading";
import OsmChangeAction from "../../Logic/Osm/Actions/OsmChangeAction";
import CreateNewWayAction from "../../Logic/Osm/Actions/CreateNewWayAction";
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
import {OsmConnection} from "../../Logic/Osm/OsmConnection";
import {Changes} from "../../Logic/Osm/Changes";
import {ElementStorage} from "../../Logic/ElementStorage";
import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline";
export default class ImportButton extends Toggle {
constructor(imageUrl: string | BaseUIElement, message: string | BaseUIElement,
constructor(imageUrl: string | BaseUIElement,
message: string | BaseUIElement,
originalTags: UIEventSource<any>,
newTags: UIEventSource<Tag[]>,
lat: number, lon: number,
newTags: UIEventSource<Tag[]>,
feature: any,
minZoom: number,
state: {
state: {
featureSwitchUserbadge: UIEventSource<boolean>;
featurePipeline: FeaturePipeline;
allElements: ElementStorage;
selectedElement: UIEventSource<any>;
layoutToUse: LayoutConfig,
osmConnection: OsmConnection,
changes: Changes,
locationControl: UIEventSource<{ zoom: number }>
}) {
const t = Translations.t.general.add;
@ -32,7 +46,7 @@ export default class ImportButton extends Toggle {
const txt = parts.join(" & ")
return t.presetInfo.Subs({tags: txt}).SetClass("subtle")
})), undefined,
State.state.osmConnection.userDetails.map(ud => ud.csCount >= Constants.userJourney.tagsVisibleAt)
state.osmConnection.userDetails.map(ud => ud.csCount >= Constants.userJourney.tagsVisibleAt)
)
const button = new SubtleButton(imageUrl, message)
@ -44,15 +58,12 @@ export default class ImportButton extends Toggle {
}
originalTags.data["_imported"] = "yes"
originalTags.ping() // will set isImported as per its definition
const newElementAction = new CreateNewNodeAction(newTags.data, lat, lon, {
theme: State.state.layoutToUse.id,
changeType: "import"
})
await State.state.changes.applyAction(newElementAction)
State.state.selectedElement.setData(State.state.allElements.ContainingFeatures.get(
const newElementAction = ImportButton.createAddActionForFeature(newTags.data, feature, state.layoutToUse.id)
await state.changes.applyAction(newElementAction)
state.selectedElement.setData(state.allElements.ContainingFeatures.get(
newElementAction.newElementId
))
console.log("Did set selected element to", State.state.allElements.ContainingFeatures.get(
console.log("Did set selected element to", state.allElements.ContainingFeatures.get(
newElementAction.newElementId
))
@ -60,25 +71,70 @@ export default class ImportButton extends Toggle {
})
const withLoadingCheck = new Toggle(new Toggle(
new Loading(t.stillLoading.Clone()),
new Combine([button, appliedTags]).SetClass("flex flex-col"),
State.state.featurePipeline.runningQuery
),t.zoomInFurther.Clone(),
state.locationControl.map(l => l.zoom >= minZoom)
)
new Loading(t.stillLoading.Clone()),
new Combine([button, appliedTags]).SetClass("flex flex-col"),
state.featurePipeline.runningQuery
), t.zoomInFurther.Clone(),
state.locationControl.map(l => l.zoom >= minZoom)
)
const importButton = new Toggle(t.hasBeenImported, withLoadingCheck, isImported)
const pleaseLoginButton =
new Toggle(t.pleaseLogin.Clone()
.onClick(() => State.state.osmConnection.AttemptLogin())
.onClick(() => state.osmConnection.AttemptLogin())
.SetClass("login-button-friendly"),
undefined,
State.state.featureSwitchUserbadge)
state.featureSwitchUserbadge)
super(importButton,
pleaseLoginButton,
State.state.osmConnection.isLoggedIn
super(new Toggle(importButton,
pleaseLoginButton,
state.osmConnection.isLoggedIn
),
t.wrongType,
new UIEventSource(ImportButton.canBeImported(feature))
)
}
private static canBeImported(feature: any) {
const type = feature.geometry.type
return type === "Point" || type === "LineString" || (type === "Polygon" && feature.geometry.coordinates.length === 1)
}
private static createAddActionForFeature(newTags: Tag[], feature: any, theme: string): OsmChangeAction & { newElementId: string } {
const geometry = feature.geometry
const type = geometry.type
if (type === "Point") {
const lat = geometry.coordinates[1]
const lon = geometry.coordinates[0];
return new CreateNewNodeAction(newTags, lat, lon, {
theme,
changeType: "import"
})
}
if (type === "LineString") {
return new CreateNewWayAction(
newTags,
geometry.coordinates.map(coor => ({lon: coor[0], lat: coor[1]})),
{
theme
}
)
}
if (type === "Polygon") {
return new CreateNewWayAction(
newTags,
geometry.coordinates[0].map(coor => ({lon: coor[0], lat: coor[1]})),
{
theme
}
)
}
return undefined;
}
}

View file

@ -33,6 +33,7 @@ import AllKnownLayers from "../Customizations/AllKnownLayers";
import ShowDataLayer from "./ShowDataLayer/ShowDataLayer";
import Link from "./Base/Link";
import List from "./Base/List";
import {OsmConnection} from "../Logic/Osm/OsmConnection";
export interface SpecialVisualization {
funcName: string,
@ -480,7 +481,7 @@ export default class SpecialVisualizations {
args: [
{
name: "tags",
doc: "Tags to copy-specification. This contains one or more pairs (seperated by a `;`), e.g. `amenity=fast_food; addr:housenumber=$number`. This new point will then have the tags `amenity=fast_food` and `addr:housenumber` with the value that was saved in `number` in the original feature. (Hint: prepare these values, e.g. with calculatedTags)"
doc: "The tags to add onto the new object - see specification above"
},
{
name: "text",
@ -499,6 +500,8 @@ export default class SpecialVisualizations {
}],
docs: `This button will copy the data from an external dataset into OpenStreetMap. It is only functional in official themes but can be tested in unofficial themes.
#### Importing a dataset into OpenStreetMap: requirements
If you want to import a dataset, make sure that:
1. The dataset to import has a suitable license
@ -507,17 +510,41 @@ If you want to import a dataset, make sure that:
There are also some technicalities in your theme to keep in mind:
1. The new point will be added and will flow through the program as any other new point as if it came from OSM.
1. The new feature will be added and will flow through the program as any other new point as if it came from OSM.
This means that there should be a layer which will match the new tags and which will display it.
2. The original point from your geojson layer will gain the tag '_imported=yes'.
2. The original feature from your geojson layer will gain the tag '_imported=yes'.
This should be used to change the appearance or even to hide it (eg by changing the icon size to zero)
3. There should be a way for the theme to detect previously imported points, even after reloading.
A reference number to the original dataset is an excellen way to do this
A reference number to the original dataset is an excellent way to do this
4. When importing ways, the theme creator is also responsible of avoiding overlapping ways.
#### Disabled in unofficial themes
The import button can be tested in an unofficial theme by adding \`test=true\` or \`backend=osm-test\` as [URL-paramter](URL_Parameters.md).
The import button will show up then. If in testmode, you can read the changeset-XML directly in the web console.
In the case that MapComplete is pointed to the testing grounds, the edit will be made on ${OsmConnection.oauth_configs["osm-test"].url}
#### Specifying which tags to copy or add
The first argument of the import button takes a \`;\`-seperated list of tags to add.
These can either be a tag to add, such as \`amenity=fast_food\` or can use a substitution, e.g. \`addr:housenumber=$number\`.
This new point will then have the tags \`amenity=fast_food\` and \`addr:housenumber\` with the value that was saved in \`number\` in the original feature.
If a value to substitute is undefined, empty string will be used instead.
This supports multiple values, e.g. \`ref=$source:geometry:type/$source:geometry:ref\`
Remark that the syntax is slightly different then expected; it uses '$' to note a value to copy, followed by a name (matched with \`[a-zA-Z0-9_:]*\`). Sadly, delimiting with \`{}\` as these already mark the boundaries of the special rendering...
Note that these values can be prepare with javascript in the theme by using a [calculatedTag](calculatedTags.md#calculating-tags-with-javascript)
`,
constr: (state, tagSource, args) => {
if (!state.layoutToUse.official && !state.featureSwitchIsTesting.data) {
if (!state.layoutToUse.official && !(state.featureSwitchIsTesting.data || state.osmConnection._oauth_config.url === OsmConnection.oauth_configs["osm-test"].url)) {
return new Combine([new FixedUiElement("The import button is disabled for unofficial themes to prevent accidents.").SetClass("alert"),
new FixedUiElement("To test, add 'test=true' to the URL. The changeset will be printed in the console. Please open a PR to officialize this theme to actually enable the import button.")])
new FixedUiElement("To test, add <b>test=true</b> or <b>backend=osm-test</b> to the URL. The changeset will be printed in the console. Please open a PR to officialize this theme to actually enable the import button.")])
}
const tgsSpec = args[0].split(";").map(spec => {
const kv = spec.split("=").map(s => s.trim());
@ -529,9 +556,18 @@ There are also some technicalities in your theme to keep in mind:
const rewrittenTags: UIEventSource<Tag[]> = tagSource.map(tags => {
const newTags: Tag [] = []
for (const [key, value] of tgsSpec) {
if (value.startsWith('$')) {
const origKey = value.substring(1)
newTags.push(new Tag(key, tags[origKey]))
if (value.indexOf('$') >= 0) {
let parts = value.split("$")
// THe first of the split won't start with a '$', so no substitution needed
let actualValue = parts[0]
parts.shift()
for (const part of parts) {
const [_, varName, leftOver] = part.match(/([a-zA-Z0-9_:]*)(.*)/)
actualValue += (tags[varName] ?? "") + leftOver
}
newTags.push(new Tag(key, actualValue))
} else {
newTags.push(new Tag(key, value))
}
@ -540,12 +576,12 @@ There are also some technicalities in your theme to keep in mind:
})
const id = tagSource.data.id;
const feature = state.allElements.ContainingFeatures.get(id)
if (feature.geometry.type !== "Point") {
return new FixedUiElement("Error: can only import point objects").SetClass("alert")
}
const [lon, lat] = feature.geometry.coordinates;
const minzoom = Number(args[3])
const message = args[1]
const image = args[2]
return new ImportButton(
args[2], args[1], tagSource, rewrittenTags, lat, lon, Number(args[3]), state
image, message, tagSource, rewrittenTags, feature, minzoom, state
)
}
},

View file

@ -565,16 +565,36 @@
"title": "GRB outline",
"minzoom": 16,
"calculatedTags": [
"_overlaps_with=feat.overlapWith('OSM-buildings').filter(f => f.overlap > 1 && feat.properties._surface - f.overlap < 5)[0] ?? null",
"_osm_obj:source:ref=JSON.parse(feat.properties._overlaps_with)?.feat?.properties['source:geometry:ref']",
"_osm_obj:source:date=JSON.parse(feat.properties._overlaps_with)?.feat?.properties['source:geometry:date'].replace(/\\//g, '-')",
"_imported_osm_object_found= feat.properties['_osm_obj:source:ref'] == feat.properties['source:geometry:entity'] + '/' + feat.properties['source:geometry:oidn']",
"_overlaps_with=feat.overlapWith('OSM-buildings').filter(f => f.overlap > 1 && (feat.get('_surface') < 20 || f.overlap / feat.get('_surface')) > 0.9)[0] ?? null",
"_overlap_absolute=feat.get('_overlaps_with')?.overlap",
"_overlap_percentage=Math.round(100 * feat.get('_overlap_absolute') / feat.get('_surface')) ",
"_osm_obj:source:ref=feat.get('_overlaps_with')?.feat?.properties['source:geometry:ref']",
"_osm_obj:source:date=feat.get('_overlaps_with')?.feat?.properties['source:geometry:date'].replace(/\\//g, '-')",
"_osm_obj:building=feat.get('_overlaps_with')?.feat?.properties.building",
"_osm_obj:id=feat.get('_overlaps_with')?.feat?.properties.id",
"_grb_ref=feat.properties['source:geometry:entity'] + '/' + feat.properties['source:geometry:oidn']",
"_imported_osm_object_found= feat.properties['_osm_obj:source:ref'] == feat.properties._grb_ref",
"_grb_date=feat.properties['source:geometry:date'].replace(/\\//g,'-')",
"_imported_osm_still_fresh= feat.properties['_osm_obj:source:date'] == feat.properties._grb_date"
],
"tagRenderings": [
{
"render": "{import_button(Upload this geometry to OpenStreetMap)}"
"id": "Building info",
"render": "This is a <b>{building}</b> <span class='subtle'>detected by {detection_method}</span>"
},
{
"id": "overlapping building type",
"render": "<div>The overlapping openstreetmap-building is a <b>{_osm_obj:building}</b> and covers <b>{_overlap_percentage}%</b> of the GRB building<div><h3>GRB geometry:</h3>{minimap(21, id):height:10rem;border-radius:1rem;overflow:hidden}<h3>OSM geometry:</h3>{minimap(21,_osm_obj:id):height:10rem;border-radius:1rem;overflow:hidden}",
"condition": "_overlaps_with!=null"
},
{
"id": "Import-button",
"render": "{import_button(building=$building; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref, Upload this building to OpenStreetMap)}",
"mappings": [
{
"if": "_overlaps_with!=null",
"then": "Cannot be imported directly, there is a nearly identical building geometry in OpenStreetMap"
}]
},
"all_tags"
],

View file

@ -108,7 +108,8 @@
"openLayerControl": "Open the layer control box",
"layerNotEnabled": "The layer {layer} is not enabled. Enable this layer to add a point",
"hasBeenImported": "This point has already been imported",
"zoomInMore": "Zoom in more to import this feature"
"zoomInMore": "Zoom in more to import this feature",
"wrongType": "This element is not a point or a way and can not be imported"
},
"pickLanguage": "Choose a language: ",
"about": "Easily edit and add OpenStreetMap for a certain theme",