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, |                         feature: feature, | ||||||
|                         freshness: now |                         freshness: now | ||||||
|                     }) |                     }) | ||||||
|                     console.warn("Added a new feature: ", JSON.stringify(feature)) |  | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 for (const change of changes) { |                 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 {Tag} from "../../Tags/Tag"; | ||||||
| import OsmChangeAction from "./OsmChangeAction"; | import OsmChangeAction, {OsmCreateAction} from "./OsmChangeAction"; | ||||||
| import {Changes} from "../Changes"; | import {Changes} from "../Changes"; | ||||||
| import {ChangeDescription} from "./ChangeDescription"; | import {ChangeDescription} from "./ChangeDescription"; | ||||||
| import {And} from "../../Tags/And"; | import {And} from "../../Tags/And"; | ||||||
| import {OsmWay} from "../OsmObject"; | import {OsmWay} from "../OsmObject"; | ||||||
| import {GeoOperations} from "../../GeoOperations"; | 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 |      * 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] |             reusedPointId = this._snapOnto.nodes[index + 1] | ||||||
|         } |         } | ||||||
|         if (reusedPointId !== undefined) { |         if (reusedPointId !== undefined) { | ||||||
|             console.log("Reusing an existing point:", reusedPointId) |  | ||||||
|             this.setElementId(reusedPointId) |             this.setElementId(reusedPointId) | ||||||
|             return [{ |             return [{ | ||||||
|                 tags: new And(this._basicTags).asChange(properties), |                 tags: new And(this._basicTags).asChange(properties), | ||||||
|  | @ -133,7 +132,6 @@ export default class CreateNewNodeAction extends OsmChangeAction { | ||||||
| 
 | 
 | ||||||
|         const locations = [...this._snapOnto.coordinates] |         const locations = [...this._snapOnto.coordinates] | ||||||
|         locations.forEach(coor => coor.reverse()) |         locations.forEach(coor => coor.reverse()) | ||||||
|         console.log("Locations are: ", locations) |  | ||||||
|         const ids = [...this._snapOnto.nodes] |         const ids = [...this._snapOnto.nodes] | ||||||
| 
 | 
 | ||||||
|         locations.splice(index + 1, 0, [this._lon, this._lat]) |         locations.splice(index + 1, 0, [this._lon, this._lat]) | ||||||
|  |  | ||||||
|  | @ -1,12 +1,13 @@ | ||||||
| import {ChangeDescription} from "./ChangeDescription"; | import {ChangeDescription} from "./ChangeDescription"; | ||||||
| import OsmChangeAction from "./OsmChangeAction"; | import {OsmCreateAction} from "./OsmChangeAction"; | ||||||
| import {Changes} from "../Changes"; | import {Changes} from "../Changes"; | ||||||
| import {Tag} from "../../Tags/Tag"; | import {Tag} from "../../Tags/Tag"; | ||||||
| import CreateNewNodeAction from "./CreateNewNodeAction"; | import CreateNewNodeAction from "./CreateNewNodeAction"; | ||||||
| import {And} from "../../Tags/And"; | import {And} from "../../Tags/And"; | ||||||
| 
 | 
 | ||||||
| export default class CreateNewWayAction extends OsmChangeAction { | export default class CreateNewWayAction extends OsmCreateAction { | ||||||
|     public newElementId: string = undefined |     public newElementId: string = undefined | ||||||
|  |     public newElementIdNumber: number = undefined; | ||||||
|     private readonly coordinates: ({ nodeId?: number, lat: number, lon: number })[]; |     private readonly coordinates: ({ nodeId?: number, lat: number, lon: number })[]; | ||||||
|     private readonly tags: Tag[]; |     private readonly tags: Tag[]; | ||||||
|     private readonly _options: { |     private readonly _options: { | ||||||
|  | @ -55,7 +56,7 @@ export default class CreateNewWayAction extends OsmChangeAction { | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         const id = changes.getNewID() |         const id = changes.getNewID() | ||||||
| 
 |         this.newElementIdNumber  = id | ||||||
|         const newWay = <ChangeDescription>{ |         const newWay = <ChangeDescription>{ | ||||||
|             id, |             id, | ||||||
|             type: "way", |             type: "way", | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import OsmChangeAction from "./OsmChangeAction"; | import OsmChangeAction, {OsmCreateAction} from "./OsmChangeAction"; | ||||||
| import {Tag} from "../../Tags/Tag"; | import {Tag} from "../../Tags/Tag"; | ||||||
| import {Changes} from "../Changes"; | import {Changes} from "../Changes"; | ||||||
| import {ChangeDescription} from "./ChangeDescription"; | 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 |  * 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[]; |     private readonly _tags: Tag[]; | ||||||
|     /** |     /** | ||||||
|      * lngLat-coordinates |      * lngLat-coordinates | ||||||
|  | @ -41,6 +41,9 @@ export default class CreateWayWithPointReuseAction extends OsmChangeAction { | ||||||
|     private _state: FeaturePipelineState; |     private _state: FeaturePipelineState; | ||||||
|     private _config: MergePointConfig[]; |     private _config: MergePointConfig[]; | ||||||
|      |      | ||||||
|  |     public newElementId: string = undefined; | ||||||
|  |     public newElementIdNumber: number = undefined | ||||||
|  | 
 | ||||||
|     constructor(tags: Tag[], |     constructor(tags: Tag[], | ||||||
|                 coordinates: [number, number][], |                 coordinates: [number, number][], | ||||||
|                 state: FeaturePipelineState, |                 state: FeaturePipelineState, | ||||||
|  | @ -87,7 +90,8 @@ export default class CreateWayWithPointReuseAction extends OsmChangeAction { | ||||||
|                     properties: { |                     properties: { | ||||||
|                         "move": "yes", |                         "move": "yes", | ||||||
|                         "osm-id": reusedPoint.node.properties.id, |                         "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: { |                     geometry: { | ||||||
|                         type: "LineString", |                         type: "LineString", | ||||||
|  | @ -97,8 +101,23 @@ export default class CreateWayWithPointReuseAction extends OsmChangeAction { | ||||||
|                 features.push(moveDescription) |                 features.push(moveDescription) | ||||||
| 
 | 
 | ||||||
|             } else { |             } else { | ||||||
|                 // The geometry is moved
 |                 // The geometry is moved, the point is reused
 | ||||||
|                 geometryMoved = true |                 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) |             features.push(newGeometry) | ||||||
| 
 | 
 | ||||||
|         } |         } | ||||||
|         console.log("Preview:", features) |  | ||||||
|         return new StaticFeatureSource(features, false) |         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 theme = this._state.layoutToUse.id | ||||||
|         const allChanges: ChangeDescription[] = [] |         const allChanges: ChangeDescription[] = [] | ||||||
|         const nodeIdsToUse: { lat: number, lon: number, nodeId?: number }[] = [] |         const nodeIdsToUse: { lat: number, lon: number, nodeId?: number }[] = [] | ||||||
|  | @ -196,6 +214,8 @@ export default class CreateWayWithPointReuseAction extends OsmChangeAction { | ||||||
|         }) |         }) | ||||||
|          |          | ||||||
|         allChanges.push(...(await newWay.CreateChangeDescriptions(changes))) |         allChanges.push(...(await newWay.CreateChangeDescriptions(changes))) | ||||||
|  |         this.newElementId = newWay.newElementId | ||||||
|  |         this.newElementIdNumber = newWay.newElementIdNumber | ||||||
|         return allChanges |         return allChanges | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -11,7 +11,7 @@ export default abstract class OsmChangeAction { | ||||||
|     public readonly trackStatistics: boolean; |     public readonly trackStatistics: boolean; | ||||||
|     /** |     /** | ||||||
|      * The ID of the object that is the center of this change. |      * 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 |      * Undefined if such an id does not make sense | ||||||
|      */ |      */ | ||||||
|     public readonly mainObjectId: string; |     public readonly mainObjectId: string; | ||||||
|  | @ -31,3 +31,10 @@ export default abstract class OsmChangeAction { | ||||||
| 
 | 
 | ||||||
|     protected abstract CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]> |     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) { |         if (shorten) { | ||||||
|             v = Utils.EllipsesAfter(v, 25); |             v = Utils.EllipsesAfter(v, 25); | ||||||
|         } |         } | ||||||
|  |         if(v === "" || v === undefined){ | ||||||
|  |             return "<span class='line-through'>"+this.key+"</span>" | ||||||
|  |         } | ||||||
|         if (linkToWiki) { |         if (linkToWiki) { | ||||||
|             return `<a href='https://wiki.openstreetmap.org/wiki/Key:${this.key}' target='_blank'>${this.key}</a>` + |             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 matchesProperties(properties: any): boolean; | ||||||
| 
 | 
 | ||||||
|     abstract asHumanString(linkToWiki: boolean, shorten: boolean, properties: any); |     abstract asHumanString(linkToWiki: boolean, shorten: boolean, properties: any) : string; | ||||||
| 
 | 
 | ||||||
|     abstract usedKeys(): string[]; |     abstract usedKeys(): string[]; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ import {Utils} from "../Utils"; | ||||||
| 
 | 
 | ||||||
| export default class Constants { | 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 ImgurApiKey = '7070e7167f0a25a' | ||||||
|     public static readonly mapillary_client_token_v4 = "MLY|4441509239301885|b40ad2d3ea105435bd40c7e76993ae85" |     public static readonly mapillary_client_token_v4 = "MLY|4441509239301885|b40ad2d3ea105435bd40c7e76993ae85" | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -136,7 +136,7 @@ export interface LayerConfigJson { | ||||||
|     titleIcons?: (string | TagRenderingConfigJson)[]; |     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. |      * 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 Constants from "../../Models/Constants"; | ||||||
| import Toggle from "../Input/Toggle"; | import Toggle from "../Input/Toggle"; | ||||||
| import CreateNewNodeAction from "../../Logic/Osm/Actions/CreateNewNodeAction"; | import CreateNewNodeAction from "../../Logic/Osm/Actions/CreateNewNodeAction"; | ||||||
| import {Tag} from "../../Logic/Tags/Tag"; |  | ||||||
| import Loading from "../Base/Loading"; | import Loading from "../Base/Loading"; | ||||||
| import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; |  | ||||||
| import {OsmConnection} from "../../Logic/Osm/OsmConnection"; | 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 Lazy from "../Base/Lazy"; | ||||||
| import ConfirmLocationOfPoint from "../NewPoint/ConfirmLocationOfPoint"; | import ConfirmLocationOfPoint from "../NewPoint/ConfirmLocationOfPoint"; | ||||||
| import Img from "../Base/Img"; | import Img from "../Base/Img"; | ||||||
| import {Translation} from "../i18n/Translation"; |  | ||||||
| import FilteredLayer from "../../Models/FilteredLayer"; | import FilteredLayer from "../../Models/FilteredLayer"; | ||||||
| import SpecialVisualizations from "../SpecialVisualizations"; | import SpecialVisualizations from "../SpecialVisualizations"; | ||||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | import {FixedUiElement} from "../Base/FixedUiElement"; | ||||||
|  | @ -28,68 +22,29 @@ import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"; | ||||||
| import AllKnownLayers from "../../Customizations/AllKnownLayers"; | import AllKnownLayers from "../../Customizations/AllKnownLayers"; | ||||||
| import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"; | import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"; | ||||||
| import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer"; | 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 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 FeatureSource from "../../Logic/FeatureSource/FeatureSource"; | ||||||
| import {OsmObject, OsmWay} from "../../Logic/Osm/OsmObject"; | import {OsmObject, OsmWay} from "../../Logic/Osm/OsmObject"; | ||||||
| import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"; | import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"; | ||||||
| import {DefaultGuiState} from "../DefaultGuiState"; | import {DefaultGuiState} from "../DefaultGuiState"; | ||||||
| import {PresetInfo} from "../BigComponents/SimpleAddUI"; | import {PresetInfo} from "../BigComponents/SimpleAddUI"; | ||||||
| 
 | import {TagUtils} from "../../Logic/Tags/TagUtils"; | ||||||
| 
 | import {And} from "../../Logic/Tags/And"; | ||||||
| export interface ImportButtonState { | import ReplaceGeometryAction from "../../Logic/Osm/Actions/ReplaceGeometryAction"; | ||||||
|     description?: Translation; | import CreateMultiPolygonWithPointReuseAction from "../../Logic/Osm/Actions/CreateMultiPolygonWithPointReuseAction"; | ||||||
|     image: () => BaseUIElement, | import {Tag} from "../../Logic/Tags/Tag"; | ||||||
|     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[] |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| abstract class AbstractImportButton implements SpecialVisualizations { | abstract class AbstractImportButton implements SpecialVisualizations { | ||||||
|     public readonly funcName: string |     public readonly funcName: string | ||||||
|     public readonly docs: string |     public readonly docs: string | ||||||
|     public readonly args: { name: string, defaultValue?: string, doc: 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.funcName = funcName | ||||||
|  |         this.showRemovedTags = showRemovedTags; | ||||||
| 
 | 
 | ||||||
|         this.docs = `${docsIntro} |         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_tagsToApplyHelpText} | ||||||
| ${Utils.special_visualizations_importRequirementDocs} | ${Utils.special_visualizations_importRequirementDocs} | ||||||
|    |  | ||||||
| ` | ` | ||||||
| 
 |  | ||||||
|         this.args = [ |         this.args = [ | ||||||
|             { |             { | ||||||
|                 name: "targetLayer", |                 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 }, |     abstract constructElement(state: FeaturePipelineState, | ||||||
|                               tagSource: UIEventSource<any>, guiState: DefaultGuiState): BaseUIElement; |                               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) { |     constr(state, tagSource, argsRaw, guiState) { | ||||||
| 
 |         const self = this; | ||||||
|         /** |         /** | ||||||
|          * Some generic import button pre-validation is implemented here: |          * Some generic import button pre-validation is implemented here: | ||||||
|          * - Are we logged in? |          * - Are we logged in? | ||||||
|  | @ -144,7 +102,7 @@ ${Utils.special_visualizations_importRequirementDocs} | ||||||
| 
 | 
 | ||||||
|         const t = Translations.t.general.add.import; |         const t = Translations.t.general.add.import; | ||||||
|         const t0 = Translations.t.general.add; |         const t0 = Translations.t.general.add; | ||||||
|         const args = this.parseArgs(argsRaw) |         const args = this.parseArgs(argsRaw, tagSource) | ||||||
| 
 | 
 | ||||||
|         { |         { | ||||||
|             // Some initial validation
 |             // Some initial validation
 | ||||||
|  | @ -172,26 +130,22 @@ ${Utils.special_visualizations_importRequirementDocs} | ||||||
|         const feature = state.allElements.ContainingFeatures.get(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
 |         // Explanation of the tags that will be applied onto the imported/conflated object
 | ||||||
|         const newTags = SpecialVisualizations.generateTagsToApply(args.tags, tagSource) |         const newTags = SpecialVisualizations.generateTagsToApply(args.tags, tagSource) | ||||||
|         const appliedTags = new Toggle( |         const appliedTags = new Toggle( | ||||||
|             new VariableUiElement( |             new VariableUiElement( | ||||||
|                 newTags.map(tgs => { |                 newTags.map(tgs => { | ||||||
|                     const parts = [] |                     const filteredTags = tgs.filter(tg => self.showRemovedTags || (tg.value ?? "") !== "") | ||||||
|                     for (const tag of tgs) { |                     const asText = new And(filteredTags) | ||||||
|                         parts.push(tag.key + "=" + tag.value) |                         .asHumanString(true, true, {}) | ||||||
|                     } | 
 | ||||||
|                     const txt = parts.join(" & ") |                     return t0.presetInfo.Subs({tags: asText}).SetClass("subtle"); | ||||||
|                     return t0.presetInfo.Subs({tags: txt}).SetClass("subtle") |                 })), | ||||||
|                 })), undefined, |             undefined, | ||||||
|             state.osmConnection.userDetails.map(ud => ud.csCount >= Constants.userJourney.tagsVisibleAt) |             state.osmConnection.userDetails.map(ud => ud.csCount >= Constants.userJourney.tagsVisibleAt) | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|         const importClicked = new UIEventSource(false); |         const importClicked = new UIEventSource(false); | ||||||
|         inviteToImportButton.onClick(() => { |         inviteToImportButton.onClick(() => { | ||||||
|             importClicked.setData(true); |             importClicked.setData(true); | ||||||
|  | @ -208,6 +162,8 @@ ${Utils.special_visualizations_importRequirementDocs} | ||||||
|         const isImported = tagSource.map(tags => tags._imported === "yes") |         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( |         const importFlow = new Toggle( | ||||||
|  | @ -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 } { |     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[]> } { | ||||||
|         return Utils.ParseVisArgs(this.args, argsRaw) |         const baseArgs = Utils.ParseVisArgs(this.args, argsRaw) | ||||||
|  |         if (originalFeatureTags !== undefined) { | ||||||
|  |             baseArgs["newTags"] = SpecialVisualizations.generateTagsToApply(baseArgs.tags, originalFeatureTags) | ||||||
|  |         } | ||||||
|  |         return baseArgs | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     getLayerDependencies(argsRaw: string[]) { |     getLayerDependencies(argsRaw: string[]) { | ||||||
|         const args = this.parseArgs(argsRaw) |         const args = this.parseArgs(argsRaw, undefined) | ||||||
| 
 | 
 | ||||||
|         const dependsOnLayers: string[] = [] |         const dependsOnLayers: string[] = [] | ||||||
| 
 | 
 | ||||||
|  | @ -261,181 +221,31 @@ ${Utils.special_visualizations_importRequirementDocs} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     protected abstract canBeImported(feature: any) |     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({ |         const confirmationMap = Minimap.createMiniMap({ | ||||||
|             allowMoving: false, |             allowMoving: false, | ||||||
|             background: o.state.backgroundLayer |             background: state.backgroundLayer | ||||||
|         }) |         }) | ||||||
|         confirmationMap.SetStyle("height: 20rem; overflow: hidden").SetClass("rounded-xl") |         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
 |         // SHow all relevant data - including (eventually) the way of which the geometry will be replaced
 | ||||||
|         new ShowDataMultiLayer({ |         new ShowDataMultiLayer({ | ||||||
|             leafletMap: confirmationMap.leafletMap, |             leafletMap: confirmationMap.leafletMap, | ||||||
|             enablePopups: false, |             enablePopups: false, | ||||||
|             zoomToFeatures: true, |             zoomToFeatures: true, | ||||||
|             features: new StaticFeatureSource(relevantFeatures, false), |             features: new StaticFeatureSource([feature], false), | ||||||
|             allElements: o.state.allElements, |             allElements: state.allElements, | ||||||
|             layers: o.state.filteredLayers |             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 => { |         action.getPreview().then(changePreview => { | ||||||
|             new ShowDataLayer({ |             new ShowDataLayer({ | ||||||
|  | @ -443,89 +253,328 @@ export default class ImportButton { | ||||||
|                 enablePopups: false, |                 enablePopups: false, | ||||||
|                 zoomToFeatures: false, |                 zoomToFeatures: false, | ||||||
|                 features: changePreview, |                 features: changePreview, | ||||||
|                 allElements: o.state.allElements, |                 allElements: state.allElements, | ||||||
|                 layerToShow: AllKnownLayers.sharedLayers.get("conflation") |                 layerToShow: AllKnownLayers.sharedLayers.get("conflation") | ||||||
|             }) |             }) | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
|         const tagsExplanation = new VariableUiElement(o.newTags.map(tagsToApply => { |         const tagsExplanation = new VariableUiElement(args.newTags.map(tagsToApply => { | ||||||
|                 const tagsStr = tagsToApply.map(t => t.asHumanString(false, true)).join("&"); |                 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}); |                 return Translations.t.general.add.import.importTags.Subs({tags: tagsStr}); | ||||||
|             } |             } | ||||||
|         )).SetClass("subtle") |         )).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 () => { |         confirmButton.onClick(async () => { | ||||||
|             { |             { | ||||||
|                 if (isImported.data) { |                 originalFeatureTags.data["_imported"] = "yes" | ||||||
|                     return |                 originalFeatureTags.ping() // will set isImported as per its definition
 | ||||||
|                 } |                 state.changes.applyAction(action) | ||||||
|                 o.originalTags.data["_imported"] = "yes" |                 state.selectedElement.setData(state.allElements.ContainingFeatures.get(action.newElementId ?? action.mainObjectId)) | ||||||
|                 o.originalTags.ping() // will set isImported as per its definition
 |  | ||||||
| 
 |  | ||||||
|                 const idToSelect = await confirm() |  | ||||||
| 
 |  | ||||||
|                 o.state.selectedElement.setData(o.state.allElements.ContainingFeatures.get(idToSelect)) |  | ||||||
| 
 | 
 | ||||||
|             } |             } | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
|         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") |         return new Combine([confirmationMap, confirmButton, cancel]).SetClass("flex flex-col") | ||||||
|     } |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     public static createConfirmPanelForPoint( | export class ConflateButton extends AbstractImportButton { | ||||||
|         o: ImportButtonState, | 
 | ||||||
|         isImported: UIEventSource<boolean>, |     constructor() { | ||||||
|         importClicked: UIEventSource<boolean>): BaseUIElement { |         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) { |         async function confirm(tags: any[], location: { lat: number, lon: number }, snapOntoWayId: string) { | ||||||
| 
 | 
 | ||||||
|             if (isImported.data) { |             originalFeatureTags.data["_imported"] = "yes" | ||||||
|                 return |             originalFeatureTags.ping() // will set isImported as per its definition
 | ||||||
|             } |  | ||||||
|             o.originalTags.data["_imported"] = "yes" |  | ||||||
|             o.originalTags.ping() // will set isImported as per its definition
 |  | ||||||
|             let snapOnto: OsmObject = undefined |             let snapOnto: OsmObject = undefined | ||||||
|             if (snapOntoWayId !== undefined) { |             if (snapOntoWayId !== undefined) { | ||||||
|                 snapOnto = await OsmObject.DownloadObjectAsync(snapOntoWayId) |                 snapOnto = await OsmObject.DownloadObjectAsync(snapOntoWayId) | ||||||
|             } |             } | ||||||
|             const newElementAction = new CreateNewNodeAction(tags, location.lat, location.lon, { |             const newElementAction = new CreateNewNodeAction(tags, location.lat, location.lon, { | ||||||
|                 theme: o.state.layoutToUse.id, |                 theme: state.layoutToUse.id, | ||||||
|                 changeType: "import", |                 changeType: "import", | ||||||
|                 snapOnto: <OsmWay>snapOnto |                 snapOnto: <OsmWay>snapOnto | ||||||
|             }) |             }) | ||||||
| 
 | 
 | ||||||
|             await o.state.changes.applyAction(newElementAction) |             await state.changes.applyAction(newElementAction) | ||||||
|             o.state.selectedElement.setData(o.state.allElements.ContainingFeatures.get( |             state.selectedElement.setData(state.allElements.ContainingFeatures.get( | ||||||
|                 newElementAction.newElementId |                 newElementAction.newElementId | ||||||
|             )) |             )) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         function cancel() { |  | ||||||
|             importClicked.setData(false) |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         const presetInfo = <PresetInfo>{ |         const presetInfo = <PresetInfo>{ | ||||||
|             tags: o.newTags.data, |             tags: args.newTags.data, | ||||||
|             icon: o.image, |             icon: () => new Img(args.icon), | ||||||
|             description: o.description, |             description: Translations.WT(args.text), | ||||||
|             layerToAddTo: o.targetLayer, |             layerToAddTo: state.filteredLayers.data.filter(l => l.layerDef.id === args.targetLayer)[0], | ||||||
|             name: o.message, |             name: args.text, | ||||||
|             title: o.message, |             title: Translations.WT(args.text), | ||||||
|             preciseInput: { |             preciseInput: { | ||||||
|                 snapToLayers: o.snapSettings?.snapToLayers, |                 snapToLayers: args.snap_onto_layers?.split(";"), | ||||||
|                 maxSnapDistance: o.snapSettings?.snapToLayersMaxDist |                 maxSnapDistance: Number(args.max_snap_distance) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const [lon, lat] = o.feature.geometry.coordinates |         const [lon, lat] = feature.geometry.coordinates | ||||||
|         return new ConfirmLocationOfPoint(o.state, o.guiState.filterViewIsOpened, presetInfo, Translations.W(o.message), { |         return new ConfirmLocationOfPoint(state, guiState.filterViewIsOpened, presetInfo, Translations.W(args.text), { | ||||||
|             lon, |             lon, | ||||||
|             lat |             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 Loc from "../Models/Loc"; | ||||||
| import {Utils} from "../Utils"; | import {Utils} from "../Utils"; | ||||||
| import LayerConfig from "../Models/ThemeConfig/LayerConfig"; | import LayerConfig from "../Models/ThemeConfig/LayerConfig"; | ||||||
| import {ImportButtonSpecialViz} from "./BigComponents/ImportButton"; |  | ||||||
| import {Tag} from "../Logic/Tags/Tag"; | import {Tag} from "../Logic/Tags/Tag"; | ||||||
| import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource"; | import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource"; | ||||||
| import ShowDataMultiLayer from "./ShowDataLayer/ShowDataMultiLayer"; | import ShowDataMultiLayer from "./ShowDataLayer/ShowDataMultiLayer"; | ||||||
|  | @ -39,6 +38,7 @@ import {DefaultGuiState} from "./DefaultGuiState"; | ||||||
| import {GeoOperations} from "../Logic/GeoOperations"; | import {GeoOperations} from "../Logic/GeoOperations"; | ||||||
| import Hash from "../Logic/Web/Hash"; | import Hash from "../Logic/Web/Hash"; | ||||||
| import FeaturePipelineState from "../Logic/State/FeaturePipelineState"; | import FeaturePipelineState from "../Logic/State/FeaturePipelineState"; | ||||||
|  | import {ConflateButton, ImportPointButton, ImportWayButton} from "./Popup/ImportButton"; | ||||||
| 
 | 
 | ||||||
| export interface SpecialVisualization { | export interface SpecialVisualization { | ||||||
|     funcName: string, |     funcName: string, | ||||||
|  | @ -478,8 +478,9 @@ export default class SpecialVisualizations { | ||||||
|                     ) |                     ) | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             new ImportButtonSpecialViz(), |             new ImportPointButton(), | ||||||
|              |             new ImportWayButton(), | ||||||
|  |             new ConflateButton(), | ||||||
|             { |             { | ||||||
|                 funcName: "multi_apply", |                 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", |                 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", |   "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, |   "minzoom": 1, | ||||||
|   "source": { |   "source": { | ||||||
|     "osmTags": { |     "osmTags": { | ||||||
|  | @ -20,16 +20,36 @@ | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       "location": "end", |       "location": "end", | ||||||
|       "icon": "circle:#0f0", |       "icon": { | ||||||
|  |         "render": "circle:#0f0", | ||||||
|  |         "mappings":[ { | ||||||
|  |           "if": "move=no", | ||||||
|  |           "then": "ring:#0f0" | ||||||
|  |         }] | ||||||
|  |       }, | ||||||
|       "iconSize": "10,10,center" |       "iconSize": "10,10,center" | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       "location": "start", |       "location": "start", | ||||||
|       "icon": "square:#f00", |       "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", |       "color": "#00f", | ||||||
|       "dasharray": { |       "dasharray": { | ||||||
|         "render": "", |         "render": "", | ||||||
|  |  | ||||||
|  | @ -155,14 +155,14 @@ | ||||||
|         { |         { | ||||||
|           "if": "door=sliding", |           "if": "door=sliding", | ||||||
|           "then": { |           "then": { | ||||||
|             "en": "A door which rolls from overhead, typically seen for garages", |             "en": "A sliding door where the door slides sidewards, typically parallel with a wall", | ||||||
|             "nl": "Een poort die langs boven dichtrolt, typisch voor garages" |             "nl": "Een schuifdeur or roldeur die bij het openen en sluiten zijwaarts beweegt" | ||||||
|           } |           } | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|           "if": "door=overhead", |           "if": "door=overhead", | ||||||
|           "then": { |           "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" |             "nl": "Een poort die langs boven dichtrolt, typisch voor garages" | ||||||
|           } |           } | ||||||
|         }, |         }, | ||||||
|  |  | ||||||
|  | @ -34,15 +34,17 @@ | ||||||
|       "override": { |       "override": { | ||||||
|         "calculatedTags": [ |         "calculatedTags": [ | ||||||
|           "_is_part_of_building=feat.get('parent_ways')?.some(p => p.building !== undefined && p.building !== '') ?? false", |           "_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_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_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": [ |         "mapRendering": [ | ||||||
|           { |           { | ||||||
|             "icon": "square:#00f", |             "icon": "square:#cc0", | ||||||
|             "iconSize": "5,5,center", |             "iconSize": "5,5,center", | ||||||
|             "location": "point" |             "location": ["point"] | ||||||
|           } |           } | ||||||
|         ], |         ], | ||||||
|         "passAllFeatures": true |         "passAllFeatures": true | ||||||
|  | @ -469,7 +471,7 @@ | ||||||
|       "tagRenderings": [ |       "tagRenderings": [ | ||||||
|         { |         { | ||||||
|           "id": "Import-button", |           "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": [ |           "mappings": [ | ||||||
|             { |             { | ||||||
|               "if": { |               "if": { | ||||||
|  | @ -513,9 +515,12 @@ | ||||||
|             { |             { | ||||||
|               "if": "_osm_obj:addr:housenumber~*", |               "if": "_osm_obj:addr:housenumber~*", | ||||||
|               "then": "The overlapping building only has a housenumber known: {_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", |           "id": "grb_address_diff", | ||||||
|  |  | ||||||
|  | @ -2755,9 +2755,6 @@ | ||||||
|                     "4": { |                     "4": { | ||||||
|                         "then": "A door which rolls from overhead, typically seen for garages" |                         "then": "A door which rolls from overhead, typically seen for garages" | ||||||
|                     }, |                     }, | ||||||
|                     "5": { |  | ||||||
|                         "then": "This is an entrance without a physical door" |  | ||||||
|                     }, |  | ||||||
|                     "5": { |                     "5": { | ||||||
|                         "then": "This is an entrance without a physical door" |                         "then": "This is an entrance without a physical door" | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
|  | @ -2730,9 +2730,6 @@ | ||||||
|                     "3": { |                     "3": { | ||||||
|                         "then": "Een schuifdeur or roldeur die bij het openen en sluiten zijwaarts beweegt" |                         "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": { |                     "4": { | ||||||
|                         "then": "Een poort die langs boven dichtrolt, typisch voor garages" |                         "then": "Een poort die langs boven dichtrolt, typisch voor garages" | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue