forked from MapComplete/MapComplete
		
	Refactoring of import button, various improvements
This commit is contained in:
		
							parent
							
								
									cabbdf96db
								
							
						
					
					
						commit
						a095af4f18
					
				
					 17 changed files with 527 additions and 328 deletions
				
			
		| 
						 | 
				
			
			@ -42,7 +42,6 @@ export class NewGeometryFromChangesFeatureSource implements FeatureSource {
 | 
			
		|||
                        feature: feature,
 | 
			
		||||
                        freshness: now
 | 
			
		||||
                    })
 | 
			
		||||
                    console.warn("Added a new feature: ", JSON.stringify(feature))
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                for (const change of changes) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										102
									
								
								Logic/Osm/Actions/CreateMultiPolygonWithPointReuseAction.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								Logic/Osm/Actions/CreateMultiPolygonWithPointReuseAction.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,102 @@
 | 
			
		|||
import {OsmCreateAction} from "./OsmChangeAction";
 | 
			
		||||
import {Tag} from "../../Tags/Tag";
 | 
			
		||||
import {Changes} from "../Changes";
 | 
			
		||||
import {ChangeDescription} from "./ChangeDescription";
 | 
			
		||||
import FeaturePipelineState from "../../State/FeaturePipelineState";
 | 
			
		||||
import FeatureSource from "../../FeatureSource/FeatureSource";
 | 
			
		||||
import CreateNewWayAction from "./CreateNewWayAction";
 | 
			
		||||
import CreateWayWithPointReuseAction, {MergePointConfig} from "./CreateWayWithPointReuseAction";
 | 
			
		||||
import {And} from "../../Tags/And";
 | 
			
		||||
import {TagUtils} from "../../Tags/TagUtils";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * More or less the same as 'CreateNewWay', except that it'll try to reuse already existing points
 | 
			
		||||
 */
 | 
			
		||||
export default class CreateMultiPolygonWithPointReuseAction extends OsmCreateAction {
 | 
			
		||||
    private readonly _tags: Tag[];
 | 
			
		||||
    public newElementId: string = undefined;
 | 
			
		||||
    public newElementIdNumber: number  = undefined;
 | 
			
		||||
    private readonly createOuterWay: CreateWayWithPointReuseAction
 | 
			
		||||
    private readonly createInnerWays : CreateNewWayAction[]
 | 
			
		||||
private readonly geojsonPreview: any;
 | 
			
		||||
    private readonly theme: string;
 | 
			
		||||
    private readonly changeType: "import" | "create" | string;
 | 
			
