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-07-13 16:11:57 +02:00
import ShowDataLayer from "../ShowDataLayer" ;
import { GeoOperations } from "../../Logic/GeoOperations" ;
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 { OsmObject , OsmWay } from "../../Logic/Osm/OsmObject" ;
import Title from "../Base/Title" ;
2021-08-07 23:11:34 +02:00
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" ;
2021-07-13 11:26:50 +02:00
export default class SplitRoadWizard extends Toggle {
2021-07-15 00:26:25 +02:00
private static splitLayout = new UIEventSource ( SplitRoadWizard . GetSplitLayout ( ) )
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-07-13 16:11:57 +02:00
// Minimap on which you can select the points to be splitted
2021-07-15 20:47:28 +02:00
const miniMap = new Minimap ( { background : State.state.backgroundLayer , allowMoving : false } ) ;
miniMap . SetStyle ( "width: 100%; height: 24rem;" ) ;
2021-07-13 16:11:57 +02:00
// 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)
2021-07-28 02:51:07 +02:00
new ShowDataLayer ( roadEventSource , miniMap . leafletMap , State . state . layoutToUse , false , true ) ;
new ShowDataLayer ( splitPoints , miniMap . leafletMap , SplitRoadWizard . splitLayout , false , false )
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 ) {
// 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" ;
2021-07-15 00:26:25 +02:00
pointOnRoad [ "_matching_layer_id" ] = "splitpositions" ;
2021-07-13 16:11:57 +02:00
// 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-07-15 20:47:28 +02:00
const splitButton = new SubtleButton ( Svg . scissors_ui ( ) , t . inviteToSplit . Clone ( ) ) ;
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-07-18 14:52:09 +02:00
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
2021-07-15 20:47:28 +02:00
}
2021-07-18 14:52:09 +02:00
hasRun = true
const splitAction = new SplitAction (
< OsmWay > way , way . asGeoJson ( ) , partOf , splitPoints . data . map ( ff = > ff . feature )
)
State . state . changes . applyAction ( splitAction )
} , [ partOfSrc ] )
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-07-13 11:26:50 +02:00
}
2021-07-15 00:26:25 +02:00
private static GetSplitLayout ( ) : LayoutConfig {
return new LayoutConfig ( {
maintainer : "mapcomplete" ,
2021-07-15 20:47:28 +02:00
language : [ "en" ] ,
2021-07-15 00:26:25 +02:00
startLon : 0 ,
startLat : 0 ,
2021-07-15 20:47:28 +02:00
description : "Split points visualisations - built in at SplitRoadWizard.ts" ,
2021-07-15 00:26:25 +02:00
icon : "" , startZoom : 0 ,
title : "Split locations" ,
version : "" ,
id : "splitpositions" ,
layers : [ { id : "splitpositions" , source : { osmTags : "_cutposition=yes" } , icon : "./assets/svg/plus.svg" } ]
2021-07-15 20:47:28 +02:00
} , true , "(BUILTIN) SplitRoadWizard.ts" )
2021-07-15 00:26:25 +02:00
}
2021-07-13 11:26:50 +02:00
}