Refactoring the import button

This commit is contained in:
pietervdvn 2021-12-09 13:16:40 +01:00
parent f29c62ab19
commit 6e84dfcab0
11 changed files with 352 additions and 208 deletions

View file

@ -16,11 +16,10 @@ import {ElementStorage} from "../../Logic/ElementStorage";
import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline";
import Lazy from "../Base/Lazy";
import ConfirmLocationOfPoint from "../NewPoint/ConfirmLocationOfPoint";
import {PresetInfo} from "./SimpleAddUI";
import Img from "../Base/Img";
import {Translation} from "../i18n/Translation";
import FilteredLayer from "../../Models/FilteredLayer";
import SpecialVisualizations, {SpecialVisualization} from "../SpecialVisualizations";
import SpecialVisualizations from "../SpecialVisualizations";
import {FixedUiElement} from "../Base/FixedUiElement";
import Svg from "../../Svg";
import {Utils} from "../../Utils";
@ -31,10 +30,13 @@ import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeature
import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer";
import BaseLayer from "../../Models/BaseLayer";
import ReplaceGeometryAction from "../../Logic/Osm/Actions/ReplaceGeometryAction";
import CreateWayWithPointReuseAction from "../../Logic/Osm/Actions/CreateWayWithPointReuseAction";
import CreateWayWithPointReuseAction, {MergePointConfig} from "../../Logic/Osm/Actions/CreateWayWithPointReuseAction";
import OsmChangeAction from "../../Logic/Osm/Actions/OsmChangeAction";
import FeatureSource from "../../Logic/FeatureSource/FeatureSource";
import {OsmObject, OsmWay} from "../../Logic/Osm/OsmObject";
import FeaturePipelineState from "../../Logic/State/FeaturePipelineState";
import {DefaultGuiState} from "../DefaultGuiState";
import {PresetInfo} from "../BigComponents/SimpleAddUI";
export interface ImportButtonState {
@ -60,224 +62,265 @@ export interface ImportButtonState {
},
guiState: { filterViewIsOpened: UIEventSource<boolean> },
/**
* SnapSettings for newly imported points
*/
snapSettings?: {
snapToLayers: string[],
snapToLayersMaxDist?: number
},
/**
* Settings if an imported feature must be conflated with an already existing feature
*/
conflationSettings?: {
conflateWayId: string
}
/**
* Settings for newly created points which are part of a way: when to snap to already existing points?
*/
mergeConfigs: MergePointConfig[]
}
export class ImportButtonSpecialViz implements SpecialVisualization {
funcName = "import_button"
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
abstract class AbstractImportButton implements SpecialVisualizations {
public readonly funcName: string
public readonly docs: string
public readonly args: { name: string, defaultValue?: string, doc: string }[]
If you want to import a dataset, make sure that:
constructor(funcName: string, docsIntro: string, extraArgs: { name: string, doc: string, defaultValue?: string }[]) {
this.funcName = funcName
1. The dataset to import has a suitable license
2. The community has been informed of the import
3. All other requirements of the [import guidelines](https://wiki.openstreetmap.org/wiki/Import/Guidelines) have been followed
There are also some technicalities in your theme to keep in mind:
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 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 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}
this.docs = `${docsIntro}
Note that the contributor must zoom to at least zoomlevel 18 to be able to use this functionality.
It is only functional in official themes, but can be tested in unoffical themes.
#### Specifying which tags to copy or add
The argument \`tags\` of the import button takes a \`;\`-seperated list of tags to add.
${Utils.Special_visualizations_tagsToApplyHelpText}
${Utils.special_visualizations_importRequirementDocs}
`
args = [
{
name: "targetLayer",
doc: "The id of the layer where this point should end up. This is not very strict, it will simply result in checking that this layer is shown preventing possible duplicate elements"
},
{
name: "tags",
doc: "The tags to add onto the new object - see specification above"
},
{
name: "text",
doc: "The text to show on the button",
defaultValue: "Import this data into OpenStreetMap"
},
{
name: "icon",
doc: "A nice icon to show in the button",
defaultValue: "./assets/svg/addSmall.svg"
},
{
name: "minzoom",
doc: "How far the contributor must zoom in before being able to import the point",
defaultValue: "18"
},
{
name: "Snap onto layer(s)/replace geometry with this other way",
doc: " - If the value corresponding with this key starts with 'way/' and the feature is a LineString or Polygon, the original OSM-way geometry will be changed to match the new geometry\n" +
" - If a way of the given layer(s) is closeby, will snap the new point onto this way (similar as preset might snap). To show multiple layers to snap onto, use a `;`-seperated list",
},
{
name: "snap max distance",
doc: "The maximum distance that this point will move to snap onto a layer (in meters)",
defaultValue: "5"
}]
getLayerDependencies(args: string[]){
const dependsOnLayers: string[] = []
// The target layer
dependsOnLayers.push(args[0])
const snapOntoLayers = args[5]?.trim() ?? "";
if(args[5] !== ""){
dependsOnLayers.push(...snapOntoLayers.split(";"))
}
return dependsOnLayers
}
constr(state, tagSource, args, guiState) {
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 <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 newTags = SpecialVisualizations.generateTagsToApply(args[1], tagSource)
const id = tagSource.data.id;
const feature = state.allElements.ContainingFeatures.get(id)
let minZoom = args[4] == "" ? 18 : Number(args[4])
if (isNaN(minZoom)) {
console.warn("Invalid minzoom:", minZoom)
minZoom = 18
}
const message = args[2]
const imageUrl = args[3]
let img: () => BaseUIElement
const targetLayer: FilteredLayer = state.filteredLayers.data.filter(fl => fl.layerDef.id === args[0])[0]
this.args = [
{
name: "targetLayer",
doc: "The id of the layer where this point should end up. This is not very strict, it will simply result in checking that this layer is shown preventing possible duplicate elements"
},
{
name: "tags",
doc: "The tags to add onto the new object - see specification above"
},
{
name: "text",
doc: "The text to show on the button",
defaultValue: "Import this data into OpenStreetMap"
},
{
name: "icon",
doc: "A nice icon to show in the button",
defaultValue: "./assets/svg/addSmall.svg"
},
...extraArgs]
if (imageUrl !== undefined && imageUrl !== "") {
img = () => new Img(imageUrl)
} else {
img = () => Svg.add_ui()
}
};
let snapSettings = undefined
let conflationSettings = undefined
const possibleWayId = tagSource.data[args[5]]
if (possibleWayId?.startsWith("way/")) {
// This is a conflation
conflationSettings = {
conflateWayId: possibleWayId
abstract constructElement(state: FeaturePipelineState, args: { minzoom: string, max_snap_distance: string, snap_onto_layers: string, icon: string, text: string, tags: string, targetLayer: string },
tagSource: UIEventSource<any>, guiState: DefaultGuiState): BaseUIElement;
constr(state, tagSource, argsRaw, guiState) {
/**
* Some generic import button pre-validation is implemented here:
* - Are we logged in?
* - Did the user zoom in enough?
* ...
*
* The actual import flow (showing the conflation map, special cases) are handled in 'constructElement'
*/
const t = Translations.t.general.add.import;
const t0 = Translations.t.general.add;
const args = this.parseArgs(argsRaw)
{
// Some initial validation
if (!state.layoutToUse.official && !(state.featureSwitchIsTesting.data || state.osmConnection._oauth_config.url === OsmConnection.oauth_configs["osm-test"].url)) {
return new Combine([t.officialThemesOnly.SetClass("alert"), t.howToTest])
}
} else {
const snapToLayers = args[5]?.split(";")?.filter(s => s !== "")
const snapToLayersMaxDist = Number(args[6] ?? 6)
const targetLayer: FilteredLayer = state.filteredLayers.data.filter(fl => fl.layerDef.id === args.targetLayer)[0]
if (targetLayer === undefined) {
const e = "Target layer not defined: error in import button for theme: " + state.layoutToUse.id + ": layer " + args[0] + " not found"
const e = `Target layer not defined: error in import button for theme: ${state.layoutToUse.id}: layer ${args.targetLayer} not found`
console.error(e)
return new FixedUiElement(e).SetClass("alert")
}
snapSettings = {
snapToLayers,
snapToLayersMaxDist
}
}
return new ImportButton(
{
state, guiState, image: img,
feature, newTags, message, minZoom,
originalTags: tagSource,
targetLayer,
snapSettings,
conflationSettings
}
);
}
}
export default class ImportButton extends Toggle {
let img: BaseUIElement
if (args.icon !== undefined && args.icon !== "") {
img = new Img(args.icon)
} else {
img = Svg.add_ui()
}
const inviteToImportButton = new SubtleButton(img, args.text)
constructor(o: ImportButtonState) {
const t = Translations.t.general.add;
const isImported = o.originalTags.map(tags => tags._imported === "yes")
const id = tagSource.data.id;
const feature = state.allElements.ContainingFeatures.get(id)
/**** THe actual panel showing the import guiding map ****/
const importGuidingPanel = this.constructElement(state, args, tagSource, guiState)
// Explanation of the tags that will be applied onto the imported/conflated object
const newTags = SpecialVisualizations.generateTagsToApply(args.tags, tagSource)
const appliedTags = new Toggle(
new VariableUiElement(
o.newTags.map(tgs => {
newTags.map(tgs => {
const parts = []
for (const tag of tgs) {
parts.push(tag.key + "=" + tag.value)
}
const txt = parts.join(" & ")
return t.presetInfo.Subs({tags: txt}).SetClass("subtle")
return t0.presetInfo.Subs({tags: txt}).SetClass("subtle")
})), undefined,
o.state.osmConnection.userDetails.map(ud => ud.csCount >= Constants.userJourney.tagsVisibleAt)
state.osmConnection.userDetails.map(ud => ud.csCount >= Constants.userJourney.tagsVisibleAt)
)
const button = new SubtleButton(o.image(), o.message)
o.minZoom = Math.max(16, o.minZoom ?? 19)
const withLoadingCheck = new Toggle(new Toggle(
new Loading(t.stillLoading.Clone()),
new Combine([button, appliedTags]).SetClass("flex flex-col"),
o.state.featurePipeline.runningQuery
), t.zoomInFurther.Clone(),
o.state.locationControl.map(l => l.zoom >= o.minZoom)
)
const importButton = new Toggle(t.hasBeenImported, withLoadingCheck, isImported)
const importClicked = new UIEventSource(false);
const importFlow = new Toggle(
ImportButton.createConfirmPanel(o, isImported, importClicked),
importButton,
importClicked
)
button.onClick(() => {
inviteToImportButton.onClick(() => {
importClicked.setData(true);
})
const pleaseLoginButton =
new Toggle(t.pleaseLogin.Clone()
.onClick(() => o.state.osmConnection.AttemptLogin())
.SetClass("login-button-friendly"),
undefined,
o.state.featureSwitchUserbadge)
const pleaseLoginButton = new Toggle(t0.pleaseLogin
.onClick(() => state.osmConnection.AttemptLogin())
.SetClass("login-button-friendly"),
undefined,
state.featureSwitchUserbadge)
super(new Toggle(importFlow,
const isImported = tagSource.map(tags => tags._imported === "yes")
const importFlow = new Toggle(
new Toggle(
new Loading(t0.stillLoading),
new Combine([importGuidingPanel, appliedTags]).SetClass("flex flex-col"),
state.featurePipeline.runningQuery
) ,
inviteToImportButton,
importClicked
);
return new Toggle(
new Toggle(
new Toggle(
new Toggle(
t.hasBeenImported,
importFlow,
isImported
),
t.zoomInMore,
state.locationControl.map(l => l.zoom >= 18)
),
pleaseLoginButton,
o.state.osmConnection.isLoggedIn
state.osmConnection.isLoggedIn
),
t.wrongType,
new UIEventSource(ImportButton.canBeImported(o.feature))
new UIEventSource(this.canBeImported(feature)))
}
private parseArgs(argsRaw: string[]): { minzoom: string, max_snap_distance: string, snap_onto_layers: string, icon: string, text: string, tags: string, targetLayer: string } {
return Utils.ParseVisArgs(this.args, argsRaw)
}
getLayerDependencies(argsRaw: string[]) {
const args = this.parseArgs(argsRaw)
const dependsOnLayers: string[] = []
// The target layer
dependsOnLayers.push(args.targetLayer)
const snapOntoLayers = args.snap_onto_layers?.trim() ?? "";
if (snapOntoLayers !== "") {
dependsOnLayers.push(...snapOntoLayers.split(";"))
}
return dependsOnLayers
}
protected abstract canBeImported(feature: any)
}
export class ImportButtonSpecialViz extends AbstractImportButton {
constructor() {
super("import_button",
"This button will copy the data from an external dataset into OpenStreetMap",
[{
name: "snap_onto_layers",
doc: "If a way of the given layer(s) is closeby, will snap the new point onto this way (similar as preset might snap). To show multiple layers to snap onto, use a `;`-seperated list",
},
{
name: "max_snap_distance",
doc: "If the imported object is a point, the maximum distance that this point will be moved to snap onto a way in an already existing layer (in meters)",
defaultValue: "5"
}]
)
}
canBeImported(feature: any) {
const type = feature.geometry.type
return type === "Point" || type === "LineString" || type === "Polygon"
}
constructElement(state, args,
tagSource,
guiState): BaseUIElement {
let snapSettings = undefined
{
// Configure the snapsettings (if applicable)
const snapToLayers = args.snap_onto_layers?.trim()?.split(";")?.filter(s => s !== "")
const snapToLayersMaxDist = Number(args.max_snap_distance ?? 5)
if (snapToLayers.length > 0) {
snapSettings = {
snapToLayers,
snapToLayersMaxDist
}
}
}
const o =
{
state, guiState, image: img,
feature, newTags, message, minZoom: 18,
originalTags: tagSource,
targetLayer,
snapSettings,
conflationSettings: undefined,
mergeConfigs: undefined
}
return ImportButton.createConfirmPanel(o, isImported, importClicked),
}
}
export default class ImportButton {
public static createConfirmPanel(o: ImportButtonState,
isImported: UIEventSource<boolean>,
importClicked: UIEventSource<boolean>) {
@ -287,6 +330,10 @@ export default class ImportButton extends Toggle {
}
if (geometry.type === "Polygon" && geometry.coordinates.length > 1) {
return new Lazy(() => ImportButton.createConfirmForMultiPolygon(o, isImported, importClicked))
}
if (geometry.type === "Polygon" || geometry.type == "LineString") {
return new Lazy(() => ImportButton.createConfirmForWay(o, isImported, importClicked))
}
@ -296,6 +343,32 @@ export default class ImportButton extends Toggle {
}
public static createConfirmForMultiPolygon(o: ImportButtonState,
isImported: UIEventSource<boolean>,
importClicked: UIEventSource<boolean>): BaseUIElement {
if (o.conflationSettings !== undefined) {
return new FixedUiElement("Conflating multipolygons is not supported").SetClass("alert")
}
// For every single linear ring, we create a new way
const createRings: (OsmChangeAction & { getPreview(): Promise<FeatureSource> })[] = []
for (const coordinateRing of o.feature.geometry.coordinates) {
createRings.push(new CreateWayWithPointReuseAction(
// The individual way doesn't receive any tags
[],
coordinateRing,
// @ts-ignore
o.state,
o.mergeConfigs
))
}
return new FixedUiElement("Multipolygon! Here we come").SetClass("alert")
}
public static createConfirmForWay(o: ImportButtonState,
isImported: UIEventSource<boolean>,
importClicked: UIEventSource<boolean>): BaseUIElement {
@ -353,17 +426,13 @@ export default class ImportButton extends Toggle {
coordinates,
// @ts-ignore
o.state,
[{
withinRangeOfM: 1,
ifMatches: new Tag("_is_part_of_building", "true"),
mode: "move_osm_point"
}]
o.mergeConfigs
)
confirm = async () => {
changes.applyAction(action)
return undefined
return action.mainObjectId
}
}
@ -381,7 +450,7 @@ export default class ImportButton extends Toggle {
const tagsExplanation = new VariableUiElement(o.newTags.map(tagsToApply => {
const tagsStr = tagsToApply.map(t => t.asHumanString(false, true)).join("&");
return Translations.t.general.add.importTags.Subs({tags: tagsStr});
return Translations.t.general.add.import.importTags.Subs({tags: tagsStr});
}
)).SetClass("subtle")
@ -413,20 +482,20 @@ export default class ImportButton extends Toggle {
importClicked: UIEventSource<boolean>): BaseUIElement {
async function confirm(tags: any[], location: { lat: number, lon: number }, snapOntoWayId: string) {
if (isImported.data) {
return
}
o.originalTags.data["_imported"] = "yes"
o.originalTags.ping() // will set isImported as per its definition
let snapOnto : OsmObject = undefined
if(snapOntoWayId !== undefined){
snapOnto = await OsmObject.DownloadObjectAsync(snapOntoWayId)
let snapOnto: OsmObject = undefined
if (snapOntoWayId !== undefined) {
snapOnto = await OsmObject.DownloadObjectAsync(snapOntoWayId)
}
const newElementAction = new CreateNewNodeAction(tags, location.lat, location.lon, {
theme: o.state.layoutToUse.id,
changeType: "import",
snapOnto: <OsmWay> snapOnto
snapOnto: <OsmWay>snapOnto
})
await o.state.changes.applyAction(newElementAction)
@ -461,8 +530,4 @@ export default class ImportButton extends Toggle {
}
private static canBeImported(feature: any) {
const type = feature.geometry.type
return type === "Point" || type === "LineString" || (type === "Polygon" && feature.geometry.coordinates.length === 1)
}
}

View file

@ -38,10 +38,11 @@ import Toggle from "./Input/Toggle";
import {DefaultGuiState} from "./DefaultGuiState";
import {GeoOperations} from "../Logic/GeoOperations";
import Hash from "../Logic/Web/Hash";
import FeaturePipelineState from "../Logic/State/FeaturePipelineState";
export interface SpecialVisualization {
funcName: string,
constr: ((state: State, tagSource: UIEventSource<any>, argument: string[], guistate: DefaultGuiState,) => BaseUIElement),
constr: ((state: FeaturePipelineState, tagSource: UIEventSource<any>, argument: string[], guistate: DefaultGuiState,) => BaseUIElement),
docs: string,
example?: string,
args: { name: string, defaultValue?: string, doc: string }[],
@ -641,6 +642,7 @@ export default class SpecialVisualizations {
}
]
static generateTagsToApply(spec: string, tagSource: UIEventSource<any>): UIEventSource<Tag[]> {
const tgsSpec = spec.split(";").map(spec => {

View file

@ -21,6 +21,53 @@ Remark that the syntax is slightly different then expected; it uses '$' to note
Note that these values can be prepare with javascript in the theme by using a [calculatedTag](calculatedTags.md#calculating-tags-with-javascript)
`
public static readonly special_visualizations_importRequirementDocs = `#### 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
2. The community has been informed of the import
3. All other requirements of the [import guidelines](https://wiki.openstreetmap.org/wiki/Import/Guidelines) have been followed
There are also some technicalities in your theme to keep in mind:
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 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 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 https://master.apis.dev.openstreetmap.org`
/**
* Parses the arguments for special visualisations
*/
public static ParseVisArgs(specs: { name: string, defaultValue?: string }[], args: string[]): any {
const parsed = {};
if(args.length> specs.length){
throw "To much arguments for special visualization: got "+args.join(",")+" but expected only "+args.length+" arguments"
}
for (let i = 0; i < specs.length; i++){
const spec = specs[i];
let arg = args[i]?.trim();
if(arg === undefined || arg === ""){
arg = spec.defaultValue
}
parsed[spec.name] = arg
}
return parsed;
}
private static knownKeys = ["addExtraTags", "and", "calculatedTags", "changesetmessage", "clustering", "color", "condition", "customCss", "dashArray", "defaultBackgroundId", "description", "descriptionTail", "doNotDownload", "enableAddNewPoints", "enableBackgroundLayerSelection", "enableGeolocation", "enableLayers", "enableMoreQuests", "enableSearch", "enableShareScreen", "enableUserBadge", "freeform", "hideFromOverview", "hideInAnswer", "icon", "iconOverlays", "iconSize", "id", "if", "ifnot", "isShown", "key", "language", "layers", "lockLocation", "maintainer", "mappings", "maxzoom", "maxZoom", "minNeededElements", "minzoom", "multiAnswer", "name", "or", "osmTags", "passAllFeatures", "presets", "question", "render", "roaming", "roamingRenderings", "rotation", "shortDescription", "socialImage", "source", "startLat", "startLon", "startZoom", "tagRenderings", "tags", "then", "title", "titleIcons", "type", "version", "wayHandling", "widenFactor", "width"]
private static extraKeys = ["nl", "en", "fr", "de", "pt", "es", "name", "phone", "email", "amenity", "leisure", "highway", "building", "yes", "no", "true", "false"]
private static injectedDownloads = {}

View file

@ -155,14 +155,14 @@
{
"if": "door=sliding",
"then": {
"en": "A sliding door where the door slides sidewards, typically parallel with a wall",
"nl": "Een schuifdeur or roldeur die bij het openen en sluiten zijwaarts beweegt"
"en": "A door which rolls from overhead, typically seen for garages",
"nl": "Een poort die langs boven dichtrolt, typisch voor garages"
}
},
{
"if": "door=overhead",
"then": {
"en": "A door which rolls from overhead, typically seen for garages",
"en": "This is an entrance without a physical door",
"nl": "Een poort die langs boven dichtrolt, typisch voor garages"
}
},
@ -273,7 +273,10 @@
"title": "entrance",
"preciseInput": {
"preferredBackground": "photo",
"snapToLayer": ["walls_and_buildings","pedestrian_path"]
"snapToLayer": [
"walls_and_buildings",
"pedestrian_path"
]
},
"tags": [
"entrance=yes"

View file

@ -31,5 +31,4 @@
"dashArray": "12 6"
}
]
}
}

View file

@ -421,7 +421,7 @@
},
{
"id": "import-button",
"render": "{import_button(OSM-buildings, addr:street=$STRAATNM; addr:housenumber=$_HNRLABEL,Voeg dit adres als een nieuw adrespunt toe,,,OSM-buildings,5)}",
"render": "{import_button(address, addr:street=$STRAATNM; addr:housenumber=$_HNRLABEL,Voeg dit adres als een nieuw adrespunt toe,,OSM-buildings,5)}",
"condition": {
"and": [
"_embedding_id!=",
@ -481,11 +481,11 @@
"addr:housenumber~*"
]
},
"then": "{import_button(OSM-buildings,building=$_target_building_type; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref; addr:street=$addr:street; addr:housenumber=$addr:housenumber, Replace the geometry in OpenStreetMap and add the address,,,_osm_obj:id)}"
"then": "{conflate_button(OSM-buildings,building=$_target_building_type; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref; addr:street=$addr:street; addr:housenumber=$addr:housenumber, Replace the geometry in OpenStreetMap and add the address,,_osm_obj:id)}"
},
{
"if": "_overlaps_with!=",
"then": "{import_button(OSM-buildings,building=$_target_building_type; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref, Replace the geometry in OpenStreetMap,,,_osm_obj:id)}"
"then": "{conflate_button(OSM-buildings,building=$_target_building_type; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref, Replace the geometry in OpenStreetMap,,_osm_obj:id)}"
}
]
},

View file

@ -78,17 +78,22 @@
],
"isShown": {
"render": "yes",
"mappings": [{
"if" :"_country!=be",
"then": "no"
}]
"mappings": [
{
"if": "_country!=be",
"then": "no"
}
]
}
},
{
"id": "wrong_postal_code",
"source": {
"osmTags": {
"and": ["boundary~*","addr:postcode~*"]
"and": [
"boundary~*",
"addr:postcode~*"
]
}
},
"title": "Boundary relation with addr:postcode={addr:postcode}",
@ -114,16 +119,16 @@
"_postal_code_properties=(() => { const f = feat.overlapWith('postal_code_boundary'); if(f.length===0){return {};}; const p = f[0]?.feat?.properties; return {id:p.id, postal_code: p.postal_code, _closest_town_hall: p._closest_town_hall}; })()",
"_postal_code=feat.get('_postal_code_properties')?.postal_code",
"_postal_code_center_distance=feat.distanceTo(feat.get('_postal_code_properties').id)"
],
"description": {},
"tagRenderings": [
],
"description": {},
"tagRenderings": [],
"presets": [],
"source": {
"osmTags": "amenity=townhall"
},
"mapRendering": [
{ "icon": "./assets/themes/postal_codes/townhall.svg",
{
"icon": "./assets/themes/postal_codes/townhall.svg",
"iconSize": {
"render": "40,40,center"
},
@ -135,10 +140,12 @@
],
"isShown": {
"render": "yes",
"mappings": [{
"if" :"_country!=be",
"then": "no"
}]
"mappings": [
{
"if": "_country!=be",
"then": "no"
}
]
}
}
]

View file

@ -108,10 +108,15 @@
"confirmButton": "Add a {category} here.<br/><div class='alert'>Your addition is visible for everyone</div>",
"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",
"importTags": "The element will receive {tags}",
"zoomInMore": "Zoom in more to import this feature",
"wrongType": "This element is not a point or a way and can not be imported"
"import": {
"officialThemesOnly": "The import button is disabled for unofficial themes to prevent accidents",
"howToTest": "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.",
"hasBeenImported": "This object has been imported",
"importTags": "The element will receive {tags}",
"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",

View file

@ -2754,6 +2754,9 @@
},
"4": {
"then": "This is an entrance without a physical door"
},
"5": {
"then": "This is an entrance without a physical door"
}
},
"question": "What is the type of this door?<br/><span class='subtle'>Wether or not the door is automated is asked in the next question</span>"
@ -3346,6 +3349,10 @@
"render": "Car parking"
}
},
"pedestrian_path": {
"description": "Pedestrian footpaths, especially used for indoor navigation and snapping entrances to this layer",
"name": "Pedestrain paths"
},
"picnic_table": {
"description": "The layer showing picnic tables",
"name": "Picnic tables",

View file

@ -2729,6 +2729,9 @@
},
"3": {
"then": "Een poort die langs boven dichtrolt, typisch voor garages"
},
"4": {
"then": "Een poort die langs boven dichtrolt, typisch voor garages"
}
}
}

View file

@ -1045,6 +1045,12 @@
"title": {
"render": "Postal code {postal_code}"
}
},
"2": {
"name": "town halls",
"title": {
"render": "Town hall {name}"
}
}
},
"shortDescription": "Postal codes",