forked from MapComplete/MapComplete
		
	
							parent
							
								
									c8971a1cbe
								
							
						
					
					
						commit
						8f51dd8d64
					
				
					 4 changed files with 123 additions and 70 deletions
				
			
		|  | @ -16,6 +16,7 @@ export default class SplitAction extends OsmChangeAction { | |||
|     private readonly _splitPointsCoordinates: [number, number][] // lon, lat
 | ||||
|     private _meta: { theme: string; changeType: "split" } | ||||
|     private _toleranceInMeters: number | ||||
|     private _withNewCoordinates: (coordinates: [number, number][]) => void | ||||
| 
 | ||||
|     /** | ||||
|      * Create a changedescription for splitting a point. | ||||
|  | @ -24,17 +25,20 @@ export default class SplitAction extends OsmChangeAction { | |||
|      * @param splitPointCoordinates: lon, lat | ||||
|      * @param meta | ||||
|      * @param toleranceInMeters: if a splitpoint closer then this amount of meters to an existing point, the existing point will be used to split the line instead of a new point | ||||
|      * @param withNewCoordinates: an optional callback which will leak the new coordinates of the original way | ||||
|      */ | ||||
|     constructor( | ||||
|         wayId: string, | ||||
|         splitPointCoordinates: [number, number][], | ||||
|         meta: { theme: string }, | ||||
|         toleranceInMeters = 5 | ||||
|         toleranceInMeters = 5, | ||||
|         withNewCoordinates?: (coordinates: [number, number][]) => void | ||||
|     ) { | ||||
|         super(wayId, true) | ||||
|         this.wayId = wayId | ||||
|         this._splitPointsCoordinates = splitPointCoordinates | ||||
|         this._toleranceInMeters = toleranceInMeters | ||||
|         this._withNewCoordinates = withNewCoordinates | ||||
|         this._meta = { ...meta, changeType: "split" } | ||||
|     } | ||||
| 
 | ||||
|  | @ -59,7 +63,7 @@ export default class SplitAction extends OsmChangeAction { | |||
|         const originalElement = <OsmWay>await OsmObject.DownloadObjectAsync(this.wayId) | ||||
|         const originalNodes = originalElement.nodes | ||||
| 
 | ||||
|         // First, calculate splitpoints and remove points close to one another
 | ||||
|         // First, calculate the splitpoints and remove points close to one another
 | ||||
|         const splitInfo = this.CalculateSplitCoordinates(originalElement, this._toleranceInMeters) | ||||
|         // Now we have a list with e.g.
 | ||||
|         // [ { originalIndex: 0}, {originalIndex: 1, doSplit: true}, {originalIndex: 2}, {originalIndex: undefined, doSplit: true}, {originalIndex: 3}]
 | ||||
|  | @ -90,7 +94,7 @@ export default class SplitAction extends OsmChangeAction { | |||
|         } | ||||
| 
 | ||||
|         const changeDescription: ChangeDescription[] = [] | ||||
|         // Let's create the new points as needed
 | ||||
|         // Let's create the new nodes as needed
 | ||||
|         for (const element of splitInfo) { | ||||
|             if (element.originalIndex >= 0) { | ||||
|                 continue | ||||
|  | @ -114,17 +118,21 @@ export default class SplitAction extends OsmChangeAction { | |||
|         for (const wayPart of wayParts) { | ||||
|             let isOriginal = wayPart === longest | ||||
|             if (isOriginal) { | ||||
|                 // We change the actual element!
 | ||||
|                 // We change the existing way
 | ||||
|                 const nodeIds = wayPart.map((p) => p.originalIndex) | ||||
|                 const newCoordinates = wayPart.map((p) => p.lngLat) | ||||
|                 changeDescription.push({ | ||||
|                     type: "way", | ||||
|                     id: originalElement.id, | ||||
|                     changes: { | ||||
|                         coordinates: wayPart.map((p) => p.lngLat), | ||||
|                         coordinates: newCoordinates, | ||||
|                         nodes: nodeIds, | ||||
|                     }, | ||||
|                     meta: this._meta, | ||||
|                 }) | ||||
|                 if (this._withNewCoordinates) { | ||||
|                     this._withNewCoordinates(newCoordinates) | ||||
|                 } | ||||
|                 allWayIdsInOrder.push(originalElement.id) | ||||
|                 allWaysNodesInOrder.push(nodeIds) | ||||
|             } else { | ||||
|  | @ -141,6 +149,10 @@ export default class SplitAction extends OsmChangeAction { | |||
|                     kv.push({ k: k, v: originalElement.tags[k] }) | ||||
|                 } | ||||
|                 const nodeIds = wayPart.map((p) => p.originalIndex) | ||||
|                 if (nodeIds.length <= 1) { | ||||
|                     console.error("Got a segment with only one node - skipping") | ||||
|                     continue | ||||
|                 } | ||||
|                 changeDescription.push({ | ||||
|                     type: "way", | ||||
|                     id: id, | ||||
|  |  | |||
|  | @ -22,8 +22,10 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" | |||
| import { ElementStorage } from "../../Logic/ElementStorage" | ||||
| import BaseLayer from "../../Models/BaseLayer" | ||||
| import FilteredLayer from "../../Models/FilteredLayer" | ||||
| import BaseUIElement from "../BaseUIElement" | ||||
| import { VariableUiElement } from "../Base/VariableUIElement" | ||||
| 
 | ||||
| export default class SplitRoadWizard extends Toggle { | ||||
| export default class SplitRoadWizard extends Combine { | ||||
|     // @ts-ignore
 | ||||
|     private static splitLayerStyling = new LayerConfig( | ||||
|         split_point, | ||||
|  | @ -63,6 +65,106 @@ export default class SplitRoadWizard extends Toggle { | |||
| 
 | ||||
|         // Toggle variable between show split button and map
 | ||||
|         const splitClicked = new UIEventSource<boolean>(false) | ||||
| 
 | ||||
|         const leafletMap = new UIEventSource<BaseUIElement>( | ||||
|             SplitRoadWizard.setupMapComponent(id, splitPoints, state) | ||||
|         ) | ||||
| 
 | ||||
|         // Toggle between splitmap
 | ||||
|         const splitButton = new SubtleButton( | ||||
|             Svg.scissors_ui().SetStyle("height: 1.5rem; width: auto"), | ||||
|             new Toggle( | ||||
|                 t.splitAgain.Clone().SetClass("text-lg font-bold"), | ||||
|                 t.inviteToSplit.Clone().SetClass("text-lg font-bold"), | ||||
|                 hasBeenSplit | ||||
|             ) | ||||
|         ) | ||||
|         splitButton.onClick(() => { | ||||
|             splitClicked.setData(true) | ||||
|         }) | ||||
| 
 | ||||
|         // Only show the splitButton if logged in, else show login prompt
 | ||||
|         const loginBtn = t.loginToSplit | ||||
|             .Clone() | ||||
|             .onClick(() => state.osmConnection.AttemptLogin()) | ||||
|             .SetClass("login-button-friendly") | ||||
|         const splitToggle = new Toggle(splitButton, loginBtn, state.osmConnection.isLoggedIn) | ||||
| 
 | ||||
|         // Save button
 | ||||
|         const saveButton = new Button(t.split.Clone(), async () => { | ||||
|             hasBeenSplit.setData(true) | ||||
|             splitClicked.setData(false) | ||||
|             const splitAction = new SplitAction( | ||||
|                 id, | ||||
|                 splitPoints.data.map((ff) => ff.feature.geometry.coordinates), | ||||
|                 { | ||||
|                     theme: state?.layoutToUse?.id, | ||||
|                 }, | ||||
|                 5, | ||||
|                 (coordinates) => { | ||||
|                     state.allElements.ContainingFeatures.get(id).geometry["coordinates"] = | ||||
|                         coordinates | ||||
|                 } | ||||
|             ) | ||||
|             await state.changes.applyAction(splitAction) | ||||
|             // We throw away the old map and splitpoints, and create a new map from scratch
 | ||||
|             splitPoints.setData([]) | ||||
|             leafletMap.setData(SplitRoadWizard.setupMapComponent(id, splitPoints, state)) | ||||
|         }) | ||||
| 
 | ||||
|         saveButton.SetClass("btn btn-primary mr-3") | ||||
|         const disabledSaveButton = new Button("Split", undefined) | ||||
|         disabledSaveButton.SetClass("btn btn-disabled mr-3") | ||||
|         // Only show the save button if there are split points defined
 | ||||
|         const saveToggle = new Toggle( | ||||
|             disabledSaveButton, | ||||
|             saveButton, | ||||
|             splitPoints.map((data) => data.length === 0) | ||||
|         ) | ||||
| 
 | ||||
|         const cancelButton = Translations.t.general.cancel | ||||
|             .Clone() // Not using Button() element to prevent full width button
 | ||||
|             .SetClass("btn btn-secondary mr-3") | ||||
|             .onClick(() => { | ||||
|                 splitPoints.setData([]) | ||||
|                 splitClicked.setData(false) | ||||
|             }) | ||||
| 
 | ||||
|         cancelButton.SetClass("btn btn-secondary block") | ||||
| 
 | ||||
|         const splitTitle = new Title(t.splitTitle) | ||||
| 
 | ||||
|         const mapView = new Combine([ | ||||
|             splitTitle, | ||||
|             new VariableUiElement(leafletMap), | ||||
|             new Combine([cancelButton, saveToggle]).SetClass("flex flex-row"), | ||||
|         ]) | ||||
|         mapView.SetClass("question") | ||||
|         super([ | ||||
|             Toggle.If(hasBeenSplit, () => | ||||
|                 t.hasBeenSplit.Clone().SetClass("font-bold thanks block w-full") | ||||
|             ), | ||||
|             new Toggle(mapView, splitToggle, splitClicked), | ||||
|         ]) | ||||
|         this.dialogIsOpened = splitClicked | ||||
|     } | ||||
| 
 | ||||
|     private static setupMapComponent( | ||||
|         id: string, | ||||
|         splitPoints: UIEventSource<{ feature: any; freshness: Date }[]>, | ||||
|         state: { | ||||
|             filteredLayers: UIEventSource<FilteredLayer[]> | ||||
|             backgroundLayer: UIEventSource<BaseLayer> | ||||
|             featureSwitchIsTesting: UIEventSource<boolean> | ||||
|             featureSwitchIsDebugging: UIEventSource<boolean> | ||||
|             featureSwitchShowAllQuestions: UIEventSource<boolean> | ||||
|             osmConnection: OsmConnection | ||||
|             featureSwitchUserbadge: UIEventSource<boolean> | ||||
|             changes: Changes | ||||
|             layoutToUse: LayoutConfig | ||||
|             allElements: ElementStorage | ||||
|         } | ||||
|     ): BaseUIElement { | ||||
|         // Load the road with given id on the minimap
 | ||||
|         const roadElement = state.allElements.ContainingFeatures.get(id) | ||||
| 
 | ||||
|  | @ -96,7 +198,6 @@ export default class SplitRoadWizard extends Toggle { | |||
|             layerToShow: SplitRoadWizard.splitLayerStyling, | ||||
|             state, | ||||
|         }) | ||||
| 
 | ||||
|         /** | ||||
|          * Handles a click on the overleaf map. | ||||
|          * Finds the closest intersection with the road and adds a point there, ready to confirm the cut. | ||||
|  | @ -137,67 +238,6 @@ export default class SplitRoadWizard extends Toggle { | |||
|                 onMapClick([mouseEvent.latlng.lng, mouseEvent.latlng.lat]) | ||||
|             }) | ||||
|         ) | ||||
| 
 | ||||
|         // Toggle between splitmap
 | ||||
|         const splitButton = new SubtleButton( | ||||
|             Svg.scissors_ui().SetStyle("height: 1.5rem; width: auto"), | ||||
|             t.inviteToSplit.Clone().SetClass("text-lg font-bold") | ||||
|         ) | ||||
|         splitButton.onClick(() => { | ||||
|             splitClicked.setData(true) | ||||
|         }) | ||||
| 
 | ||||
|         // Only show the splitButton if logged in, else show login prompt
 | ||||
|         const loginBtn = t.loginToSplit | ||||
|             .Clone() | ||||
|             .onClick(() => state.osmConnection.AttemptLogin()) | ||||
|             .SetClass("login-button-friendly") | ||||
|         const splitToggle = new Toggle(splitButton, loginBtn, state.osmConnection.isLoggedIn) | ||||
| 
 | ||||
|         // Save button
 | ||||
|         const saveButton = new Button(t.split.Clone(), () => { | ||||
|             hasBeenSplit.setData(true) | ||||
|             state.changes.applyAction( | ||||
|                 new SplitAction( | ||||
|                     id, | ||||
|                     splitPoints.data.map((ff) => ff.feature.geometry.coordinates), | ||||
|                     { | ||||
|                         theme: state?.layoutToUse?.id, | ||||
|                     } | ||||
|                 ) | ||||
|             ) | ||||
|         }) | ||||
| 
 | ||||
|         saveButton.SetClass("btn btn-primary mr-3") | ||||
|         const disabledSaveButton = new Button("Split", undefined) | ||||
|         disabledSaveButton.SetClass("btn btn-disabled mr-3") | ||||
|         // Only show the save button if there are split points defined
 | ||||
|         const saveToggle = new Toggle( | ||||
|             disabledSaveButton, | ||||
|             saveButton, | ||||
|             splitPoints.map((data) => data.length === 0) | ||||
|         ) | ||||
| 
 | ||||
|         const cancelButton = Translations.t.general.cancel | ||||
|             .Clone() // Not using Button() element to prevent full width button
 | ||||
|             .SetClass("btn btn-secondary mr-3") | ||||
|             .onClick(() => { | ||||
|                 splitPoints.setData([]) | ||||
|                 splitClicked.setData(false) | ||||
|             }) | ||||
| 
 | ||||
|         cancelButton.SetClass("btn btn-secondary block") | ||||
| 
 | ||||
|         const splitTitle = new Title(t.splitTitle) | ||||
| 
 | ||||
|         const mapView = new Combine([ | ||||
|             splitTitle, | ||||
|             miniMap, | ||||
|             new Combine([cancelButton, saveToggle]).SetClass("flex flex-row"), | ||||
|         ]) | ||||
|         mapView.SetClass("question") | ||||
|         const confirm = new Toggle(mapView, splitToggle, splitClicked) | ||||
|         super(t.hasBeenSplit.Clone(), confirm, hasBeenSplit) | ||||
|         this.dialogIsOpened = splitClicked | ||||
|         return miniMap | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -913,6 +913,7 @@ | |||
|         "inviteToSplit": "Split this road in smaller segments. This allows to give different properties to parts of the road.", | ||||
|         "loginToSplit": "You must be logged in to split a road", | ||||
|         "split": "Split", | ||||
|         "splitAgain": "Split this road again", | ||||
|         "splitTitle": "Choose on the map where the properties of this road change" | ||||
|     }, | ||||
|     "translations": { | ||||
|  |  | |||
|  | @ -7594,7 +7594,7 @@ describe("GenerateCache", () => { | |||
|         } | ||||
|         mkdirSync(dir + "np-cache") | ||||
|         initDownloads( | ||||
|             "(nwr%5B%22amenity%22%3D%22toilets%22%5D%3Bnwr%5B%22amenity%22%3D%22parking%22%5D%3Bnwr%5B%22amenity%22%3D%22bench%22%5D%3Bnwr%5B%22id%22%3D%22location_track%22%5D%3Bnwr%5B%22id%22%3D%22gps%22%5D%3Bnwr%5B%22information%22%3D%22board%22%5D%3Bnwr%5B%22leisure%22%3D%22picnic_table%22%5D%3Bnwr%5B%22man_made%22%3D%22watermill%22%5D%3Bnwr%5B%22selected%22%3D%22yes%22%5D%3Bnwr%5B%22user%3Ahome%22%3D%22yes%22%5D%3Bnwr%5B%22user%3Alocation%22%3D%22yes%22%5D%3Bnwr%5B%22leisure%22%3D%22nature_reserve%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22boundary%22%3D%22protected_area%22%5D%5B%22protect_class%22!%3D%2298%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22information%22%3D%22visitor_centre%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22information%22%3D%22office%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22route%22~%22%5E(.*foot.*)%24%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22route%22~%22%5E(.*hiking.*)%24%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22route%22~%22%5E(.*bycicle.*)%24%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22route%22~%22%5E(.*horse.*)%24%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22leisure%22%3D%22bird_hide%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22amenity%22%3D%22drinking_water%22%5D%5B%22man_made%22!%3D%22reservoir_covered%22%5D%5B%22access%22!%3D%22permissive%22%5D%5B%22access%22!%3D%22private%22%5D%3Bnwr%5B%22drinking_water%22%3D%22yes%22%5D%5B%22man_made%22!%3D%22reservoir_covered%22%5D%5B%22access%22!%3D%22permissive%22%5D%5B%22access%22!%3D%22private%22%5D%3B)%3Bout%20body%3Bout%20meta%3B%3E%3Bout%20skel%20qt%3B" | ||||
|             "(nwr%5B%22amenity%22%3D%22toilets%22%5D%3Bnwr%5B%22amenity%22%3D%22parking%22%5D%3Bnwr%5B%22amenity%22%3D%22bench%22%5D%3Bnwr%5B%22id%22%3D%22location_track%22%5D%3Bnwr%5B%22id%22%3D%22gps%22%5D%3Bnwr%5B%22information%22%3D%22board%22%5D%3Bnwr%5B%22leisure%22%3D%22picnic_table%22%5D%3Bnwr%5B%22selected%22%3D%22yes%22%5D%3Bnwr%5B%22user%3Ahome%22%3D%22yes%22%5D%3Bnwr%5B%22user%3Alocation%22%3D%22yes%22%5D%3Bnwr%5B%22leisure%22%3D%22nature_reserve%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22boundary%22%3D%22protected_area%22%5D%5B%22protect_class%22!%3D%2298%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22information%22%3D%22visitor_centre%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22information%22%3D%22office%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22route%22~%22%5E(.*foot.*)%24%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22route%22~%22%5E(.*hiking.*)%24%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22route%22~%22%5E(.*bycicle.*)%24%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22route%22~%22%5E(.*horse.*)%24%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22leisure%22%3D%22bird_hide%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22amenity%22%3D%22drinking_water%22%5D%5B%22man_made%22!%3D%22reservoir_covered%22%5D%5B%22access%22!%3D%22permissive%22%5D%5B%22access%22!%3D%22private%22%5D%3Bnwr%5B%22drinking_water%22%3D%22yes%22%5D%5B%22man_made%22!%3D%22reservoir_covered%22%5D%5B%22access%22!%3D%22permissive%22%5D%5B%22access%22!%3D%22private%22%5D%3B)%3Bout%20body%3Bout%20meta%3B%3E%3Bout%20skel%20qt%3B" | ||||
|         ) | ||||
|         await main([ | ||||
|             "natuurpunt", | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue