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" ;
import State from "../../State" ;
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-07-13 11:26:50 +02:00
export default class SplitRoadWizard extends Toggle {
2021-09-21 02:10:42 +02:00
private static splitLayerStyling = new LayerConfig ( {
id : "splitpositions" ,
source : { osmTags : "_cutposition=yes" } ,
2021-09-22 05:02:09 +02:00
icon : { render : "circle:white;./assets/svg/scissors.svg" } ,
iconSize : { render : "30,30,center" } ,
2021-09-21 02:10:42 +02:00
} , "(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
* /
constructor ( id : string ) {
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 ) ;
2021-09-22 05:02:09 +02:00
// Load the road with given id on the minimap
const roadElement = State . 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 (
{
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 ) )
2021-07-13 16:11:57 +02:00
// Define how a cut is displayed on the map
// Datalayer displaying the road and the cut points (if any)
2021-09-21 02:10:42 +02:00
new ShowDataLayer ( {
features : new StaticFeatureSource ( splitPoints , true ) ,
leafletMap : miniMap.leafletMap ,
zoomToFeatures : false ,
enablePopups : false ,
2021-09-22 05:02:09 +02:00
layerToShow : SplitRoadWizard.splitLayerStyling
} )
new ShowDataMultiLayer ( {
2021-09-26 17:36:39 +02:00
features : new StaticFeatureSource ( [ roadElement ] , false ) ,
2021-09-22 05:02:09 +02:00
layers : State.state.filteredLayers ,
leafletMap : miniMap.leafletMap ,
enablePopups : false ,
zoomToFeatures : true
2021-09-21 02:10:42 +02: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 ] )
. 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 ;
}
2021-07-13 16:11:57 +02:00
// 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" ;
// let the state remember the point, to be able to retrieve it later by id
State . state . allElements . addOrGetElement ( pointOnRoad ) ;
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 ] )
} ) )
// Toggle between splitmap
2021-09-22 05:02:09 +02:00
const splitButton = new SubtleButton ( Svg . scissors_ui ( ) , t . inviteToSplit . Clone ( ) . SetClass ( "text-lg font-bold" ) ) ;
2021-07-13 11:26:50 +02:00
splitButton . onClick (
( ) = > {
splitClicked . setData ( true )
}
)
2021-07-13 16:11:57 +02:00
// Only show the splitButton if logged in, else show login prompt
2021-07-19 11:47:17 +02:00
const loginBtn = t . loginToSplit . Clone ( )
. onClick ( ( ) = > State . state . osmConnection . AttemptLogin ( ) )
. SetClass ( "login-button-friendly" ) ;
const splitToggle = new Toggle ( splitButton , loginBtn , State . state . osmConnection . isLoggedIn )
2021-07-13 11:26:50 +02:00
2021-07-13 16:11:57 +02:00
// Save button
2021-07-15 20:47:28 +02:00
const saveButton = new Button ( t . split . Clone ( ) , ( ) = > {
hasBeenSplit . setData ( true )
2021-09-22 05:02:09 +02:00
State . state . changes . applyAction ( new SplitAction ( id , splitPoints . data . map ( ff = > ff . feature . geometry . coordinates ) ) )
} )
2021-07-15 20:47:28 +02:00
2021-07-19 11:47:17 +02:00
saveButton . SetClass ( "btn btn-primary mr-3" ) ;
const disabledSaveButton = new Button ( "Split" , undefined ) ;
disabledSaveButton . SetClass ( "btn btn-disabled mr-3" ) ;
2021-07-13 16:11:57 +02:00
// Only show the save button if there are split points defined
2021-07-15 00:26:25 +02:00
const saveToggle = new Toggle ( disabledSaveButton , saveButton , splitPoints . map ( ( data ) = > data . length === 0 ) )
2021-07-13 11:26:50 +02:00
2021-07-19 11:47:17 +02:00
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 ) ;
} ) ;
2021-07-13 11:26:50 +02:00
2021-07-19 11:47:17 +02:00
cancelButton . SetClass ( "btn btn-secondary block" ) ;
2021-07-13 11:26:50 +02:00
2021-07-15 20:47:28 +02:00
const splitTitle = new Title ( t . splitTitle ) ;
2021-07-13 11:26:50 +02:00
2021-07-18 14:52:09 +02:00
const mapView = new Combine ( [ splitTitle , miniMap , new Combine ( [ cancelButton , saveToggle ] ) . SetClass ( "flex flex-row" ) ] ) ;
2021-07-15 20:47:28 +02:00
mapView . SetClass ( "question" )
const confirm = new Toggle ( mapView , splitToggle , splitClicked ) ;
super ( t . hasBeenSplit . Clone ( ) , confirm , hasBeenSplit )
2021-09-22 05:02:09 +02:00
this . dialogIsOpened = splitClicked
2021-07-13 11:26:50 +02:00
}
}