2021-07-13 11:26:50 +02:00
import Toggle from "../Input/Toggle"
import Svg from "../../Svg"
import { UIEventSource } from "../../Logic/UIEventSource"
import { SubtleButton } from "../Base/SubtleButton"
import Minimap from "../Base/Minimap"
2021-09-21 02:10:42 +02:00
import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"
2021-09-28 17:30:48 +02:00
import { GeoOperations } from "../../Logic/GeoOperations"
2021-07-13 16:11:57 +02:00
import { LeafletMouseEvent } from "leaflet"
import Combine from "../Base/Combine"
import { Button } from "../Base/Button"
import Translations from "../i18n/Translations"
2021-07-15 20:47:28 +02:00
import SplitAction from "../../Logic/Osm/Actions/SplitAction"
import Title from "../Base/Title"
2021-09-21 02:10:42 +02:00
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
2021-09-28 17:30:48 +02:00
import { BBox } from "../../Logic/BBox"
2021-12-03 01:29:09 +01:00
import * as split_point from "../../assets/layers/split_point/split_point.json"
2022-01-19 20:34:04 +01:00
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import { Changes } from "../../Logic/Osm/Changes"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import { ElementStorage } from "../../Logic/ElementStorage"
import BaseLayer from "../../Models/BaseLayer"
import FilteredLayer from "../../Models/FilteredLayer"
2022-12-24 01:58:52 +01:00
import BaseUIElement from "../BaseUIElement"
import { VariableUiElement } from "../Base/VariableUIElement"
2023-01-04 18:52:49 +01:00
import ScrollableFullScreen from "../Base/ScrollableFullScreen"
2023-01-06 03:37:22 +01:00
import { LoginToggle } from "./LoginButton"
2022-01-18 18:52:42 +01:00
2022-12-24 01:58:52 +01:00
export default class SplitRoadWizard extends Combine {
2021-12-03 01:29:09 +01:00
// @ts-ignore
private static splitLayerStyling = new LayerConfig (
split_point ,
"(BUILTIN) SplitRoadWizard.ts" ,
true
)
2021-07-15 00:26:25 +02:00
2021-09-22 05:02:09 +02:00
public dialogIsOpened : UIEventSource < boolean >
2021-07-13 11:26:50 +02:00
/ * *
* A UI Element used for splitting roads
*
* @param id : The id of the road to remove
2022-01-19 20:34:04 +01:00
* @param state : the state of the application
2021-07-13 11:26:50 +02:00
* /
2022-01-19 20:34:04 +01:00
constructor (
id : string ,
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
2022-01-26 21:40:38 +01:00
allElements : ElementStorage
2023-01-04 18:52:49 +01:00
selectedElement : UIEventSource < any >
2022-01-26 21:40:38 +01:00
}
) {
2021-07-13 16:11:57 +02:00
const t = Translations . t . split
2021-07-13 11:26:50 +02:00
2021-07-15 00:26:25 +02:00
// Contains the points on the road that are selected to split on - contains geojson points with extra properties such as 'location' with the distance along the linestring
2021-07-15 20:47:28 +02:00
const splitPoints = new UIEventSource < { feature : any ; freshness : Date } [ ] > ( [ ] )
const hasBeenSplit = new UIEventSource ( false )
2021-07-13 16:11:57 +02:00
// Toggle variable between show split button and map
2021-07-15 00:26:25 +02:00
const splitClicked = new UIEventSource < boolean > ( false )
2022-12-24 01:58:52 +01:00
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
)
)
2023-01-06 03:37:22 +01:00
const splitToggle = new LoginToggle ( splitButton , t . loginToSplit . Clone ( ) , state )
2022-12-24 01:58:52 +01:00
// 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 ) )
2023-01-04 18:52:49 +01:00
// Close the popup. The contributor has to select a segment again to make sure they continue editing the correct segment; see #1219
ScrollableFullScreen . collapse ( )
2022-12-24 01:58:52 +01:00
} )
saveButton . SetClass ( "btn btn-primary mr-3" )
2023-01-03 01:01:59 +01:00
const disabledSaveButton = new Button ( t . split . Clone ( ) , undefined )
2022-12-24 01:58:52 +01:00
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
2023-01-04 18:52:49 +01:00
const self = this
splitButton . onClick ( ( ) = > {
splitClicked . setData ( true )
self . ScrollIntoView ( )
} )
2022-12-24 01:58:52 +01:00
}
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 {
2021-09-22 05:02:09 +02:00
// Load the road with given id on the minimap
2022-01-19 20:34:04 +01:00
const roadElement = state . allElements . ContainingFeatures . get ( id )
2021-07-13 16:11:57 +02:00
// Minimap on which you can select the points to be splitted
2021-09-22 05:02:09 +02:00
const miniMap = Minimap . createMiniMap ( {
2022-01-19 20:34:04 +01:00
background : state.backgroundLayer ,
2021-09-22 05:02:09 +02:00
allowMoving : true ,
leafletOptions : {
minZoom : 14 ,
} ,
} )
miniMap . SetStyle ( "width: 100%; height: 24rem" ) . SetClass ( "rounded-xl overflow-hidden" )
2021-10-16 02:54:22 +02:00
miniMap . installBounds ( BBox . get ( roadElement ) . pad ( 0.25 ) , false )
2021-07-13 16:11:57 +02:00
// Define how a cut is displayed on the map
2021-10-19 03:00:57 +02:00
2021-07-13 16:11:57 +02:00
// Datalayer displaying the road and the cut points (if any)
2021-09-22 05:02:09 +02:00
new ShowDataMultiLayer ( {
2022-06-05 02:24:14 +02:00
features : StaticFeatureSource.fromGeojson ( [ roadElement ] ) ,
2022-01-19 20:34:04 +01:00
layers : state.filteredLayers ,
2021-09-22 05:02:09 +02:00
leafletMap : miniMap.leafletMap ,
2021-10-15 05:20:02 +02:00
zoomToFeatures : true ,
2022-01-19 20:34:04 +01:00
state ,
2021-09-21 02:10:42 +02:00
} )
2022-01-26 21:40:38 +01:00
2021-12-03 01:29:09 +01:00
new ShowDataLayer ( {
2022-06-05 02:24:14 +02:00
features : new StaticFeatureSource ( splitPoints ) ,
2021-12-03 01:29:09 +01:00
leafletMap : miniMap.leafletMap ,
zoomToFeatures : false ,
layerToShow : SplitRoadWizard.splitLayerStyling ,
2022-01-19 20:34:04 +01:00
state ,
2021-12-03 01:29:09 +01:00
} )
2021-07-13 16:11:57 +02:00
/ * *
* Handles a click on the overleaf map .
* Finds the closest intersection with the road and adds a point there , ready to confirm the cut .
* @param coordinates Clicked location , [ lon , lat ]
* /
function onMapClick ( coordinates ) {
2021-09-22 05:02:09 +02:00
// First, we check if there is another, already existing point nearby
const points = splitPoints . data
. map ( ( f , i ) = > [ f . feature , i ] )
2021-11-12 18:39:38 +01:00
. filter (
( p ) = > GeoOperations . distanceBetween ( p [ 0 ] . geometry . coordinates , coordinates ) < 5
2022-09-08 21:40:48 +02:00
)
2021-09-22 05:02:09 +02:00
. map ( ( p ) = > p [ 1 ] )
2021-11-09 01:49:07 +01:00
. sort ( ( a , b ) = > a - b )
2022-02-22 14:13:41 +01:00
. reverse ( /*Copy/derived list, inplace reverse is fine*/ )
2021-09-22 05:02:09 +02:00
if ( points . length > 0 ) {
for ( const point of points ) {
splitPoints . data . splice ( point , 1 )
}
splitPoints . ping ( )
return
}
2021-07-13 16:11:57 +02:00
// Get nearest point on the road
2022-11-08 14:37:48 +01:00
const pointOnRoad = GeoOperations . nearestPoint ( < any > roadElement , coordinates ) // pointOnRoad is a geojson
2021-07-13 16:11:57 +02:00
// Update point properties to let it match the layer
2021-12-03 01:29:09 +01:00
pointOnRoad . properties [ "_split_point" ] = "yes"
2021-07-15 20:47:28 +02:00
2021-07-15 00:26:25 +02:00
// Add it to the list of all points and notify observers
splitPoints . data . push ( { feature : pointOnRoad , freshness : new Date ( ) } ) // show the point on the data layer
splitPoints . ping ( ) // not updated using .setData, so manually ping observers
2021-07-13 16:11:57 +02:00
}
2021-07-13 11:26:50 +02:00
2021-07-13 16:11:57 +02:00
// When clicked, pass clicked location coordinates to onMapClick function
miniMap . leafletMap . addCallbackAndRunD ( ( leafletMap ) = >
leafletMap . on ( "click" , ( mouseEvent : LeafletMouseEvent ) = > {
onMapClick ( [ mouseEvent . latlng . lng , mouseEvent . latlng . lat ] )
} )
2022-09-08 21:40:48 +02:00
)
2022-12-24 01:58:52 +01:00
return miniMap
2021-07-13 11:26:50 +02:00
}
}