		||||
    constructor(tags: Tag[],
 | 
			
		||||
                outerRingCoordinates: [number, number][],
 | 
			
		||||
                innerRingsCoordinates:  [number, number][][],
 | 
			
		||||
                state: FeaturePipelineState,
 | 
			
		||||
                config: MergePointConfig[],
 | 
			
		||||
                changeType: "import" | "create" | string
 | 
			
		||||
    ) {
 | 
			
		||||
        super(null,true);
 | 
			
		||||
        this._tags = [...tags, new Tag("type","multipolygon")];
 | 
			
		||||
        this.changeType = changeType;
 | 
			
		||||
        this.theme = state.layoutToUse.id
 | 
			
		||||
        this. createOuterWay = new CreateWayWithPointReuseAction([], outerRingCoordinates, state, config)
 | 
			
		||||
        this. createInnerWays = innerRingsCoordinates.map(ringCoordinates => 
 | 
			
		||||
            new CreateNewWayAction([], 
 | 
			
		||||
            ringCoordinates.map(([lon, lat] )=> ({lat, lon})), 
 | 
			
		||||
            {theme: state.layoutToUse.id}))
 | 
			
		||||
        
 | 
			
		||||
        this.geojsonPreview =  {
 | 
			
		||||
            type: "Feature",
 | 
			
		||||
            properties: TagUtils.changeAsProperties(new And(this._tags).asChange({})),
 | 
			
		||||
            geometry:{
 | 
			
		||||
                type: "Polygon",
 | 
			
		||||
                coordinates: [
 | 
			
		||||
                    outerRingCoordinates,
 | 
			
		||||
                    ...innerRingsCoordinates
 | 
			
		||||
                ]
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async getPreview(): Promise<FeatureSource> {
 | 
			
		||||
        const outerPreview = await this.createOuterWay.getPreview()
 | 
			
		||||
        outerPreview.features.data.push({
 | 
			
		||||
            freshness: new Date(),
 | 
			
		||||
            feature: this.geojsonPreview
 | 
			
		||||
        })
 | 
			
		||||
       return outerPreview
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected async CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]> {
 | 
			
		||||
        console.log("Running CMPWPRA")
 | 
			
		||||
        const descriptions: ChangeDescription[] = []
 | 
			
		||||
        descriptions.push(...await this.createOuterWay.CreateChangeDescriptions(changes));
 | 
			
		||||
        for (const innerWay of this.createInnerWays) {
 | 
			
		||||
            descriptions.push(...await innerWay.CreateChangeDescriptions(changes))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        this.newElementIdNumber = changes.getNewID();
 | 
			
		||||
        this.newElementId = "relation/"+this.newElementIdNumber
 | 
			
		||||
        descriptions.push({
 | 
			
		||||
            type:"relation",
 | 
			
		||||
            id: this.newElementIdNumber,
 | 
			
		||||
            tags: new And(this._tags).asChange({}),
 | 
			
		||||
            meta: {
 | 
			
		||||
                theme: this.theme,
 | 
			
		||||
                changeType:this.changeType
 | 
			
		||||
            },
 | 
			
		||||
            changes: {
 | 
			
		||||
                members: [
 | 
			
		||||
                    {
 | 
			
		||||
                        type: "way",
 | 
			
		||||
                        ref: this.createOuterWay.newElementIdNumber,
 | 
			
		||||
                        role: "outer"
 | 
			
		||||
                    },
 | 
			
		||||
                    // @ts-ignore
 | 
			
		||||
                    ...this.createInnerWays.map(a => ({type: "way", ref: a.newElementIdNumber, role: "inner"}))
 | 
			
		||||
                ]
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        
 | 
			
		||||
        
 | 
			
		||||
        return descriptions
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,12 +1,12 @@
 | 
			
		|||
import {Tag} from "../../Tags/Tag";
 | 
			
		||||
import OsmChangeAction from "./OsmChangeAction";
 | 
			
		||||
import OsmChangeAction, {OsmCreateAction} from "./OsmChangeAction";
 | 
			
		||||
import {Changes} from "../Changes";
 | 
			
		||||
import {ChangeDescription} from "./ChangeDescription";
 | 
			
		||||
import {And} from "../../Tags/And";
 | 
			
		||||
import {OsmWay} from "../OsmObject";
 | 
			
		||||
import {GeoOperations} from "../../GeoOperations";
 | 
			
		||||
 | 
			
		||||
export default class CreateNewNodeAction extends OsmChangeAction {
 | 
			
		||||
export default class CreateNewNodeAction extends OsmCreateAction {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Maps previously created points onto their assigned ID, to reuse the point if uplaoded
 | 
			
		||||
| 
						 | 
				
			
			@ -121,7 +121,6 @@ export default class CreateNewNodeAction extends OsmChangeAction {
 | 
			
		|||
            reusedPointId = this._snapOnto.nodes[index + 1]
 | 
			
		||||
        }
 | 
			
		||||
        if (reusedPointId !== undefined) {
 | 
			
		||||
            console.log("Reusing an existing point:", reusedPointId)
 | 
			
		||||
            this.setElementId(reusedPointId)
 | 
			
		||||
            return [{
 | 
			
		||||
                tags: new And(this._basicTags).asChange(properties),
 | 
			
		||||
| 
						 | 
				
			
			@ -133,7 +132,6 @@ export default class CreateNewNodeAction extends OsmChangeAction {
 | 
			
		|||
 | 
			
		||||
        const locations = [...this._snapOnto.coordinates]
 | 
			
		||||
        locations.forEach(coor => coor.reverse())
 | 
			
		||||
        console.log("Locations are: ", locations)
 | 
			
		||||
        const ids = [...this._snapOnto.nodes]
 | 
			
		||||
 | 
			
		||||
        locations.splice(index + 1, 0, [this._lon, this._lat])
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,12 +1,13 @@
 | 
			
		|||
import {ChangeDescription} from "./ChangeDescription";
 | 
			
		||||
import OsmChangeAction from "./OsmChangeAction";
 | 
			
		||||
import {OsmCreateAction} 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 {
 | 
			
		||||
export default class CreateNewWayAction extends OsmCreateAction {
 | 
			
		||||
    public newElementId: string = undefined
 | 
			
		||||
    public newElementIdNumber: number = undefined;
 | 
			
		||||
    private readonly coordinates: ({ nodeId?: number, lat: number, lon: number })[];
 | 
			
		||||
    private readonly tags: Tag[];
 | 
			
		||||
    private readonly _options: {
 | 
			
		||||
| 
						 | 
				
			
			@ -55,7 +56,7 @@ export default class CreateNewWayAction extends OsmChangeAction {
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
        const id = changes.getNewID()
 | 
			
		||||
 | 
			
		||||
        this.newElementIdNumber  = id
 | 
			
		||||
        const newWay = <ChangeDescription>{
 | 
			
		||||
            id,
 | 
			
		||||
            type: "way",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
import OsmChangeAction from "./OsmChangeAction";
 | 
			
		||||
import OsmChangeAction, {OsmCreateAction} from "./OsmChangeAction";
 | 
			
		||||
import {Tag} from "../../Tags/Tag";
 | 
			
		||||
import {Changes} from "../Changes";
 | 
			
		||||
import {ChangeDescription} from "./ChangeDescription";
 | 
			
		||||
| 
						 | 
				
			
			@ -31,7 +31,7 @@ interface CoordinateInfo {
 | 
			
		|||
/**
 | 
			
		||||
 * More or less the same as 'CreateNewWay', except that it'll try to reuse already existing points
 | 
			
		||||
 */
 | 
			
		||||
export default class CreateWayWithPointReuseAction extends OsmChangeAction {
 | 
			
		||||
export default class CreateWayWithPointReuseAction extends OsmCreateAction {
 | 
			
		||||
    private readonly _tags: Tag[];
 | 
			
		||||
    /**
 | 
			
		||||
     * lngLat-coordinates
 | 
			
		||||
| 
						 | 
				
			
			@ -40,6 +40,9 @@ export default class CreateWayWithPointReuseAction extends OsmChangeAction {
 | 
			
		|||
    private _coordinateInfo: CoordinateInfo[];
 | 
			
		||||
    private _state: FeaturePipelineState;
 | 
			
		||||
    private _config: MergePointConfig[];
 | 
			
		||||
    
 | 
			
		||||
    public newElementId: string = undefined;
 | 
			
		||||
    public newElementIdNumber: number = undefined
 | 
			
		||||
 | 
			
		||||
    constructor(tags: Tag[],
 | 
			
		||||
                coordinates: [number, number][],
 | 
			
		||||
| 
						 | 
				
			
			@ -87,7 +90,8 @@ export default class CreateWayWithPointReuseAction extends OsmChangeAction {
 | 
			
		|||
                    properties: {
 | 
			
		||||
                        "move": "yes",
 | 
			
		||||
                        "osm-id": reusedPoint.node.properties.id,
 | 
			
		||||
                        "id": "new-geometry-move-existing" + i
 | 
			
		||||
                        "id": "new-geometry-move-existing" + i,
 | 
			
		||||
                        "distance":GeoOperations.distanceBetween(coordinateInfo.lngLat, reusedPoint.node.geometry.coordinates)
 | 
			
		||||
                    },
 | 
			
		||||
                    geometry: {
 | 
			
		||||
                        type: "LineString",
 | 
			
		||||
| 
						 | 
				
			
			@ -97,8 +101,23 @@ export default class CreateWayWithPointReuseAction extends OsmChangeAction {
 | 
			
		|||
                features.push(moveDescription)
 | 
			
		||||
 | 
			
		||||
            } else {
 | 
			
		||||
                // The geometry is moved
 | 
			
		||||
                // The geometry is moved, the point is reused
 | 
			
		||||
                geometryMoved = true
 | 
			
		||||
 | 
			
		||||
                const reuseDescription = {
 | 
			
		||||
                    type: "Feature",
 | 
			
		||||
                    properties: {
 | 
			
		||||
                        "move": "no",
 | 
			
		||||
                        "osm-id": reusedPoint.node.properties.id,
 | 
			
		||||
                        "id": "new-geometry-reuse-existing" + i,
 | 
			
		||||
                        "distance":GeoOperations.distanceBetween(coordinateInfo.lngLat, reusedPoint.node.geometry.coordinates)
 | 
			
		||||
                    },
 | 
			
		||||
                    geometry: {
 | 
			
		||||
                        type: "LineString",
 | 
			
		||||
                        coordinates: [coordinateInfo.lngLat, reusedPoint.node.geometry.coordinates]
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                features.push(reuseDescription)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -138,11 +157,10 @@ export default class CreateWayWithPointReuseAction extends OsmChangeAction {
 | 
			
		|||
            features.push(newGeometry)
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        console.log("Preview:", features)
 | 
			
		||||
        return new StaticFeatureSource(features, false)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected async CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]> {
 | 
			
		||||
    public async CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]> {
 | 
			
		||||
        const theme = this._state.layoutToUse.id
 | 
			
		||||
        const allChanges: ChangeDescription[] = []
 | 
			
		||||
        const nodeIdsToUse: { lat: number, lon: number, nodeId?: number }[] = []
 | 
			
		||||
| 
						 | 
				
			
			@ -196,6 +214,8 @@ export default class CreateWayWithPointReuseAction extends OsmChangeAction {
 | 
			
		|||
        })
 | 
			
		||||
        
 | 
			
		||||
        allChanges.push(...(await newWay.CreateChangeDescriptions(changes)))
 | 
			
		||||
        this.newElementId = newWay.newElementId
 | 
			
		||||
        this.newElementIdNumber = newWay.newElementIdNumber
 | 
			
		||||
        return allChanges
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,7 +11,7 @@ export default abstract class OsmChangeAction {
 | 
			
		|||
    public readonly trackStatistics: boolean;
 | 
			
		||||
    /**
 | 
			
		||||
     * The ID of the object that is the center of this change.
 | 
			
		||||
     * Null if the action creates a new object
 | 
			
		||||
     * Null if the action creates a new object (at initialization)
 | 
			
		||||
     * Undefined if such an id does not make sense
 | 
			
		||||
     */
 | 
			
		||||
    public readonly mainObjectId: string;
 | 
			
		||||
| 
						 | 
				
			
			@ -30,4 +30,11 @@ export default abstract class OsmChangeAction {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    protected abstract CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]>
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export abstract class OsmCreateAction extends OsmChangeAction{
 | 
			
		||||
 | 
			
		||||
    public newElementId : string
 | 
			
		||||
    public newElementIdNumber: number
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -46,6 +46,9 @@ export class Tag extends TagsFilter {
 | 
			
		|||
        if (shorten) {
 | 
			
		||||
            v = Utils.EllipsesAfter(v, 25);
 | 
			
		||||
        }
 | 
			
		||||
        if(v === "" || v === undefined){
 | 
			
		||||
            return "<span class='line-through'>"+this.key+"</span>"
 | 
			
		||||
        }
 | 
			
		||||
        if (linkToWiki) {
 | 
			
		||||
            return `<a href='https://wiki.openstreetmap.org/wiki/Key:${this.key}' target='_blank'>${this.key}</a>` +
 | 
			
		||||
                `=` +
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,7 +8,7 @@ export abstract class TagsFilter {
 | 
			
		|||
 | 
			
		||||
    abstract matchesProperties(properties: any): boolean;
 | 
			
		||||
 | 
			
		||||
    abstract asHumanString(linkToWiki: boolean, shorten: boolean, properties: any);
 | 
			
		||||
    abstract asHumanString(linkToWiki: boolean, shorten: boolean, properties: any) : string;
 | 
			
		||||
 | 
			
		||||
    abstract usedKeys(): string[];
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,7 @@ import {Utils} from "../Utils";
 | 
			
		|||
 | 
			
		||||
export default class Constants {
 | 
			
		||||
 | 
			
		||||
    public static vNumber = "0.13.0-alpha-4";
 | 
			
		||||
    public static vNumber = "0.13.0-alpha-5";
 | 
			
		||||
    public static ImgurApiKey = '7070e7167f0a25a'
 | 
			
		||||
    public static readonly mapillary_client_token_v4 = "MLY|4441509239301885|b40ad2d3ea105435bd40c7e76993ae85"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -136,7 +136,7 @@ export interface LayerConfigJson {
 | 
			
		|||
    titleIcons?: (string | TagRenderingConfigJson)[];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    mapRendering: (PointRenderingConfigJson | LineRenderingConfigJson)[]
 | 
			
		||||
    mapRendering: null | (PointRenderingConfigJson | LineRenderingConfigJson)[]
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * If set, this layer will pass all the features it receives onto the next layer.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,17 +7,11 @@ import Translations from "../i18n/Translations";
 | 
			
		|||
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 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";
 | 
			
		||||
import Lazy from "../Base/Lazy";
 | 
			
		||||
import ConfirmLocationOfPoint from "../NewPoint/ConfirmLocationOfPoint";
 | 
			
		||||
import Img from "../Base/Img";
 | 
			
		||||
import {Translation} from "../i18n/Translation";
 | 
			
		||||
import FilteredLayer from "../../Models/FilteredLayer";
 | 
			
		||||
import SpecialVisualizations from "../SpecialVisualizations";
 | 
			
		||||
import {FixedUiElement} from "../Base/FixedUiElement";
 | 
			
		||||
| 
						 | 
				
			
			@ -28,68 +22,29 @@ import ShowDataLayer from "../ShowDataLayer/ShowDataLayer";
 | 
			
		|||
import AllKnownLayers from "../../Customizations/AllKnownLayers";
 | 
			
		||||
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource";
 | 
			
		||||
import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer";
 | 
			
		||||
import BaseLayer from "../../Models/BaseLayer";
 | 
			
		||||
import ReplaceGeometryAction from "../../Logic/Osm/Actions/ReplaceGeometryAction";
 | 
			
		||||
import CreateWayWithPointReuseAction, {MergePointConfig} from "../../Logic/Osm/Actions/CreateWayWithPointReuseAction";
 | 
			
		||||
import OsmChangeAction from "../../Logic/Osm/Actions/OsmChangeAction";
 | 
			
		||||
import OsmChangeAction, {OsmCreateAction} 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 {
 | 
			
		||||
    description?: Translation;
 | 
			
		||||
    image: () => BaseUIElement,
 | 
			
		||||
    message: string | BaseUIElement,
 | 
			
		||||
    originalTags: UIEventSource<any>,
 | 
			
		||||
    newTags: UIEventSource<Tag[]>,
 | 
			
		||||
    targetLayer: FilteredLayer,
 | 
			
		||||
    feature: any,
 | 
			
		||||
    minZoom: number,
 | 
			
		||||
    state: {
 | 
			
		||||
        backgroundLayer: UIEventSource<BaseLayer>;
 | 
			
		||||
        filteredLayers: UIEventSource<FilteredLayer[]>;
 | 
			
		||||
        featureSwitchUserbadge: UIEventSource<boolean>;
 | 
			
		||||
        featurePipeline: FeaturePipeline;
 | 
			
		||||
        allElements: ElementStorage;
 | 
			
		||||
        selectedElement: UIEventSource<any>;
 | 
			
		||||
        layoutToUse: LayoutConfig,
 | 
			
		||||
        osmConnection: OsmConnection,
 | 
			
		||||
        changes: Changes,
 | 
			
		||||
        locationControl: UIEventSource<{ zoom: number }>
 | 
			
		||||
    },
 | 
			
		||||
    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[]
 | 
			
		||||
}
 | 
			
		||||
import {TagUtils} from "../../Logic/Tags/TagUtils";
 | 
			
		||||
import {And} from "../../Logic/Tags/And";
 | 
			
		||||
import ReplaceGeometryAction from "../../Logic/Osm/Actions/ReplaceGeometryAction";
 | 
			
		||||
import CreateMultiPolygonWithPointReuseAction from "../../Logic/Osm/Actions/CreateMultiPolygonWithPointReuseAction";
 | 
			
		||||
import {Tag} from "../../Logic/Tags/Tag";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
abstract class AbstractImportButton implements SpecialVisualizations {
 | 
			
		||||
    public readonly funcName: string
 | 
			
		||||
    public readonly docs: string
 | 
			
		||||
    public readonly args: { name: string, defaultValue?: string, doc: string }[]
 | 
			
		||||
    private readonly showRemovedTags: boolean;
 | 
			
		||||
 | 
			
		||||
    constructor(funcName: string, docsIntro: string, extraArgs: { name: string, doc: string, defaultValue?: string }[]) {
 | 
			
		||||
    constructor(funcName: string, docsIntro: string, extraArgs: { name: string, doc: string, defaultValue?: string }[], showRemovedTags = true) {
 | 
			
		||||
        this.funcName = funcName
 | 
			
		||||
        this.showRemovedTags = showRemovedTags;
 | 
			
		||||
 | 
			
		||||
        this.docs = `${docsIntro}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -102,9 +57,7 @@ The argument \`tags\` of the import button takes a \`;\`-seperated list of tags
 | 
			
		|||
 | 
			
		||||
${Utils.Special_visualizations_tagsToApplyHelpText}
 | 
			
		||||
${Utils.special_visualizations_importRequirementDocs}
 | 
			
		||||
  
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
        this.args = [
 | 
			
		||||
            {
 | 
			
		||||
                name: "targetLayer",
 | 
			
		||||
| 
						 | 
				
			
			@ -128,11 +81,16 @@ ${Utils.special_visualizations_importRequirementDocs}
 | 
			
		|||
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    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;
 | 
			
		||||
    abstract constructElement(state: FeaturePipelineState,
 | 
			
		||||
                              args: { max_snap_distance: string, snap_onto_layers: string, icon: string, text: string, tags: string, newTags: UIEventSource<any>, targetLayer: string },
 | 
			
		||||
                              tagSource: UIEventSource<any>,
 | 
			
		||||
                              guiState: DefaultGuiState,
 | 
			
		||||
                              feature: any,
 | 
			
		||||
                              onCancelClicked: () => void): BaseUIElement;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    constr(state, tagSource, argsRaw, guiState) {
 | 
			
		||||
 | 
			
		||||
        const self = this;
 | 
			
		||||
        /**
 | 
			
		||||
         * Some generic import button pre-validation is implemented here:
 | 
			
		||||
         * - Are we logged in?
 | 
			
		||||
| 
						 | 
				
			
			@ -144,7 +102,7 @@ ${Utils.special_visualizations_importRequirementDocs}
 | 
			
		|||
 | 
			
		||||
        const t = Translations.t.general.add.import;
 | 
			
		||||
        const t0 = Translations.t.general.add;
 | 
			
		||||
        const args = this.parseArgs(argsRaw)
 | 
			
		||||
        const args = this.parseArgs(argsRaw, tagSource)
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            // Some initial validation
 | 
			
		||||
| 
						 | 
				
			
			@ -171,26 +129,22 @@ ${Utils.special_visualizations_importRequirementDocs}
 | 
			
		|||
        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(
 | 
			
		||||
                newTags.map(tgs => {
 | 
			
		||||
                    const parts = []
 | 
			
		||||
                    for (const tag of tgs) {
 | 
			
		||||
                        parts.push(tag.key + "=" + tag.value)
 | 
			
		||||
                    }
 | 
			
		||||
                    const txt = parts.join(" & ")
 | 
			
		||||
                    return t0.presetInfo.Subs({tags: txt}).SetClass("subtle")
 | 
			
		||||
                })), undefined,
 | 
			
		||||
                    const filteredTags = tgs.filter(tg => self.showRemovedTags || (tg.value ?? "") !== "")
 | 
			
		||||
                    const asText = new And(filteredTags)
 | 
			
		||||
                        .asHumanString(true, true, {})
 | 
			
		||||
 | 
			
		||||
                    return t0.presetInfo.Subs({tags: asText}).SetClass("subtle");
 | 
			
		||||
                })),
 | 
			
		||||
            undefined,
 | 
			
		||||
            state.osmConnection.userDetails.map(ud => ud.csCount >= Constants.userJourney.tagsVisibleAt)
 | 
			
		||||
        )
 | 
			
		||||
        
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        const importClicked = new UIEventSource(false);
 | 
			
		||||
        inviteToImportButton.onClick(() => {
 | 
			
		||||
| 
						 | 
				
			
			@ -207,15 +161,17 @@ ${Utils.special_visualizations_importRequirementDocs}
 | 
			
		|||
 | 
			
		||||
        const isImported = tagSource.map(tags => tags._imported === "yes")
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
        
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        /**** THe actual panel showing the import guiding map ****/
 | 
			
		||||
        const importGuidingPanel = this.constructElement(state, args, tagSource, guiState, feature, () => importClicked.setData(false))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        const importFlow = new Toggle(
 | 
			
		||||
            new Toggle(
 | 
			
		||||
                    new Loading(t0.stillLoading),
 | 
			
		||||
                    new Combine([importGuidingPanel, appliedTags]).SetClass("flex flex-col"),
 | 
			
		||||
                    state.featurePipeline.runningQuery
 | 
			
		||||
            ) ,
 | 
			
		||||
                new Loading(t0.stillLoading),
 | 
			
		||||
                new Combine([importGuidingPanel, appliedTags]).SetClass("flex flex-col"),
 | 
			
		||||
                state.featurePipeline.runningQuery
 | 
			
		||||
            ),
 | 
			
		||||
            inviteToImportButton,
 | 
			
		||||
            importClicked
 | 
			
		||||
        );
 | 
			
		||||
| 
						 | 
				
			
			@ -239,12 +195,16 @@ ${Utils.special_visualizations_importRequirementDocs}
 | 
			
		|||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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)
 | 
			
		||||
    private parseArgs(argsRaw: string[], originalFeatureTags: UIEventSource<any>): { minzoom: string, max_snap_distance: string, snap_onto_layers: string, icon: string, text: string, tags: string, targetLayer: string, newTags: UIEventSource<Tag[]> } {
 | 
			
		||||
        const baseArgs = Utils.ParseVisArgs(this.args, argsRaw)
 | 
			
		||||
        if (originalFeatureTags !== undefined) {
 | 
			
		||||
            baseArgs["newTags"] = SpecialVisualizations.generateTagsToApply(baseArgs.tags, originalFeatureTags)
 | 
			
		||||
        }
 | 
			
		||||
        return baseArgs
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getLayerDependencies(argsRaw: string[]) {
 | 
			
		||||
        const args = this.parseArgs(argsRaw)
 | 
			
		||||
        const args = this.parseArgs(argsRaw, undefined)
 | 
			
		||||
 | 
			
		||||
        const dependsOnLayers: string[] = []
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -261,181 +221,31 @@ ${Utils.special_visualizations_importRequirementDocs}
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
    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>) {
 | 
			
		||||
        const geometry = o.feature.geometry
 | 
			
		||||
        if (geometry.type === "Point") {
 | 
			
		||||
            return new Lazy(() => ImportButton.createConfirmPanelForPoint(o, isImported, importClicked))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        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))
 | 
			
		||||
        }
 | 
			
		||||
        console.error("Invalid type to import", geometry.type)
 | 
			
		||||
        return new FixedUiElement("Invalid geometry type:" + geometry.type).SetClass("alert")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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 {
 | 
			
		||||
 | 
			
		||||
    protected createConfirmPanelForWay(
 | 
			
		||||
        state: FeaturePipelineState,
 | 
			
		||||
        args: { max_snap_distance: string, snap_onto_layers: string, icon: string, text: string, newTags: UIEventSource<Tag[]>, targetLayer: string },
 | 
			
		||||
        feature: any,
 | 
			
		||||
        originalFeatureTags: UIEventSource<any>,
 | 
			
		||||
        action: (OsmChangeAction & { getPreview(): Promise<FeatureSource>, newElementId?: string }),
 | 
			
		||||
        onCancel: () => void): BaseUIElement {
 | 
			
		||||
        const self = this;
 | 
			
		||||
        const confirmationMap = Minimap.createMiniMap({
 | 
			
		||||
            allowMoving: false,
 | 
			
		||||
            background: o.state.backgroundLayer
 | 
			
		||||
            background: state.backgroundLayer
 | 
			
		||||
        })
 | 
			
		||||
        confirmationMap.SetStyle("height: 20rem; overflow: hidden").SetClass("rounded-xl")
 | 
			
		||||
 | 
			
		||||
        const relevantFeatures = Utils.NoNull([o.feature, o.state.allElements?.ContainingFeatures?.get(o.conflationSettings?.conflateWayId)])
 | 
			
		||||
        // SHow all relevant data - including (eventually) the way of which the geometry will be replaced
 | 
			
		||||
        new ShowDataMultiLayer({
 | 
			
		||||
            leafletMap: confirmationMap.leafletMap,
 | 
			
		||||
            enablePopups: false,
 | 
			
		||||
            zoomToFeatures: true,
 | 
			
		||||
            features: new StaticFeatureSource(relevantFeatures, false),
 | 
			
		||||
            allElements: o.state.allElements,
 | 
			
		||||
            layers: o.state.filteredLayers
 | 
			
		||||
            features: new StaticFeatureSource([feature], false),
 | 
			
		||||
            allElements: state.allElements,
 | 
			
		||||
            layers: state.filteredLayers
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        let action: OsmChangeAction & { getPreview(): Promise<FeatureSource> }
 | 
			
		||||
 | 
			
		||||
        const changes = o.state.changes
 | 
			
		||||
        let confirm: () => Promise<string>
 | 
			
		||||
        if (o.conflationSettings !== undefined) {
 | 
			
		||||
            // Conflate the way
 | 
			
		||||
            action = new ReplaceGeometryAction(
 | 
			
		||||
                o.state,
 | 
			
		||||
                o.feature,
 | 
			
		||||
                o.conflationSettings.conflateWayId,
 | 
			
		||||
                {
 | 
			
		||||
                    theme: o.state.layoutToUse.id,
 | 
			
		||||
                    newTags: o.newTags.data
 | 
			
		||||
                }
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            confirm = async () => {
 | 
			
		||||
                changes.applyAction(action)
 | 
			
		||||
                return o.feature.properties.id
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        } else {
 | 
			
		||||
            // Upload the way to OSM
 | 
			
		||||
            const geom = o.feature.geometry
 | 
			
		||||
            let coordinates: [number, number][]
 | 
			
		||||
            if (geom.type === "LineString") {
 | 
			
		||||
                coordinates = geom.coordinates
 | 
			
		||||
            } else if (geom.type === "Polygon") {
 | 
			
		||||
                coordinates = geom.coordinates[0]
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            action = new CreateWayWithPointReuseAction(
 | 
			
		||||
                o.newTags.data,
 | 
			
		||||
                coordinates,
 | 
			
		||||
                // @ts-ignore
 | 
			
		||||
                o.state,
 | 
			
		||||
                o.mergeConfigs
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            confirm = async () => {
 | 
			
		||||
                changes.applyAction(action)
 | 
			
		||||
                return action.mainObjectId
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        action.getPreview().then(changePreview => {
 | 
			
		||||
            new ShowDataLayer({
 | 
			
		||||
| 
						 | 
				
			
			@ -443,89 +253,328 @@ export default class ImportButton {
 | 
			
		|||
                enablePopups: false,
 | 
			
		||||
                zoomToFeatures: false,
 | 
			
		||||
                features: changePreview,
 | 
			
		||||
                allElements: o.state.allElements,
 | 
			
		||||
                allElements: state.allElements,
 | 
			
		||||
                layerToShow: AllKnownLayers.sharedLayers.get("conflation")
 | 
			
		||||
            })
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        const tagsExplanation = new VariableUiElement(o.newTags.map(tagsToApply => {
 | 
			
		||||
                const tagsStr = tagsToApply.map(t => t.asHumanString(false, true)).join("&");
 | 
			
		||||
        const tagsExplanation = new VariableUiElement(args.newTags.map(tagsToApply => {
 | 
			
		||||
                const filteredTags = tagsToApply.filter(t => self.showRemovedTags || (t.value ?? "") !== "")
 | 
			
		||||
                const tagsStr = new And(filteredTags).asHumanString(false, true, {})
 | 
			
		||||
                return Translations.t.general.add.import.importTags.Subs({tags: tagsStr});
 | 
			
		||||
            }
 | 
			
		||||
        )).SetClass("subtle")
 | 
			
		||||
 | 
			
		||||
        const confirmButton = new SubtleButton(o.image(), new Combine([o.message, tagsExplanation]).SetClass("flex flex-col"))
 | 
			
		||||
        const confirmButton = new SubtleButton(new Img(args.icon), new Combine([args.text, tagsExplanation]).SetClass("flex flex-col"))
 | 
			
		||||
        confirmButton.onClick(async () => {
 | 
			
		||||
            {
 | 
			
		||||
                if (isImported.data) {
 | 
			
		||||
                    return
 | 
			
		||||
                }
 | 
			
		||||
                o.originalTags.data["_imported"] = "yes"
 | 
			
		||||
                o.originalTags.ping() // will set isImported as per its definition
 | 
			
		||||
 | 
			
		||||
                const idToSelect = await confirm()
 | 
			
		||||
 | 
			
		||||
                o.state.selectedElement.setData(o.state.allElements.ContainingFeatures.get(idToSelect))
 | 
			
		||||
                originalFeatureTags.data["_imported"] = "yes"
 | 
			
		||||
                originalFeatureTags.ping() // will set isImported as per its definition
 | 
			
		||||
                state.changes.applyAction(action)
 | 
			
		||||
                state.selectedElement.setData(state.allElements.ContainingFeatures.get(action.newElementId ?? action.mainObjectId))
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        const cancel = new SubtleButton(Svg.close_ui(), Translations.t.general.cancel).onClick(() => importClicked.setData(false))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        const cancel = new SubtleButton(Svg.close_ui(), Translations.t.general.cancel).onClick(onCancel)
 | 
			
		||||
        return new Combine([confirmationMap, confirmButton, cancel]).SetClass("flex flex-col")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    public static createConfirmPanelForPoint(
 | 
			
		||||
        o: ImportButtonState,
 | 
			
		||||
        isImported: UIEventSource<boolean>,
 | 
			
		||||
        importClicked: UIEventSource<boolean>): BaseUIElement {
 | 
			
		||||
export class ConflateButton extends AbstractImportButton {
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super("conflate_button", "This button will modify the geometry of an existing OSM way to match the specified geometry. This can conflate OSM-ways with LineStrings and Polygons (only simple polygons with one single ring). An attempt is made to move points with special values to a decent new location (e.g. entrances)",
 | 
			
		||||
            [{
 | 
			
		||||
                name: "way_to_conflate",
 | 
			
		||||
                doc: "The key, of which the corresponding value is the id of the OSM-way that must be conflated; typically a calculatedTag"
 | 
			
		||||
            }]
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected canBeImported(feature: any) {
 | 
			
		||||
        return feature.geometry.type === "LineString" || (feature.geometry.type === "Polygon" && feature.geometry.coordinates.length === 1)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    constructElement(state: FeaturePipelineState,
 | 
			
		||||
                     args: { max_snap_distance: string; snap_onto_layers: string; icon: string; text: string; tags: string; newTags: UIEventSource<Tag[]>; targetLayer: string },
 | 
			
		||||
                     tagSource: UIEventSource<any>, guiState: DefaultGuiState, feature: any, onCancelClicked: () => void): BaseUIElement {
 | 
			
		||||
 | 
			
		||||
        const nodesMustMatch = args.snap_onto_layers?.split(";")?.map((tag, i) => TagUtils.Tag(tag, "TagsSpec for import button " + i))
 | 
			
		||||
 | 
			
		||||
        const mergeConfigs = []
 | 
			
		||||
        if (nodesMustMatch !== undefined && nodesMustMatch.length > 0) {
 | 
			
		||||
            const mergeConfig: MergePointConfig = {
 | 
			
		||||
                mode: args["point_move_mode"] == "move_osm" ? "move_osm_point" : "reuse_osm_point",
 | 
			
		||||
                ifMatches: new And(nodesMustMatch),
 | 
			
		||||
                withinRangeOfM: Number(args.max_snap_distance)
 | 
			
		||||
            }
 | 
			
		||||
            mergeConfigs.push(mergeConfig)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        const key = args["way_to_conflate"]
 | 
			
		||||
        const wayToConflate = tagSource.data[key]
 | 
			
		||||
        const action = new ReplaceGeometryAction(
 | 
			
		||||
            state,
 | 
			
		||||
            feature,
 | 
			
		||||
            wayToConflate,
 | 
			
		||||
            {
 | 
			
		||||
                theme: state.layoutToUse.id,
 | 
			
		||||
                newTags: args.newTags.data
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        return this.createConfirmPanelForWay(
 | 
			
		||||
            state,
 | 
			
		||||
            args,
 | 
			
		||||
            feature,
 | 
			
		||||
            tagSource,
 | 
			
		||||
            action,
 | 
			
		||||
            onCancelClicked
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class ImportWayButton extends AbstractImportButton {
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super("import_way_button",
 | 
			
		||||
            "This button will copy the data from an external dataset into OpenStreetMap",
 | 
			
		||||
            [
 | 
			
		||||
                {
 | 
			
		||||
                    name: "snap_to_point_if",
 | 
			
		||||
                    doc: "Points with the given tags will be snapped to or moved",
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: "max_snap_distance",
 | 
			
		||||
                    doc: "If the imported object is a LineString or (Multi)Polygon, already existing OSM-points will be reused to construct the geometry of the newly imported way",
 | 
			
		||||
                    defaultValue: "5"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: "move_osm_point_if",
 | 
			
		||||
                    doc: "Moves the OSM-point to the newly imported point if these conditions are met",
 | 
			
		||||
                },{
 | 
			
		||||
                    name:"max_move_distance",
 | 
			
		||||
                doc: "If an OSM-point is moved, the maximum amount of meters it is moved. Capped on 20m",
 | 
			
		||||
                defaultValue: "1"
 | 
			
		||||
            },{
 | 
			
		||||
            name:"snap_onto_layers",
 | 
			
		||||
                doc:"If no existing nearby point exists, but a line of a specified layer is closeby, snap to this layer instead",
 | 
			
		||||
                
 | 
			
		||||
            },{
 | 
			
		||||
                name:"snap_to_layer_max_distance",
 | 
			
		||||
            doc:"Distance to distort the geometry to snap to this layer",
 | 
			
		||||
defaultValue: "0.1"
 | 
			
		||||
    }],
 | 
			
		||||
            false
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    canBeImported(feature: any) {
 | 
			
		||||
        const type = feature.geometry.type
 | 
			
		||||
        return type === "LineString" || type === "Polygon"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getLayerDependencies(argsRaw: string[]): string[] {
 | 
			
		||||
        const deps = super.getLayerDependencies(argsRaw);
 | 
			
		||||
        deps.push("type_node")
 | 
			
		||||
        return deps
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    constructElement(state, args,
 | 
			
		||||
                     originalFeatureTags,
 | 
			
		||||
                     guiState,
 | 
			
		||||
                     feature,
 | 
			
		||||
                     onCancel): BaseUIElement {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        const geometry = feature.geometry
 | 
			
		||||
 | 
			
		||||
        if (!(geometry.type == "LineString" || geometry.type === "Polygon")) {
 | 
			
		||||
            console.error("Invalid type to import", geometry.type)
 | 
			
		||||
            return new FixedUiElement("Invalid geometry type:" + geometry.type).SetClass("alert")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        // Upload the way to OSM
 | 
			
		||||
        const geom = feature.geometry
 | 
			
		||||
        let coordinates: [number, number][]
 | 
			
		||||
        if (geom.type === "LineString") {
 | 
			
		||||
            coordinates = geom.coordinates
 | 
			
		||||
        } else if (geom.type === "Polygon") {
 | 
			
		||||
            coordinates = geom.coordinates[0]
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const nodesMustMatch = args["snap_to_point_if"]?.split(";")?.map((tag, i) => TagUtils.Tag(tag, "TagsSpec for import button " + i))
 | 
			
		||||
 | 
			
		||||
        const mergeConfigs = []
 | 
			
		||||
        if (nodesMustMatch !== undefined && nodesMustMatch.length > 0) {
 | 
			
		||||
            const mergeConfig: MergePointConfig = {
 | 
			
		||||
                mode: "reuse_osm_point",
 | 
			
		||||
                ifMatches: new And(nodesMustMatch),
 | 
			
		||||
                withinRangeOfM: Number(args.max_snap_distance)
 | 
			
		||||
            }
 | 
			
		||||
            mergeConfigs.push(mergeConfig)
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        const moveOsmPointIfTags = args["move_osm_point_if"]?.split(";")?.map((tag, i) => TagUtils.Tag(tag, "TagsSpec for import button " + i))
 | 
			
		||||
 | 
			
		||||
        if (nodesMustMatch !== undefined && moveOsmPointIfTags.length > 0) {
 | 
			
		||||
           const moveDistance = Math.min(20, Number(args["max_move_distance"]))
 | 
			
		||||
            const mergeConfig: MergePointConfig = {
 | 
			
		||||
                mode: "move_osm_point" ,
 | 
			
		||||
                ifMatches: new And(moveOsmPointIfTags),
 | 
			
		||||
                withinRangeOfM: moveDistance
 | 
			
		||||
            }
 | 
			
		||||
            mergeConfigs.push(mergeConfig)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let action: OsmCreateAction & { getPreview(): Promise<FeatureSource> };
 | 
			
		||||
 | 
			
		||||
        const coors = feature.geometry.coordinates
 | 
			
		||||
        if (feature.geometry.type === "Polygon" && coors.length > 1) {
 | 
			
		||||
            const outer = coors[0]
 | 
			
		||||
            const inner = [...coors]
 | 
			
		||||
            inner.splice(0, 1)
 | 
			
		||||
            action = new CreateMultiPolygonWithPointReuseAction(
 | 
			
		||||
                args.newTags.data,
 | 
			
		||||
                outer,
 | 
			
		||||
                inner,
 | 
			
		||||
                state,
 | 
			
		||||
                mergeConfigs,
 | 
			
		||||
                "import"
 | 
			
		||||
            )
 | 
			
		||||
        } else {
 | 
			
		||||
 | 
			
		||||
            action = new CreateWayWithPointReuseAction(
 | 
			
		||||
                args.newTags.data,
 | 
			
		||||
                coordinates,
 | 
			
		||||
                state,
 | 
			
		||||
                mergeConfigs
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        return this.createConfirmPanelForWay(
 | 
			
		||||
            state,
 | 
			
		||||
            args,
 | 
			
		||||
            feature,
 | 
			
		||||
            originalFeatureTags,
 | 
			
		||||
            action,
 | 
			
		||||
            onCancel
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class ImportPointButton extends AbstractImportButton {
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super("import_button",
 | 
			
		||||
            "This button will copy the point 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: "The maximum distance that the imported point will be moved to snap onto a way in an already existing layer (in meters). This is previewed to the contributor, similar to the 'add new point'-action of MapComplete",
 | 
			
		||||
                    defaultValue: "5"
 | 
			
		||||
                }],
 | 
			
		||||
            false
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    canBeImported(feature: any) {
 | 
			
		||||
        return feature.geometry.type === "Point"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getLayerDependencies(argsRaw: string[]): string[] {
 | 
			
		||||
        const deps = super.getLayerDependencies(argsRaw);
 | 
			
		||||
        const layerSnap = argsRaw["snap_onto_layers"] ?? ""
 | 
			
		||||
        if (layerSnap === "") {
 | 
			
		||||
            return deps
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        deps.push(...layerSnap.split(";"))
 | 
			
		||||
        return deps
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static createConfirmPanelForPoint(
 | 
			
		||||
        args: { max_snap_distance: string, snap_onto_layers: string, icon: string, text: string, newTags: UIEventSource<any>, targetLayer: string },
 | 
			
		||||
        state: FeaturePipelineState,
 | 
			
		||||
        guiState: DefaultGuiState,
 | 
			
		||||
        originalFeatureTags: UIEventSource<any>,
 | 
			
		||||
        feature: any,
 | 
			
		||||
        onCancel: () => void): 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
 | 
			
		||||
            originalFeatureTags.data["_imported"] = "yes"
 | 
			
		||||
            originalFeatureTags.ping() // will set isImported as per its definition
 | 
			
		||||
            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,
 | 
			
		||||
                theme: state.layoutToUse.id,
 | 
			
		||||
                changeType: "import",
 | 
			
		||||
                snapOnto: <OsmWay>snapOnto
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            await o.state.changes.applyAction(newElementAction)
 | 
			
		||||
            o.state.selectedElement.setData(o.state.allElements.ContainingFeatures.get(
 | 
			
		||||
            await state.changes.applyAction(newElementAction)
 | 
			
		||||
            state.selectedElement.setData(state.allElements.ContainingFeatures.get(
 | 
			
		||||
                newElementAction.newElementId
 | 
			
		||||
            ))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function cancel() {
 | 
			
		||||
            importClicked.setData(false)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const presetInfo = <PresetInfo>{
 | 
			
		||||
            tags: o.newTags.data,
 | 
			
		||||
            icon: o.image,
 | 
			
		||||
            description: o.description,
 | 
			
		||||
            layerToAddTo: o.targetLayer,
 | 
			
		||||
            name: o.message,
 | 
			
		||||
            title: o.message,
 | 
			
		||||
            tags: args.newTags.data,
 | 
			
		||||
            icon: () => new Img(args.icon),
 | 
			
		||||
            description: Translations.WT(args.text),
 | 
			
		||||
            layerToAddTo: state.filteredLayers.data.filter(l => l.layerDef.id === args.targetLayer)[0],
 | 
			
		||||
            name: args.text,
 | 
			
		||||
            title: Translations.WT(args.text),
 | 
			
		||||
            preciseInput: {
 | 
			
		||||
                snapToLayers: o.snapSettings?.snapToLayers,
 | 
			
		||||
                maxSnapDistance: o.snapSettings?.snapToLayersMaxDist
 | 
			
		||||
                snapToLayers: args.snap_onto_layers?.split(";"),
 | 
			
		||||
                maxSnapDistance: Number(args.max_snap_distance)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const [lon, lat] = o.feature.geometry.coordinates
 | 
			
		||||
        return new ConfirmLocationOfPoint(o.state, o.guiState.filterViewIsOpened, presetInfo, Translations.W(o.message), {
 | 
			
		||||
        const [lon, lat] = feature.geometry.coordinates
 | 
			
		||||
        return new ConfirmLocationOfPoint(state, guiState.filterViewIsOpened, presetInfo, Translations.W(args.text), {
 | 
			
		||||
            lon,
 | 
			
		||||
            lat
 | 
			
		||||
        }, confirm, cancel)
 | 
			
		||||
        }, confirm, onCancel)
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    constructElement(state, args,
 | 
			
		||||
                     originalFeatureTags,
 | 
			
		||||
                     guiState,
 | 
			
		||||
                     feature,
 | 
			
		||||
                     onCancel): BaseUIElement {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        const geometry = feature.geometry
 | 
			
		||||
 | 
			
		||||
        if (geometry.type === "Point") {
 | 
			
		||||
            return new Lazy(() => ImportPointButton.createConfirmPanelForPoint(
 | 
			
		||||
                args,
 | 
			
		||||
                state,
 | 
			
		||||
                guiState,
 | 
			
		||||
                originalFeatureTags,
 | 
			
		||||
                feature,
 | 
			
		||||
                onCancel
 | 
			
		||||
            ))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        console.error("Invalid type to import", geometry.type)
 | 
			
		||||
        return new FixedUiElement("Invalid geometry type:" + geometry.type).SetClass("alert")
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,6 @@ import Histogram from "./BigComponents/Histogram";
 | 
			
		|||
import Loc from "../Models/Loc";
 | 
			
		||||
import {Utils} from "../Utils";
 | 
			
		||||
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
 | 
			
		||||
import {ImportButtonSpecialViz} from "./BigComponents/ImportButton";
 | 
			
		||||
import {Tag} from "../Logic/Tags/Tag";
 | 
			
		||||
import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource";
 | 
			
		||||
import ShowDataMultiLayer from "./ShowDataLayer/ShowDataMultiLayer";
 | 
			
		||||
| 
						 | 
				
			
			@ -39,6 +38,7 @@ import {DefaultGuiState} from "./DefaultGuiState";
 | 
			
		|||
import {GeoOperations} from "../Logic/GeoOperations";
 | 
			
		||||
import Hash from "../Logic/Web/Hash";
 | 
			
		||||
import FeaturePipelineState from "../Logic/State/FeaturePipelineState";
 | 
			
		||||
import {ConflateButton, ImportPointButton, ImportWayButton} from "./Popup/ImportButton";
 | 
			
		||||
 | 
			
		||||
export interface SpecialVisualization {
 | 
			
		||||
    funcName: string,
 | 
			
		||||
| 
						 | 
				
			
			@ -478,8 +478,9 @@ export default class SpecialVisualizations {
 | 
			
		|||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            new ImportButtonSpecialViz(),
 | 
			
		||||
            
 | 
			
		||||
            new ImportPointButton(),
 | 
			
		||||
            new ImportWayButton(),
 | 
			
		||||
            new ConflateButton(),
 | 
			
		||||
            {
 | 
			
		||||
                funcName: "multi_apply",
 | 
			
		||||
                docs: "A button to apply the tagging of this object onto a list of other features. This is an advanced feature for which you'll need calculatedTags",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
{
 | 
			
		||||
  "id": "conflation",
 | 
			
		||||
  "description": "If the import-button is set to conflate two ways, a preview is shown. This layer defines how this preview is rendered. This layer cannot be included in a theme.",
 | 
			
		||||
  "description": "If the import-button moves OSM points, the imported way points or conflates, a preview is shown. This layer defines how this preview is rendered. This layer cannot be included in a theme.",
 | 
			
		||||
  "minzoom": 1,
 | 
			
		||||
  "source": {
 | 
			
		||||
    "osmTags": {
 | 
			
		||||
| 
						 | 
				
			
			@ -20,16 +20,36 @@
 | 
			
		|||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "location": "end",
 | 
			
		||||
      "icon": "circle:#0f0",
 | 
			
		||||
      "icon": {
 | 
			
		||||
        "render": "circle:#0f0",
 | 
			
		||||
        "mappings":[ {
 | 
			
		||||
          "if": "move=no",
 | 
			
		||||
          "then": "ring:#0f0"
 | 
			
		||||
        }]
 | 
			
		||||
      },
 | 
			
		||||
      "iconSize": "10,10,center"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "location": "start",
 | 
			
		||||
      "icon": "square:#f00",
 | 
			
		||||
      "iconSize": "10,10,center"
 | 
			
		||||
      "iconSize": {
 | 
			
		||||
        "render":"10,10,center",
 | 
			
		||||
        "mappings": [
 | 
			
		||||
          {
 | 
			
		||||
            "if": "distance<0.1",
 | 
			
		||||
            "then": "0,0,center"
 | 
			
		||||
          }
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "width": "3",
 | 
			
		||||
      "width": {
 | 
			
		||||
        "render":         "3",
 | 
			
		||||
        "mappings": [{
 | 
			
		||||
          "if": "distance<0.2",
 | 
			
		||||
          "then": "0"
 | 
			
		||||
        }]
 | 
			
		||||
      },
 | 
			
		||||
      "color": "#00f",
 | 
			
		||||
      "dasharray": {
 | 
			
		||||
        "render": "",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -155,14 +155,14 @@
 | 
			
		|||
        {
 | 
			
		||||
          "if": "door=sliding",
 | 
			
		||||
          "then": {
 | 
			
		||||
            "en": "A door which rolls from overhead, typically seen for garages",
 | 
			
		||||
            "nl": "Een poort die langs boven dichtrolt, typisch voor garages"
 | 
			
		||||
            "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"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "if": "door=overhead",
 | 
			
		||||
          "then": {
 | 
			
		||||
            "en": "This is an entrance without a physical door",
 | 
			
		||||
            "en": "A door which rolls from overhead, typically seen for garages",
 | 
			
		||||
            "nl": "Een poort die langs boven dichtrolt, typisch voor garages"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -34,15 +34,17 @@
 | 
			
		|||
      "override": {
 | 
			
		||||
        "calculatedTags": [
 | 
			
		||||
          "_is_part_of_building=feat.get('parent_ways')?.some(p => p.building !== undefined && p.building !== '') ?? false",
 | 
			
		||||
          "_is_part_of_grb_building=feat.get('parent_ways')?.some(p => p['source:geometry:ref'] !== undefined) ?? false",
 | 
			
		||||
          "_is_part_of_building_passage=feat.get('parent_ways')?.some(p => p.tunnel === 'building_passage') ?? false",
 | 
			
		||||
          "_is_part_of_highway=!feat.get('is_part_of_building_passage') && (feat.get('parent_ways')?.some(p => p.highway !== undefined && p.highway !== '') ?? false)",
 | 
			
		||||
          "_is_part_of_landuse=feat.get('parent_ways')?.some(p => (p.landuse !== undefined && p.landuse !== '') || (p.natural !== undefined && p.natural !== '')) ?? false"
 | 
			
		||||
          "_is_part_of_landuse=feat.get('parent_ways')?.some(p => (p.landuse !== undefined && p.landuse !== '') || (p.natural !== undefined && p.natural !== '')) ?? false",
 | 
			
		||||
          "_moveable=feat.get('_is_part_of_building') && !feat.get('_is_part_of_grb_building')"
 | 
			
		||||
        ],
 | 
			
		||||
        "mapRendering": [
 | 
			
		||||
          {
 | 
			
		||||
            "icon": "square:#00f",
 | 
			
		||||
            "icon": "square:#cc0",
 | 
			
		||||
            "iconSize": "5,5,center",
 | 
			
		||||
            "location": "point"
 | 
			
		||||
            "location": ["point"]
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "passAllFeatures": true
 | 
			
		||||
| 
						 | 
				
			
			@ -469,7 +471,7 @@
 | 
			
		|||
      "tagRenderings": [
 | 
			
		||||
        {
 | 
			
		||||
          "id": "Import-button",
 | 
			
		||||
          "render": "{import_button(OSM-buildings,building=$building;man_made=$man_made; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref; addr:street=$addr:street; addr:housenumber=$addr:housenumber; building:min_level=$_building:min_level, Upload this building to OpenStreetMap)}",
 | 
			
		||||
          "render": "{import_way_button(OSM-buildings,building=$building;man_made=$man_made; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref; addr:street=$addr:street; addr:housenumber=$addr:housenumber; building:min_level=$_building:min_level, Upload this building to OpenStreetMap,,_is_part_of_building=true,1,_moveable=true)}",
 | 
			
		||||
          "mappings": [
 | 
			
		||||
            {
 | 
			
		||||
              "if": {
 | 
			
		||||
| 
						 | 
				
			
			@ -513,9 +515,12 @@
 | 
			
		|||
            {
 | 
			
		||||
              "if": "_osm_obj:addr:housenumber~*",
 | 
			
		||||
              "then": "The overlapping building only has a housenumber known: {_osm_obj:addr:housenumber}"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              "if": "_osm_obj:id=",
 | 
			
		||||
              "then": "No overlapping OpenStreetMap-building found"
 | 
			
		||||
            }
 | 
			
		||||
          ],
 | 
			
		||||
          "conditon": "_osm_obj:id~*"
 | 
			
		||||
          ]
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "id": "grb_address_diff",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2755,9 +2755,6 @@
 | 
			
		|||
                    "4": {
 | 
			
		||||
                        "then": "A door which rolls from overhead, typically seen for garages"
 | 
			
		||||
                    },
 | 
			
		||||
                    "5": {
 | 
			
		||||
                        "then": "This is an entrance without a physical door"
 | 
			
		||||
                    },
 | 
			
		||||
                    "5": {
 | 
			
		||||
                        "then": "This is an entrance without a physical door"
 | 
			
		||||
                    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2730,9 +2730,6 @@
 | 
			
		|||
                    "3": {
 | 
			
		||||
                        "then": "Een schuifdeur or roldeur die bij het openen en sluiten zijwaarts beweegt"
 | 
			
		||||
                    },
 | 
			
		||||
                    "4": {
 | 
			
		||||
                        "then": "Een poort die langs boven dichtrolt, typisch voor garages"
 | 
			
		||||
                    },
 | 
			
		||||
                    "4": {
 | 
			
		||||
                        "then": "Een poort die langs boven dichtrolt, typisch voor garages"
 | 
			
		||||
                    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue