forked from MapComplete/MapComplete
		
	More work on splitting roads, WIP; refactoring tests
This commit is contained in:
		
							parent
							
								
									e374bb355c
								
							
						
					
					
						commit
						1f93923820
					
				
					 62 changed files with 1163 additions and 823 deletions
				
			
		|  | @ -16,6 +16,11 @@ export interface MinimapOptions { | |||
|     lastClickLocation?: UIEventSource<{ lat: number, lon: number }> | ||||
| } | ||||
| 
 | ||||
| export interface MinimapObj { | ||||
|     readonly leafletMap: UIEventSource<any>,  | ||||
|     installBounds(factor: number | BBox, showRange?: boolean) : void | ||||
| } | ||||
| 
 | ||||
| export default class Minimap { | ||||
|     /** | ||||
|      * A stub implementation. The actual implementation is injected later on, but only in the browser. | ||||
|  | @ -25,6 +30,6 @@ export default class Minimap { | |||
|     /** | ||||
|      * Construct a minimap | ||||
|      */ | ||||
|     public static createMiniMap: (options: MinimapOptions) => BaseUIElement & { readonly leafletMap: UIEventSource<any> } | ||||
|     public static createMiniMap: (options: MinimapOptions) => (BaseUIElement & MinimapObj) | ||||
| 
 | ||||
| } | ||||
|  | @ -7,9 +7,9 @@ import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; | |||
| import {BBox} from "../../Logic/GeoOperations"; | ||||
| import * as L from "leaflet"; | ||||
| import {Map} from "leaflet"; | ||||
| import Minimap, {MinimapOptions} from "./Minimap"; | ||||
| import Minimap, {MinimapObj, MinimapOptions} from "./Minimap"; | ||||
| 
 | ||||
| export default class MinimapImplementation extends BaseUIElement { | ||||
| export default class MinimapImplementation extends BaseUIElement implements MinimapObj { | ||||
|     private static _nextId = 0; | ||||
|     public readonly leafletMap: UIEventSource<Map> | ||||
|     private readonly _id: string; | ||||
|  | @ -44,6 +44,65 @@ export default class MinimapImplementation extends BaseUIElement { | |||
|         Minimap.createMiniMap = options => new MinimapImplementation(options) | ||||
|     } | ||||
| 
 | ||||
|     public installBounds(factor: number | BBox, showRange?: boolean) { | ||||
|         this.leafletMap.addCallbackD(leaflet => { | ||||
|             console.log("Installing max bounds") | ||||
| 
 | ||||
|             let bounds; | ||||
|             if (typeof factor === "number") { | ||||
|                 bounds = leaflet.getBounds() | ||||
|                 leaflet.setMaxBounds(bounds.pad(factor)) | ||||
|             }else{ | ||||
|                 // @ts-ignore
 | ||||
|                 leaflet.setMaxBounds(factor.toLeaflet()) | ||||
|                 bounds = leaflet.getBounds() | ||||
|             } | ||||
| 
 | ||||
|             if (showRange) { | ||||
|                 const data = { | ||||
|                     type: "FeatureCollection", | ||||
|                     features: [{ | ||||
|                         "type": "Feature", | ||||
|                         "geometry": { | ||||
|                             "type": "LineString", | ||||
|                             "coordinates": [ | ||||
|                                 [ | ||||
|                                     bounds.getEast(), | ||||
|                                     bounds.getNorth() | ||||
|                                 ], | ||||
|                                 [ | ||||
|                                     bounds.getWest(), | ||||
|                                     bounds.getNorth() | ||||
|                                 ], | ||||
|                                 [ | ||||
|                                     bounds.getWest(), | ||||
|                                     bounds.getSouth() | ||||
|                                 ], | ||||
| 
 | ||||
|                                 [ | ||||
|                                     bounds.getEast(), | ||||
|                                     bounds.getSouth() | ||||
|                                 ], | ||||
|                                 [ | ||||
|                                     bounds.getEast(), | ||||
|                                     bounds.getNorth() | ||||
|                                 ] | ||||
|                             ] | ||||
|                         } | ||||
|                     }] | ||||
|                 } | ||||
|                 // @ts-ignore
 | ||||
|                 L.geoJSON(data, { | ||||
|                     style: { | ||||
|                         color: "#f00", | ||||
|                         weight: 2, | ||||
|                         opacity: 0.4 | ||||
|                     } | ||||
|                 }).addTo(leaflet) | ||||
|             } | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     protected InnerConstructElement(): HTMLElement { | ||||
|         const div = document.createElement("div") | ||||
|         div.id = this._id; | ||||
|  |  | |||
|  | @ -65,7 +65,8 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen { | |||
|         const tabsWithAboutMc = [...FullWelcomePaneWithTabs.ConstructBaseTabs(layoutToUse, isShown)] | ||||
| 
 | ||||
|         const now = new Date() | ||||
|         const date = now.getFullYear()+"-"+Utils.TwoDigits(now.getMonth()+1)+"-"+Utils.TwoDigits(now.getDate()) | ||||
|         const lastWeek = new Date(now.getDate() - 7 * 24 * 60 * 60 * 1000) | ||||
|         const date = lastWeek.getFullYear()+"-"+Utils.TwoDigits(lastWeek.getMonth()+1)+"-"+Utils.TwoDigits(lastWeek.getDate()) | ||||
|         const osmcha_link = `https://osmcha.org/?filters=%7B%22date__gte%22%3A%5B%7B%22label%22%3A%22${date}%22%2C%22value%22%3A%222021-01-01%22%7D%5D%2C%22editor%22%3A%5B%7B%22label%22%3A%22mapcomplete%22%2C%22value%22%3A%22mapcomplete%22%7D%5D%7D` | ||||
|          | ||||
|         tabsWithAboutMc.push({ | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ import {OsmObject, OsmWay} from "../../Logic/Osm/OsmObject"; | |||
| import PresetConfig from "../../Models/ThemeConfig/PresetConfig"; | ||||
| import FilteredLayer from "../../Models/FilteredLayer"; | ||||
| import {And} from "../../Logic/Tags/And"; | ||||
| import {BBox} from "../../Logic/GeoOperations"; | ||||
| 
 | ||||
| /* | ||||
| * The SimpleAddUI is a single panel, which can have multiple states: | ||||
|  | @ -39,8 +40,6 @@ interface PresetInfo extends PresetConfig { | |||
| export default class SimpleAddUI extends Toggle { | ||||
| 
 | ||||
|     constructor(isShown: UIEventSource<boolean>) { | ||||
| 
 | ||||
| 
 | ||||
|         const loginButton = new SubtleButton(Svg.osm_logo_ui(), Translations.t.general.add.pleaseLogin.Clone()) | ||||
|             .onClick(() => State.state.osmConnection.AttemptLogin()); | ||||
|         const readYourMessages = new Combine([ | ||||
|  | @ -52,7 +51,8 @@ export default class SimpleAddUI extends Toggle { | |||
| 
 | ||||
|         const selectedPreset = new UIEventSource<PresetInfo>(undefined); | ||||
|         isShown.addCallback(_ => selectedPreset.setData(undefined)) // Clear preset selection when the UI is closed/opened
 | ||||
| 
 | ||||
|         State.state.LastClickLocation.addCallback( _ => selectedPreset.setData(undefined)) | ||||
|          | ||||
|         const presetsOverview = SimpleAddUI.CreateAllPresetsPanel(selectedPreset) | ||||
| 
 | ||||
| 
 | ||||
|  | @ -82,11 +82,7 @@ export default class SimpleAddUI extends Toggle { | |||
|                                     return true; | ||||
|                                 }) | ||||
|                             } | ||||
| 
 | ||||
| 
 | ||||
|                         }, | ||||
| 
 | ||||
| 
 | ||||
|                         () => { | ||||
|                             selectedPreset.setData(undefined) | ||||
|                         }) | ||||
|  | @ -98,9 +94,9 @@ export default class SimpleAddUI extends Toggle { | |||
|             new Toggle( | ||||
|                 new Toggle( | ||||
|                     new Toggle( | ||||
|                         Translations.t.general.add.stillLoading.Clone().SetClass("alert"), | ||||
|                         addUi, | ||||
|                         State.state.featurePipeline.runningQuery | ||||
|                         Translations.t.general.add.stillLoading.Clone().SetClass("alert"), | ||||
|                         State.state.featurePipeline.somethingLoaded | ||||
|                     ), | ||||
|                     Translations.t.general.add.zoomInFurther.Clone().SetClass("alert"), | ||||
|                     State.state.locationControl.map(loc => loc.zoom >= Constants.userJourney.minZoomLevelToAddNewPoints) | ||||
|  | @ -126,6 +122,7 @@ export default class SimpleAddUI extends Toggle { | |||
|         let location = State.state.LastClickLocation; | ||||
|         let preciseInput: LocationInput = undefined | ||||
|         if (preset.preciseInput !== undefined) { | ||||
|             // We uncouple the event source
 | ||||
|             const locationSrc = new UIEventSource({ | ||||
|                 lat: location.data.lat, | ||||
|                 lon: location.data.lon, | ||||
|  | @ -137,24 +134,48 @@ export default class SimpleAddUI extends Toggle { | |||
|                 backgroundLayer = AvailableBaseLayers.SelectBestLayerAccordingTo(locationSrc, new UIEventSource<string | string[]>(preset.preciseInput.preferredBackground)) | ||||
|             } | ||||
| 
 | ||||
|             let features: UIEventSource<{ feature: any }[]> = undefined | ||||
|             let snapToFeatures: UIEventSource<{ feature: any }[]> = undefined | ||||
|             let mapBounds: UIEventSource<BBox> = undefined | ||||
|             if (preset.preciseInput.snapToLayers) { | ||||
|                 // We have to snap to certain layers.
 | ||||
|                 // Lets fetch tehm
 | ||||
|                 const asSet = new Set(preset.preciseInput.snapToLayers) | ||||
|                 features = State.state.featurePipeline.features.map(f => f.filter(feat => asSet.has(feat.feature._matching_layer_id))) | ||||
|                 snapToFeatures = new UIEventSource<{ feature: any }[]>([]) | ||||
|                 mapBounds = new UIEventSource<BBox>(undefined) | ||||
|             } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|             const tags = TagUtils.KVtoProperties(preset.tags ?? []); | ||||
|             preciseInput = new LocationInput({ | ||||
|                 mapBackground: backgroundLayer, | ||||
|                 centerLocation: locationSrc, | ||||
|                 snapTo: features, | ||||
|                 snapTo: snapToFeatures, | ||||
|                 snappedPointTags: tags, | ||||
|                 maxSnapDistance: preset.preciseInput.maxSnapDistance | ||||
| 
 | ||||
|                 maxSnapDistance: preset.preciseInput.maxSnapDistance, | ||||
|                 bounds: mapBounds | ||||
|             }) | ||||
|             preciseInput.SetClass("h-32 rounded-xl overflow-hidden border border-gray").SetStyle("height: 12rem;") | ||||
| 
 | ||||
| 
 | ||||
|             if (preset.preciseInput.snapToLayers) { | ||||
|                 // We have to snap to certain layers.
 | ||||
|                 // Lets fetch them
 | ||||
|                  | ||||
|                 let loadedBbox : BBox= undefined | ||||
|                 mapBounds?.addCallbackAndRunD(bbox => { | ||||
|                     if(loadedBbox !== undefined && bbox.isContainedIn(loadedBbox)){ | ||||
|                         // All is already there
 | ||||
|                         // return;
 | ||||
|                     } | ||||
| 
 | ||||
|                     bbox = bbox.pad(2); | ||||
|                     loadedBbox = bbox; | ||||
|                     const allFeatures: {feature: any}[] = [] | ||||
|                     preset.preciseInput.snapToLayers.forEach(layerId => { | ||||
|                        State.state.featurePipeline.GetFeaturesWithin(layerId, bbox).forEach(feats => allFeatures.push(...feats.map(f => ({feature :f})))) | ||||
|                     }) | ||||
|                     snapToFeatures.setData(allFeatures) | ||||
|                 }) | ||||
|             } | ||||
|              | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,7 +7,7 @@ import Combine from "../Base/Combine"; | |||
| import Svg from "../../Svg"; | ||||
| import State from "../../State"; | ||||
| import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; | ||||
| import {GeoOperations} from "../../Logic/GeoOperations"; | ||||
| import {BBox, GeoOperations} from "../../Logic/GeoOperations"; | ||||
| import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"; | ||||
| import * as L from "leaflet"; | ||||
| import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer"; | ||||
|  | @ -38,6 +38,8 @@ export default class LocationInput extends InputElement<Loc> { | |||
|     private readonly _snappedPoint: UIEventSource<any> | ||||
|     private readonly _maxSnapDistance: number | ||||
|     private readonly _snappedPointTags: any; | ||||
|     private readonly _bounds: UIEventSource<BBox>; | ||||
|     public readonly _matching_layer: UIEventSource<LayerConfig>; | ||||
| 
 | ||||
|     constructor(options: { | ||||
|         mapBackground?: UIEventSource<BaseLayer>, | ||||
|  | @ -46,32 +48,33 @@ export default class LocationInput extends InputElement<Loc> { | |||
|         snappedPointTags?: any, | ||||
|         requiresSnapping?: boolean, | ||||
|         centerLocation: UIEventSource<Loc>, | ||||
|         bounds?: UIEventSource<BBox> | ||||
|     }) { | ||||
|         super(); | ||||
|         this._snapTo = options.snapTo?.map(features => features?.filter(feat => feat.feature.geometry.type !== "Point")) | ||||
|         this._maxSnapDistance = options.maxSnapDistance | ||||
|         this._centerLocation = options.centerLocation; | ||||
|         this._snappedPointTags = options.snappedPointTags | ||||
|         this._bounds = options.bounds; | ||||
|         if (this._snapTo === undefined) { | ||||
|             this._value = this._centerLocation; | ||||
|         } else { | ||||
|             const self = this; | ||||
| 
 | ||||
|             let matching_layer: UIEventSource<string> | ||||
| 
 | ||||
|             if (self._snappedPointTags !== undefined) { | ||||
|                 matching_layer = State.state.layoutToUse.map(layout => { | ||||
|                 this._matching_layer = State.state.layoutToUse.map(layout => { | ||||
| 
 | ||||
|                     for (const layer of layout.layers) { | ||||
|                         if (layer.source.osmTags.matchesProperties(self._snappedPointTags)) { | ||||
|                             return layer.id | ||||
|                             return layer | ||||
|                         } | ||||
|                     } | ||||
|                     console.error("No matching layer found for tags ", self._snappedPointTags) | ||||
|                     return "matchpoint" | ||||
|                     return LocationInput.matchLayer | ||||
|                 }) | ||||
|             } else { | ||||
|                 matching_layer = new UIEventSource<string>("matchpoint") | ||||
|                this._matching_layer = new UIEventSource<LayerConfig>(LocationInput.matchLayer) | ||||
|             } | ||||
| 
 | ||||
|             this._snappedPoint = options.centerLocation.map(loc => { | ||||
|  | @ -83,7 +86,7 @@ export default class LocationInput extends InputElement<Loc> { | |||
| 
 | ||||
|                 let min = undefined; | ||||
|                 let matchedWay = undefined; | ||||
|                 for (const feature of self._snapTo.data) { | ||||
|                 for (const feature of self._snapTo.data ?? []) { | ||||
|                     const nearestPointOnLine = GeoOperations.nearestPoint(feature.feature, [loc.lon, loc.lat]) | ||||
|                     if (min === undefined) { | ||||
|                         min = nearestPointOnLine | ||||
|  | @ -98,19 +101,17 @@ export default class LocationInput extends InputElement<Loc> { | |||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 if (min.properties.dist * 1000 > self._maxSnapDistance) { | ||||
|                 if (min === undefined || min.properties.dist * 1000 > self._maxSnapDistance) { | ||||
|                     if (options.requiresSnapping) { | ||||
|                         return undefined | ||||
|                     } else { | ||||
|                         return { | ||||
|                             "type": "Feature", | ||||
|                             "_matching_layer_id": matching_layer.data, | ||||
|                             "properties": options.snappedPointTags ?? min.properties, | ||||
|                             "geometry": {"type": "Point", "coordinates": [loc.lon, loc.lat]} | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 min._matching_layer_id = matching_layer?.data ?? "matchpoint" | ||||
|                 min.properties = options.snappedPointTags ?? min.properties | ||||
|                 self.snappedOnto.setData(matchedWay) | ||||
|                 return min | ||||
|  | @ -144,84 +145,40 @@ export default class LocationInput extends InputElement<Loc> { | |||
|                     location: this._centerLocation, | ||||
|                     background: this.mapBackground, | ||||
|                     attribution: this.mapBackground !== State.state.backgroundLayer, | ||||
|                     lastClickLocation: clickLocation | ||||
|                     lastClickLocation: clickLocation, | ||||
|                     bounds: this._bounds | ||||
|                 } | ||||
|             ) | ||||
|             clickLocation.addCallbackAndRunD(location => this._centerLocation.setData(location)) | ||||
|             map.leafletMap.addCallbackAndRunD(leaflet => { | ||||
|                 const bounds = leaflet.getBounds() | ||||
|                 leaflet.setMaxBounds(bounds.pad(0.15)) | ||||
|                 const data = { | ||||
|                     type: "FeatureCollection", | ||||
|                     features: [{ | ||||
|                         "type": "Feature", | ||||
|                         "geometry": { | ||||
|                             "type": "LineString", | ||||
|                             "coordinates": [ | ||||
|                                 [ | ||||
|                                     bounds.getEast(), | ||||
|                                     bounds.getNorth() | ||||
|                                 ], | ||||
|                                 [ | ||||
|                                     bounds.getWest(), | ||||
|                                     bounds.getNorth() | ||||
|                                 ], | ||||
|                                 [ | ||||
|                                     bounds.getWest(), | ||||
|                                     bounds.getSouth() | ||||
|                                 ], | ||||
| 
 | ||||
|                                 [ | ||||
|                                     bounds.getEast(), | ||||
|                                     bounds.getSouth() | ||||
|                                 ], | ||||
|                                 [ | ||||
|                                     bounds.getEast(), | ||||
|                                     bounds.getNorth() | ||||
|                                 ] | ||||
|                             ] | ||||
|                         } | ||||
|                     }] | ||||
|                 } | ||||
|                 // @ts-ignore
 | ||||
|                 L.geoJSON(data, { | ||||
|                     style: { | ||||
|                         color: "#f00", | ||||
|                         weight: 2, | ||||
|                         opacity: 0.4 | ||||
|                     } | ||||
|                 }).addTo(leaflet) | ||||
|             }) | ||||
|             map.installBounds(0.15, true); | ||||
| 
 | ||||
|             if (this._snapTo !== undefined) { | ||||
| 
 | ||||
|                  | ||||
|                 // Show the lines to snap to
 | ||||
|                 new ShowDataMultiLayer({ | ||||
|                         features: new StaticFeatureSource(this._snapTo, true), | ||||
|                         enablePopups: false, | ||||
|                         zoomToFeatures: false, | ||||
|                         leafletMap: map.leafletMap, | ||||
|                         layers: State.state.filteredLayers | ||||
|                     } | ||||
|                 ) | ||||
|                 // Show the central point
 | ||||
|                 const matchPoint = this._snappedPoint.map(loc => { | ||||
|                     if (loc === undefined) { | ||||
|                         return [] | ||||
|                     } | ||||
|                     return [{feature: loc}]; | ||||
|                 }) | ||||
|                 if (this._snapTo) { | ||||
|                     if (this._snappedPointTags === undefined) { | ||||
|                         // No special tags - we show a default crosshair
 | ||||
|                         new ShowDataLayer({ | ||||
|                             features: new StaticFeatureSource(matchPoint), | ||||
|                             enablePopups: false, | ||||
|                             zoomToFeatures: false, | ||||
|                             leafletMap: map.leafletMap, | ||||
|                             layerToShow: LocationInput.matchLayer | ||||
|                         }) | ||||
|                     }else{ | ||||
|                         new ShowDataMultiLayer({ | ||||
|                                 features: new StaticFeatureSource(matchPoint), | ||||
|                                 enablePopups: false, | ||||
|                                 zoomToFeatures: false, | ||||
|                                 leafletMap: map.leafletMap, | ||||
|                                 layers: State.state.filteredLayers | ||||
|                             } | ||||
|                         ) | ||||
|                     } | ||||
|                 } | ||||
|                     new ShowDataLayer({ | ||||
|                         features: new StaticFeatureSource(matchPoint, true), | ||||
|                         enablePopups: false, | ||||
|                         zoomToFeatures: false, | ||||
|                         leafletMap: map.leafletMap, | ||||
|                         layerToShow: this._matching_layer.data | ||||
|                     }) | ||||
|                      | ||||
|             } | ||||
| 
 | ||||
|             this.mapBackground.map(layer => { | ||||
|  |  | |||
|  | @ -130,7 +130,7 @@ export default class FeatureInfoBox extends ScrollableFullScreen { | |||
|                 if (!userbadge) { | ||||
|                     return undefined | ||||
|                 } | ||||
|                 return new Combine(editElements) | ||||
|                 return new Combine(editElements).SetClass("flex flex-col") | ||||
|             } | ||||
|         )) | ||||
|         renderings.push(editors) | ||||
|  |  | |||
|  | @ -5,13 +5,12 @@ import {SubtleButton} from "../Base/SubtleButton"; | |||
| import Minimap from "../Base/Minimap"; | ||||
| import State from "../../State"; | ||||
| import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"; | ||||
| import {GeoOperations} from "../../Logic/GeoOperations"; | ||||
| import {BBox, GeoOperations} from "../../Logic/GeoOperations"; | ||||
| import {LeafletMouseEvent} from "leaflet"; | ||||
| import Combine from "../Base/Combine"; | ||||
| import {Button} from "../Base/Button"; | ||||
| import Translations from "../i18n/Translations"; | ||||
| import SplitAction from "../../Logic/Osm/Actions/SplitAction"; | ||||
| import {OsmObject, OsmWay} from "../../Logic/Osm/OsmObject"; | ||||
| import Title from "../Base/Title"; | ||||
| import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"; | ||||
| import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer"; | ||||
|  | @ -21,9 +20,12 @@ export default class SplitRoadWizard extends Toggle { | |||
|     private static splitLayerStyling = new LayerConfig({ | ||||
|         id: "splitpositions", | ||||
|         source: {osmTags: "_cutposition=yes"}, | ||||
|         icon: "./assets/svg/plus.svg" | ||||
|         icon: {render: "circle:white;./assets/svg/scissors.svg"}, | ||||
|         iconSize: {render: "30,30,center"}, | ||||
|     }, "(BUILTIN) SplitRoadWizard.ts", true) | ||||
| 
 | ||||
|     public dialogIsOpened: UIEventSource<boolean> | ||||
| 
 | ||||
|     /** | ||||
|      * A UI Element used for splitting roads | ||||
|      * | ||||
|  | @ -40,30 +42,40 @@ export default class SplitRoadWizard extends Toggle { | |||
| 
 | ||||
|         // Toggle variable between show split button and map
 | ||||
|         const splitClicked = new UIEventSource<boolean>(false); | ||||
|         // Load the road with given id on the minimap
 | ||||
|         const roadElement = State.state.allElements.ContainingFeatures.get(id) | ||||
| 
 | ||||
|         // Minimap on which you can select the points to be splitted
 | ||||
|         const miniMap = Minimap.createMiniMap({background: State.state.backgroundLayer, allowMoving: false}); | ||||
|         miniMap.SetStyle("width: 100%; height: 24rem;"); | ||||
|         const miniMap = Minimap.createMiniMap( | ||||
|             { | ||||
|                 background: State.state.backgroundLayer, | ||||
|                 allowMoving: true, | ||||
|                 leafletOptions: { | ||||
|                     minZoom: 14 | ||||
|                 } | ||||
|             }); | ||||
|         miniMap.SetStyle("width: 100%; height: 24rem") | ||||
|             .SetClass("rounded-xl overflow-hidden"); | ||||
| 
 | ||||
|         miniMap.installBounds(BBox.get(roadElement)) | ||||
| 
 | ||||
|         // Define how a cut is displayed on the map
 | ||||
| 
 | ||||
|         // Load the road with given id on the minimap
 | ||||
|         const roadElement = State.state.allElements.ContainingFeatures.get(id) | ||||
|         const roadEventSource = new UIEventSource([{feature: roadElement, freshness: new Date()}]); | ||||
|         // Datalayer displaying the road and the cut points (if any)
 | ||||
|         new ShowDataMultiLayer({ | ||||
|             features: new StaticFeatureSource(roadEventSource, true), | ||||
|             layers: State.state.filteredLayers, | ||||
|             leafletMap: miniMap.leafletMap, | ||||
|             enablePopups: false, | ||||
|             zoomToFeatures: true | ||||
|         })  | ||||
|         new ShowDataLayer({ | ||||
|             features: new StaticFeatureSource(splitPoints, true), | ||||
|             leafletMap: miniMap.leafletMap, | ||||
|             zoomToFeatures: false, | ||||
|             enablePopups: false, | ||||
|             layerToShow:  SplitRoadWizard.splitLayerStyling | ||||
|             layerToShow: SplitRoadWizard.splitLayerStyling | ||||
|         }) | ||||
| 
 | ||||
|         new ShowDataMultiLayer({ | ||||
|             features: new StaticFeatureSource([roadElement]), | ||||
|             layers: State.state.filteredLayers, | ||||
|             leafletMap: miniMap.leafletMap, | ||||
|             enablePopups: false, | ||||
|             zoomToFeatures: true | ||||
|         }) | ||||
| 
 | ||||
|         /** | ||||
|  | @ -72,12 +84,25 @@ export default class SplitRoadWizard extends Toggle { | |||
|          * @param coordinates Clicked location, [lon, lat] | ||||
|          */ | ||||
|         function onMapClick(coordinates) { | ||||
|             // First, we check if there is another, already existing point nearby
 | ||||
|             const points = splitPoints.data.map((f, i) => [f.feature, i]) | ||||
|                 .filter(p => GeoOperations.distanceBetween(p[0].geometry.coordinates, coordinates) * 1000 < 5) | ||||
|                 .map(p => p[1]) | ||||
|                 .sort() | ||||
|                 .reverse() | ||||
|             if (points.length > 0) { | ||||
|                 for (const point of points) { | ||||
|                     splitPoints.data.splice(point, 1) | ||||
|                 } | ||||
|                 splitPoints.ping() | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             // Get nearest point on the road
 | ||||
|             const pointOnRoad = GeoOperations.nearestPoint(roadElement, coordinates); // pointOnRoad is a geojson
 | ||||
| 
 | ||||
|             // Update point properties to let it match the layer
 | ||||
|             pointOnRoad.properties._cutposition = "yes"; | ||||
|             pointOnRoad["_matching_layer_id"] = "splitpositions"; | ||||
| 
 | ||||
|             // let the state remember the point, to be able to retrieve it later by id
 | ||||
|             State.state.allElements.addOrGetElement(pointOnRoad); | ||||
|  | @ -94,7 +119,7 @@ export default class SplitRoadWizard extends Toggle { | |||
|             })) | ||||
| 
 | ||||
|         // Toggle between splitmap
 | ||||
|         const splitButton = new SubtleButton(Svg.scissors_ui(), t.inviteToSplit.Clone()); | ||||
|         const splitButton = new SubtleButton(Svg.scissors_ui(), t.inviteToSplit.Clone().SetClass("text-lg font-bold")); | ||||
|         splitButton.onClick( | ||||
|             () => { | ||||
|                 splitClicked.setData(true) | ||||
|  | @ -110,27 +135,9 @@ export default class SplitRoadWizard extends Toggle { | |||
|         // Save button
 | ||||
|         const saveButton = new Button(t.split.Clone(), () => { | ||||
|             hasBeenSplit.setData(true) | ||||
|             const way = OsmObject.DownloadObject(id) | ||||
|             const partOfSrc = OsmObject.DownloadReferencingRelations(id); | ||||
|             let hasRun = false | ||||
|             way.map(way => { | ||||
|                 const partOf = partOfSrc.data | ||||
|                 if (way === undefined || partOf === undefined) { | ||||
|                     return; | ||||
|                 } | ||||
|                 if (hasRun) { | ||||
|                     return | ||||
|                 } | ||||
|                 hasRun = true | ||||
|                 const splitAction = new SplitAction( | ||||
|                     <OsmWay>way, way.asGeoJson(), partOf, splitPoints.data.map(ff => ff.feature) | ||||
|                 ) | ||||
|                 State.state.changes.applyAction(splitAction) | ||||
|             State.state.changes.applyAction(new SplitAction(id, splitPoints.data.map(ff => ff.feature.geometry.coordinates))) | ||||
|         }) | ||||
| 
 | ||||
|             }, [partOfSrc]) | ||||
| 
 | ||||
| 
 | ||||
|         }); | ||||
|         saveButton.SetClass("btn btn-primary mr-3"); | ||||
|         const disabledSaveButton = new Button("Split", undefined); | ||||
|         disabledSaveButton.SetClass("btn btn-disabled mr-3"); | ||||
|  | @ -152,5 +159,6 @@ export default class SplitRoadWizard extends Toggle { | |||
|         mapView.SetClass("question") | ||||
|         const confirm = new Toggle(mapView, splitToggle, splitClicked); | ||||
|         super(t.hasBeenSplit.Clone(), confirm, hasBeenSplit) | ||||
|         this.dialogIsOpened = splitClicked | ||||
|     } | ||||
| } | ||||
|  | @ -37,8 +37,8 @@ export default class ShowDataLayer { | |||
|         this._layerToShow = options.layerToShow; | ||||
|         const self = this; | ||||
| 
 | ||||
|         features.addCallback(() => self.update(options)); | ||||
|         options.leafletMap.addCallback(() => self.update(options)); | ||||
|         features.addCallback(_ => self.update(options)); | ||||
|         options.leafletMap.addCallback(_ => self.update(options)); | ||||
|         this.update(options); | ||||
| 
 | ||||
| 
 | ||||
|  | @ -83,13 +83,17 @@ export default class ShowDataLayer { | |||
|             mp.removeLayer(this.geoLayer); | ||||
|         } | ||||
| 
 | ||||
|         this.geoLayer= this.CreateGeojsonLayer() | ||||
|         const allFeats = this._features.data; | ||||
|         this.geoLayer = this.CreateGeojsonLayer(); | ||||
|         for (const feat of allFeats) { | ||||
|             if (feat === undefined) { | ||||
|                 continue | ||||
|             } | ||||
|             this.geoLayer.addData(feat); | ||||
|             try{ | ||||
|                 this.geoLayer.addData(feat); | ||||
|             }catch(e){ | ||||
|                 console.error("Could not add ", feat, "to the geojson layer in leaflet") | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         mp.addLayer(this.geoLayer) | ||||
|  | @ -122,7 +126,8 @@ export default class ShowDataLayer { | |||
|         } | ||||
| 
 | ||||
|         const tagSource = feature.properties.id === undefined ? new UIEventSource<any>(feature.properties) : State.state.allElements.getEventSourceById(feature.properties.id) | ||||
|         const style = layer.GenerateLeafletStyle(tagSource, !(layer.title === undefined && (layer.tagRenderings ?? []).length === 0)); | ||||
|         const clickable = !(layer.title === undefined && (layer.tagRenderings ?? []).length === 0) | ||||
|         const style = layer.GenerateLeafletStyle(tagSource, clickable); | ||||
|         const baseElement = style.icon.html; | ||||
|         if (!this._enablePopups) { | ||||
|             baseElement.SetStyle("cursor: initial !important") | ||||
|  | @ -132,7 +137,7 @@ export default class ShowDataLayer { | |||
|                 html: baseElement.ConstructElement(), | ||||
|                 className: style.icon.className, | ||||
|                 iconAnchor: style.icon.iconAnchor, | ||||
|                 iconUrl: style.icon.iconUrl, | ||||
|                 iconUrl: style.icon.iconUrl ?? "./assets/svg/bug.svg", | ||||
|                 popupAnchor: style.icon.popupAnchor, | ||||
|                 iconSize: style.icon.iconSize | ||||
|             }) | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue