2024-06-13 17:59:16 +02:00
import { distance , type Coordinates , TrackPoint , TrackSegment , Track } from "gpx" ;
2024-05-15 15:30:02 +02:00
import { original } from "immer" ;
2024-06-12 14:56:29 +02:00
import { get , writable , type Readable } from "svelte/store" ;
2024-04-25 16:41:06 +02:00
import mapboxgl from "mapbox-gl" ;
import { route } from "./Routing" ;
2024-04-27 11:16:59 +02:00
import { toast } from "svelte-sonner" ;
import { _ } from "svelte-i18n" ;
2024-05-08 21:31:54 +02:00
import { dbUtils , type GPXFileWithStatistics } from "$lib/db" ;
2024-05-24 16:37:26 +02:00
import { selection } from "$lib/components/file-list/Selection" ;
2024-06-13 19:11:54 +02:00
import { ListFileItem , ListTrackItem , ListTrackSegmentItem } from "$lib/components/file-list/FileList" ;
2024-05-24 16:37:26 +02:00
import { currentTool , Tool } from "$lib/stores" ;
2024-06-06 11:58:50 +02:00
import { resetCursor , setCrosshairCursor , setGrabbingCursor } from "$lib/utils" ;
2024-04-25 16:41:06 +02:00
2024-06-12 14:56:29 +02:00
export const canChangeStart = writable ( false ) ;
2024-04-25 16:41:06 +02:00
export class RoutingControls {
2024-05-24 16:37:26 +02:00
active : boolean = false ;
2024-04-25 16:41:06 +02:00
map : mapboxgl.Map ;
2024-05-03 15:59:34 +02:00
fileId : string = '' ;
2024-05-08 21:31:54 +02:00
file : Readable < GPXFileWithStatistics | undefined > ;
2024-04-30 15:19:50 +02:00
anchors : AnchorWithMarker [ ] = [ ] ;
shownAnchors : AnchorWithMarker [ ] = [ ] ;
2024-04-26 13:33:17 +02:00
popup : mapboxgl.Popup ;
popupElement : HTMLElement ;
2024-04-30 15:19:50 +02:00
temporaryAnchor : AnchorWithMarker ;
2024-05-24 16:37:26 +02:00
fileUnsubscribe : ( ) = > void = ( ) = > { } ;
unsubscribes : Function [ ] = [ ] ;
2024-04-25 16:41:06 +02:00
2024-04-30 15:19:50 +02:00
toggleAnchorsForZoomLevelAndBoundsBinded : ( ) = > void = this . toggleAnchorsForZoomLevelAndBounds . bind ( this ) ;
2024-04-26 19:34:46 +02:00
showTemporaryAnchorBinded : ( e : any ) = > void = this . showTemporaryAnchor . bind ( this ) ;
2024-04-30 15:19:50 +02:00
updateTemporaryAnchorBinded : ( e : any ) = > void = this . updateTemporaryAnchor . bind ( this ) ;
2024-04-26 14:37:05 +02:00
appendAnchorBinded : ( e : mapboxgl.MapMouseEvent ) = > void = this . appendAnchor . bind ( this ) ;
2024-04-25 16:41:06 +02:00
2024-05-08 21:31:54 +02:00
constructor ( map : mapboxgl.Map , fileId : string , file : Readable < GPXFileWithStatistics | undefined > , popup : mapboxgl.Popup , popupElement : HTMLElement ) {
2024-04-25 16:41:06 +02:00
this . map = map ;
2024-05-03 15:59:34 +02:00
this . fileId = fileId ;
2024-04-25 16:41:06 +02:00
this . file = file ;
2024-04-26 13:33:17 +02:00
this . popup = popup ;
this . popupElement = popupElement ;
2024-04-30 15:19:50 +02:00
let point = new TrackPoint ( {
attributes : {
lat : 0 ,
lon : 0
}
2024-04-26 19:34:46 +02:00
} ) ;
2024-05-24 16:37:26 +02:00
this . temporaryAnchor = this . createAnchor ( point , new TrackSegment ( ) , 0 , 0 ) ;
2024-04-30 15:19:50 +02:00
this . temporaryAnchor . marker . getElement ( ) . classList . remove ( 'z-10' ) ; // Show below the other markers
2024-04-26 19:34:46 +02:00
2024-05-24 16:37:26 +02:00
this . unsubscribes . push ( selection . subscribe ( this . addIfNeeded . bind ( this ) ) ) ;
this . unsubscribes . push ( currentTool . subscribe ( this . addIfNeeded . bind ( this ) ) ) ;
}
addIfNeeded() {
let routing = get ( currentTool ) === Tool . ROUTING ;
if ( ! routing ) {
if ( this . active ) {
this . remove ( ) ;
}
return ;
}
2024-05-24 19:00:26 +02:00
let selected = get ( selection ) . hasAnyChildren ( new ListFileItem ( this . fileId ) , true , [ 'waypoints' ] ) && get ( selection ) . size == 1 ;
2024-05-24 16:37:26 +02:00
if ( selected ) {
2024-05-24 19:00:26 +02:00
if ( this . active ) {
this . updateControls ( ) ;
} else {
this . add ( ) ;
}
} else if ( this . active ) {
2024-05-24 16:37:26 +02:00
this . remove ( ) ;
}
2024-04-25 16:41:06 +02:00
}
add() {
2024-05-24 16:37:26 +02:00
this . active = true ;
2024-04-30 15:19:50 +02:00
this . map . on ( 'zoom' , this . toggleAnchorsForZoomLevelAndBoundsBinded ) ;
this . map . on ( 'move' , this . toggleAnchorsForZoomLevelAndBoundsBinded ) ;
2024-04-26 14:37:05 +02:00
this . map . on ( 'click' , this . appendAnchorBinded ) ;
2024-05-03 15:59:34 +02:00
this . map . on ( 'mousemove' , this . fileId , this . showTemporaryAnchorBinded ) ;
2024-06-06 11:58:50 +02:00
setCrosshairCursor ( ) ;
2024-04-25 16:41:06 +02:00
2024-05-24 16:37:26 +02:00
this . fileUnsubscribe = this . file . subscribe ( this . updateControls . bind ( this ) ) ;
2024-04-25 16:41:06 +02:00
}
2024-04-26 14:37:05 +02:00
updateControls() { // Update the markers when the file changes
2024-05-08 21:31:54 +02:00
let file = get ( this . file ) ? . file ;
2024-05-03 15:59:34 +02:00
if ( ! file ) {
return ;
}
2024-04-30 16:00:06 +02:00
let anchorIndex = 0 ;
2024-05-24 16:37:26 +02:00
file . forEachSegment ( ( segment , trackIndex , segmentIndex ) = > {
if ( get ( selection ) . hasAnyParent ( new ListTrackSegmentItem ( this . fileId , trackIndex , segmentIndex ) ) ) {
for ( let point of segment . trkpt ) { // Update the existing anchors (could be improved by matching the existing anchors with the new ones?)
if ( point . _data . anchor ) {
if ( anchorIndex < this . anchors . length ) {
this . anchors [ anchorIndex ] . point = point ;
this . anchors [ anchorIndex ] . segment = segment ;
this . anchors [ anchorIndex ] . trackIndex = trackIndex ;
this . anchors [ anchorIndex ] . segmentIndex = segmentIndex ;
this . anchors [ anchorIndex ] . marker . setLngLat ( point . getCoordinates ( ) ) ;
} else {
this . anchors . push ( this . createAnchor ( point , segment , trackIndex , segmentIndex ) ) ;
}
anchorIndex ++ ;
2024-04-30 15:19:50 +02:00
}
2024-04-25 19:02:34 +02:00
}
2024-04-30 15:19:50 +02:00
}
2024-05-24 16:37:26 +02:00
} ) ;
2024-04-30 15:19:50 +02:00
2024-04-30 16:00:06 +02:00
while ( anchorIndex < this . anchors . length ) { // Remove the extra anchors
this . anchors . pop ( ) ? . marker . remove ( ) ;
2024-04-25 19:02:34 +02:00
}
2024-04-30 15:19:50 +02:00
this . toggleAnchorsForZoomLevelAndBounds ( ) ;
2024-04-25 16:41:06 +02:00
}
remove() {
2024-05-24 16:37:26 +02:00
this . active = false ;
2024-04-30 15:19:50 +02:00
for ( let anchor of this . anchors ) {
anchor . marker . remove ( ) ;
2024-04-25 16:41:06 +02:00
}
2024-04-30 15:19:50 +02:00
this . map . off ( 'zoom' , this . toggleAnchorsForZoomLevelAndBoundsBinded ) ;
this . map . off ( 'move' , this . toggleAnchorsForZoomLevelAndBoundsBinded ) ;
2024-04-26 14:37:05 +02:00
this . map . off ( 'click' , this . appendAnchorBinded ) ;
2024-05-03 15:59:34 +02:00
this . map . off ( 'mousemove' , this . fileId , this . showTemporaryAnchorBinded ) ;
2024-04-30 15:19:50 +02:00
this . map . off ( 'mousemove' , this . updateTemporaryAnchorBinded ) ;
2024-06-11 23:40:10 +02:00
this . temporaryAnchor . marker . remove ( ) ;
2024-06-06 11:58:50 +02:00
resetCursor ( ) ;
2024-04-25 16:41:06 +02:00
2024-05-24 16:37:26 +02:00
this . fileUnsubscribe ( ) ;
2024-04-25 16:41:06 +02:00
}
2024-05-24 16:37:26 +02:00
createAnchor ( point : TrackPoint , segment : TrackSegment , trackIndex : number , segmentIndex : number ) : AnchorWithMarker {
2024-04-25 19:02:34 +02:00
let element = document . createElement ( 'div' ) ;
2024-04-27 11:16:59 +02:00
element . className = ` h-3 w-3 rounded-full bg-white border-2 border-black cursor-pointer ` ;
2024-04-25 19:02:34 +02:00
let marker = new mapboxgl . Marker ( {
draggable : true ,
2024-04-26 19:34:46 +02:00
className : 'z-10' ,
2024-04-25 19:02:34 +02:00
element
2024-04-30 15:19:50 +02:00
} ) . setLngLat ( point . getCoordinates ( ) ) ;
2024-04-25 19:02:34 +02:00
2024-04-30 15:19:50 +02:00
let anchor = {
point ,
segment ,
2024-05-24 16:37:26 +02:00
trackIndex ,
2024-04-30 20:55:47 +02:00
segmentIndex ,
2024-04-30 15:19:50 +02:00
marker ,
inZoom : false
} ;
2024-04-25 19:02:34 +02:00
2024-04-26 14:16:59 +02:00
let lastDragEvent = 0 ;
marker . on ( 'dragstart' , ( e ) = > {
lastDragEvent = Date . now ( ) ;
2024-06-06 11:58:50 +02:00
setGrabbingCursor ( ) ;
2024-04-30 15:19:50 +02:00
element . classList . remove ( 'cursor-pointer' ) ;
2024-04-26 13:33:17 +02:00
element . classList . add ( 'cursor-grabbing' ) ;
} ) ;
2024-04-30 15:19:50 +02:00
marker . on ( 'dragend' , ( e ) = > {
2024-04-26 14:16:59 +02:00
lastDragEvent = Date . now ( ) ;
2024-06-06 11:58:50 +02:00
resetCursor ( ) ;
2024-04-26 13:33:17 +02:00
element . classList . remove ( 'cursor-grabbing' ) ;
2024-04-30 15:19:50 +02:00
element . classList . add ( 'cursor-pointer' ) ;
this . moveAnchor ( anchor ) ;
2024-04-26 13:33:17 +02:00
} ) ;
marker . getElement ( ) . addEventListener ( 'click' , ( e ) = > {
2024-04-29 17:03:23 +02:00
e . stopPropagation ( ) ;
2024-05-24 20:23:49 +02:00
if ( marker === this . temporaryAnchor . marker ) {
return ;
}
2024-04-29 17:03:23 +02:00
2024-04-26 14:37:05 +02:00
if ( Date . now ( ) - lastDragEvent < 100 ) { // Prevent click event during drag
2024-04-26 14:16:59 +02:00
return ;
}
2024-04-29 17:03:23 +02:00
if ( e . shiftKey ) {
this . deleteAnchor ( anchor ) ;
return ;
}
2024-06-12 14:56:29 +02:00
canChangeStart . update ( ( ) = > {
if ( anchor . point . _data . index === 0 ) {
return false ;
}
let segment = anchor . segment ;
if ( distance ( segment . trkpt [ 0 ] . getCoordinates ( ) , segment . trkpt [ segment . trkpt . length - 1 ] . getCoordinates ( ) ) > 1000 ) {
return false ;
}
return true ;
} ) ;
2024-04-26 13:33:17 +02:00
marker . setPopup ( this . popup ) ;
marker . togglePopup ( ) ;
let deleteThisAnchor = this . getDeleteAnchor ( anchor ) ;
2024-04-26 14:37:05 +02:00
this . popupElement . addEventListener ( 'delete' , deleteThisAnchor ) ; // Register the delete event for this anchor
2024-06-12 14:56:29 +02:00
let startLoopAtThisAnchor = this . getStartLoopAtAnchor ( anchor ) ;
this . popupElement . addEventListener ( 'change-start' , startLoopAtThisAnchor ) ; // Register the start loop event for this anchor
2024-04-26 13:33:17 +02:00
this . popup . once ( 'close' , ( ) = > {
this . popupElement . removeEventListener ( 'delete' , deleteThisAnchor ) ;
2024-06-12 14:56:29 +02:00
this . popupElement . removeEventListener ( 'change-start' , startLoopAtThisAnchor ) ;
2024-04-26 13:33:17 +02:00
} ) ;
} ) ;
2024-04-25 19:02:34 +02:00
2024-04-30 15:19:50 +02:00
return anchor ;
2024-04-25 19:02:34 +02:00
}
2024-04-30 15:19:50 +02:00
toggleAnchorsForZoomLevelAndBounds() { // Show markers only if they are in the current zoom level and bounds
this . shownAnchors . splice ( 0 , this . shownAnchors . length ) ;
2024-04-27 09:33:49 +02:00
2024-04-25 16:41:06 +02:00
let zoom = this . map . getZoom ( ) ;
2024-04-30 15:19:50 +02:00
this . anchors . forEach ( ( anchor ) = > {
anchor . inZoom = anchor . point . _data . zoom <= zoom ;
if ( anchor . inZoom && this . map . getBounds ( ) . contains ( anchor . marker . getLngLat ( ) ) ) {
anchor . marker . addTo ( this . map ) ;
this . shownAnchors . push ( anchor ) ;
2024-04-25 16:41:06 +02:00
} else {
2024-04-30 15:19:50 +02:00
anchor . marker . remove ( ) ;
2024-04-25 16:41:06 +02:00
}
} ) ;
}
2024-04-26 19:34:46 +02:00
showTemporaryAnchor ( e : any ) {
2024-05-24 16:37:26 +02:00
if ( ! get ( selection ) . hasAnyParent ( new ListTrackSegmentItem ( this . fileId , e . features [ 0 ] . properties . trackIndex , e . features [ 0 ] . properties . segmentIndex ) ) ) {
return ;
}
2024-04-27 09:33:49 +02:00
if ( this . temporaryAnchorCloseToOtherAnchor ( e ) ) {
return ;
}
2024-04-26 19:34:46 +02:00
this . temporaryAnchor . point . setCoordinates ( {
lat : e.lngLat.lat ,
lon : e.lngLat.lng
} ) ;
2024-04-30 15:19:50 +02:00
this . temporaryAnchor . marker . setLngLat ( e . lngLat ) . addTo ( this . map ) ;
2024-04-27 09:33:49 +02:00
2024-04-30 15:19:50 +02:00
this . map . on ( 'mousemove' , this . updateTemporaryAnchorBinded ) ;
2024-04-26 19:34:46 +02:00
}
2024-04-30 15:19:50 +02:00
updateTemporaryAnchor ( e : any ) {
2024-04-27 09:33:49 +02:00
if ( this . temporaryAnchor . marker . getElement ( ) . classList . contains ( 'cursor-grabbing' ) ) { // Do not hide if it is being dragged, and stop listening for mousemove
2024-04-30 15:19:50 +02:00
this . map . off ( 'mousemove' , this . updateTemporaryAnchorBinded ) ;
2024-04-26 19:34:46 +02:00
return ;
}
2024-04-27 09:33:49 +02:00
if ( e . point . dist ( this . map . project ( this . temporaryAnchor . point . getCoordinates ( ) ) ) > 20 || this . temporaryAnchorCloseToOtherAnchor ( e ) ) { // Hide if too far from the layer
this . temporaryAnchor . marker . remove ( ) ;
2024-04-30 15:19:50 +02:00
this . map . off ( 'mousemove' , this . updateTemporaryAnchorBinded ) ;
2024-04-26 19:34:46 +02:00
return ;
}
2024-04-27 09:33:49 +02:00
this . temporaryAnchor . marker . setLngLat ( e . lngLat ) ; // Update the position of the temporary anchor
2024-04-26 19:34:46 +02:00
}
2024-04-27 09:33:49 +02:00
temporaryAnchorCloseToOtherAnchor ( e : any ) {
2024-04-30 15:19:50 +02:00
for ( let anchor of this . shownAnchors ) {
if ( e . point . dist ( this . map . project ( anchor . marker . getLngLat ( ) ) ) < 10 ) {
2024-04-27 09:33:49 +02:00
return true ;
}
}
return false ;
}
2024-04-30 15:19:50 +02:00
async moveAnchor ( anchorWithMarker : AnchorWithMarker ) { // Move the anchor and update the route from and to the neighbouring anchors
2024-04-25 19:02:34 +02:00
let coordinates = {
2024-04-30 15:19:50 +02:00
lat : anchorWithMarker.marker.getLngLat ( ) . lat ,
lon : anchorWithMarker.marker.getLngLat ( ) . lng
2024-04-25 19:02:34 +02:00
} ;
2024-04-30 15:19:50 +02:00
let anchor = anchorWithMarker as Anchor ;
if ( anchorWithMarker === this . temporaryAnchor ) { // Temporary anchor, need to find the closest point of the segment and create an anchor for it
this . temporaryAnchor . marker . remove ( ) ;
anchor = this . getPermanentAnchor ( ) ;
}
2024-04-26 14:16:59 +02:00
let [ previousAnchor , nextAnchor ] = this . getNeighbouringAnchors ( anchor ) ;
2024-04-25 19:02:34 +02:00
2024-04-26 14:16:59 +02:00
let anchors = [ ] ;
let targetCoordinates = [ ] ;
2024-04-25 19:02:34 +02:00
2024-04-26 14:16:59 +02:00
if ( previousAnchor !== null ) {
anchors . push ( previousAnchor ) ;
targetCoordinates . push ( previousAnchor . point . getCoordinates ( ) ) ;
2024-04-25 19:02:34 +02:00
}
2024-04-26 14:16:59 +02:00
anchors . push ( anchor ) ;
targetCoordinates . push ( coordinates ) ;
2024-04-25 19:02:34 +02:00
2024-04-26 14:16:59 +02:00
if ( nextAnchor !== null ) {
anchors . push ( nextAnchor ) ;
targetCoordinates . push ( nextAnchor . point . getCoordinates ( ) ) ;
2024-04-25 19:02:34 +02:00
}
2024-04-26 10:18:08 +02:00
2024-04-27 09:42:55 +02:00
let success = await this . routeBetweenAnchors ( anchors , targetCoordinates ) ;
if ( ! success ) { // Route failed, revert the anchor to the previous position
2024-04-30 15:19:50 +02:00
anchorWithMarker . marker . setLngLat ( anchorWithMarker . point . getCoordinates ( ) ) ;
2024-04-27 09:42:55 +02:00
}
2024-04-25 19:02:34 +02:00
}
2024-04-30 15:19:50 +02:00
getPermanentAnchor ( ) : Anchor {
2024-05-08 21:31:54 +02:00
let file = get ( this . file ) ? . file ;
2024-06-10 20:03:57 +02:00
// Find the point closest to the temporary anchor
2024-04-26 19:34:46 +02:00
let minDistance = Number . MAX_VALUE ;
2024-04-30 20:55:47 +02:00
let minAnchor = this . temporaryAnchor as Anchor ;
2024-05-24 16:37:26 +02:00
file ? . forEachSegment ( ( segment , trackIndex , segmentIndex ) = > {
if ( get ( selection ) . hasAnyParent ( new ListTrackSegmentItem ( this . fileId , trackIndex , segmentIndex ) ) ) {
for ( let point of segment . trkpt ) {
let dist = distance ( point . getCoordinates ( ) , this . temporaryAnchor . point . getCoordinates ( ) ) ;
if ( dist < minDistance ) {
minDistance = dist ;
minAnchor = {
point ,
segment ,
trackIndex ,
segmentIndex ,
} ;
}
2024-04-26 19:34:46 +02:00
}
}
2024-05-24 16:37:26 +02:00
} ) ;
2024-04-26 19:34:46 +02:00
2024-04-30 20:55:47 +02:00
return minAnchor ;
2024-04-26 19:34:46 +02:00
}
2024-04-30 15:19:50 +02:00
getDeleteAnchor ( anchor : Anchor ) {
2024-04-26 13:33:17 +02:00
return ( ) = > this . deleteAnchor ( anchor ) ;
}
2024-04-30 15:19:50 +02:00
async deleteAnchor ( anchor : Anchor ) { // Remove the anchor and route between the neighbouring anchors if they exist
this . popup . remove ( ) ;
2024-04-26 14:16:59 +02:00
let [ previousAnchor , nextAnchor ] = this . getNeighbouringAnchors ( anchor ) ;
2024-04-26 13:33:17 +02:00
2024-04-30 15:19:50 +02:00
if ( previousAnchor === null && nextAnchor === null ) { // Only one point, remove it
2024-05-24 16:37:26 +02:00
dbUtils . applyToFile ( this . fileId , ( file ) = > file . replaceTrackPoints ( anchor . trackIndex , anchor . segmentIndex , 0 , 0 , [ ] ) ) ;
2024-04-30 15:19:50 +02:00
} else if ( previousAnchor === null ) { // First point, remove trackpoints until nextAnchor
2024-05-24 16:37:26 +02:00
dbUtils . applyToFile ( this . fileId , ( file ) = > file . replaceTrackPoints ( anchor . trackIndex , anchor . segmentIndex , 0 , nextAnchor . point . _data . index - 1 , [ ] ) ) ;
2024-04-30 15:19:50 +02:00
} else if ( nextAnchor === null ) { // Last point, remove trackpoints from previousAnchor
2024-05-03 15:59:34 +02:00
dbUtils . applyToFile ( this . fileId , ( file ) = > {
2024-05-24 16:37:26 +02:00
let segment = file . getSegment ( anchor . trackIndex , anchor . segmentIndex ) ;
return file . replaceTrackPoints ( anchor . trackIndex , anchor . segmentIndex , previousAnchor . point . _data . index + 1 , segment . trkpt . length - 1 , [ ] ) ;
2024-04-30 20:55:47 +02:00
} ) ;
2024-04-30 15:19:50 +02:00
} else { // Route between previousAnchor and nextAnchor
2024-04-26 14:16:59 +02:00
this . routeBetweenAnchors ( [ previousAnchor , nextAnchor ] , [ previousAnchor . point . getCoordinates ( ) , nextAnchor . point . getCoordinates ( ) ] ) ;
2024-04-26 10:18:08 +02:00
}
2024-04-26 14:16:59 +02:00
}
2024-04-26 10:18:08 +02:00
2024-06-12 14:56:29 +02:00
getStartLoopAtAnchor ( anchor : Anchor ) {
return ( ) = > this . startLoopAtAnchor ( anchor ) ;
}
startLoopAtAnchor ( anchor : Anchor ) {
this . popup . remove ( ) ;
let file = get ( this . file ) ? . file ;
if ( ! file ) {
return ;
}
let segment = anchor . segment ;
dbUtils . applyToFile ( this . fileId , ( file ) = > {
let newFile = file . replaceTrackPoints ( anchor . trackIndex , anchor . segmentIndex , segment . trkpt . length , segment . trkpt . length - 1 , segment . trkpt . slice ( 0 , anchor . point . _data . index ) ) ;
return newFile . replaceTrackPoints ( anchor . trackIndex , anchor . segmentIndex , 0 , anchor . point . _data . index - 1 , [ ] ) ;
} ) ;
}
2024-04-26 14:37:05 +02:00
async appendAnchor ( e : mapboxgl.MapMouseEvent ) { // Add a new anchor to the end of the last segment
2024-05-07 17:19:53 +02:00
this . appendAnchorWithCoordinates ( {
lat : e.lngLat.lat ,
lon : e.lngLat.lng
} ) ;
}
async appendAnchorWithCoordinates ( coordinates : Coordinates ) { // Add a new anchor to the end of the last segment
2024-04-30 15:19:50 +02:00
let lastAnchor = this . anchors [ this . anchors . length - 1 ] ;
2024-04-25 16:41:06 +02:00
2024-04-26 14:30:08 +02:00
let newPoint = new TrackPoint ( {
2024-05-07 17:19:53 +02:00
attributes : coordinates
2024-04-26 14:30:08 +02:00
} ) ;
2024-04-30 15:19:50 +02:00
newPoint . _data . anchor = true ;
newPoint . _data . zoom = 0 ;
2024-04-25 19:02:34 +02:00
2024-04-27 12:18:40 +02:00
if ( ! lastAnchor ) {
2024-06-13 17:59:16 +02:00
dbUtils . applyToFile ( this . fileId , ( file ) = > {
2024-06-13 19:11:54 +02:00
let item = get ( selection ) . getSelected ( ) [ 0 ] ;
let trackIndex = file . trk . length > 0 ? file . trk . length - 1 : 0 ;
if ( item instanceof ListTrackItem || item instanceof ListTrackSegmentItem ) {
trackIndex = item . getTrackIndex ( ) ;
}
let segmentIndex = file . trk [ trackIndex ] . trkseg . length > 0 ? file . trk [ trackIndex ] . trkseg . length - 1 : 0 ;
if ( item instanceof ListTrackSegmentItem ) {
segmentIndex = item . getSegmentIndex ( ) ;
}
2024-06-13 17:59:16 +02:00
if ( file . trk . length === 0 ) {
let track = new Track ( ) ;
track = track . replaceTrackPoints ( 0 , 0 , 0 , [ newPoint ] ) ;
return file . replaceTracks ( 0 , 0 , [ track ] ) [ 0 ] ;
2024-06-13 19:11:54 +02:00
} else if ( file . trk [ trackIndex ] . trkseg . length === 0 ) {
2024-06-13 17:59:16 +02:00
let segment = new TrackSegment ( ) ;
segment = segment . replaceTrackPoints ( 0 , 0 , [ newPoint ] ) ;
2024-06-13 19:11:54 +02:00
return file . replaceTrackSegments ( trackIndex , 0 , 0 , [ segment ] ) [ 0 ] ;
2024-06-13 17:59:16 +02:00
} else {
2024-06-13 19:11:54 +02:00
return file . replaceTrackPoints ( trackIndex , segmentIndex , 0 , 0 , [ newPoint ] ) ;
2024-06-13 17:59:16 +02:00
}
} ) ;
2024-04-27 12:18:40 +02:00
return ;
}
2024-06-04 15:30:49 +02:00
newPoint . _data . index = lastAnchor . segment . trkpt . length - 1 ; // Do as if the point was the last point in the segment
2024-04-30 15:19:50 +02:00
let newAnchor = {
point : newPoint ,
2024-04-30 20:55:47 +02:00
segment : lastAnchor.segment ,
2024-05-24 16:37:26 +02:00
trackIndex : lastAnchor.trackIndex ,
2024-04-30 20:55:47 +02:00
segmentIndex : lastAnchor.segmentIndex
2024-04-30 15:19:50 +02:00
} ;
2024-04-27 09:42:55 +02:00
2024-04-30 15:19:50 +02:00
await this . routeBetweenAnchors ( [ lastAnchor , newAnchor ] , [ lastAnchor . point . getCoordinates ( ) , newAnchor . point . getCoordinates ( ) ] ) ;
2024-04-26 14:16:59 +02:00
}
2024-05-07 17:19:53 +02:00
routeToStart() {
2024-05-24 16:37:26 +02:00
if ( this . anchors . length === 0 ) {
2024-05-07 17:19:53 +02:00
return ;
}
2024-05-24 16:37:26 +02:00
let lastAnchor = this . anchors [ this . anchors . length - 1 ] ;
let firstAnchor = this . anchors . find ( ( anchor ) = > anchor . segment === lastAnchor . segment ) ;
2024-05-07 17:19:53 +02:00
if ( ! firstAnchor ) {
return ;
}
this . appendAnchorWithCoordinates ( firstAnchor . point . getCoordinates ( ) ) ;
}
createRoundTrip() {
2024-05-24 16:37:26 +02:00
if ( this . anchors . length === 0 ) {
2024-05-07 17:19:53 +02:00
return ;
}
2024-05-24 16:37:26 +02:00
let lastAnchor = this . anchors [ this . anchors . length - 1 ] ;
2024-05-07 17:19:53 +02:00
dbUtils . applyToFile ( this . fileId , ( file ) = > {
2024-05-24 16:37:26 +02:00
let segment = original ( file ) . getSegment ( lastAnchor . trackIndex , lastAnchor . segmentIndex ) ;
2024-05-07 17:19:53 +02:00
let newSegment = segment . clone ( ) ;
2024-06-05 23:37:55 +02:00
newSegment = newSegment . _reverse ( segment . getEndTimestamp ( ) , segment . getEndTimestamp ( ) ) ;
2024-05-24 16:37:26 +02:00
return file . replaceTrackPoints ( lastAnchor . trackIndex , lastAnchor . segmentIndex , segment . trkpt . length , segment . trkpt . length , newSegment . trkpt . map ( ( point ) = > point ) ) ;
2024-05-07 17:19:53 +02:00
} ) ;
}
2024-04-30 15:19:50 +02:00
getNeighbouringAnchors ( anchor : Anchor ) : [ Anchor | null , Anchor | null ] {
let previousAnchor : Anchor | null = null ;
let nextAnchor : Anchor | null = null ;
for ( let i = 0 ; i < this . anchors . length ; i ++ ) {
if ( this . anchors [ i ] . segment === anchor . segment && this . anchors [ i ] . inZoom ) {
if ( this . anchors [ i ] . point . _data . index < anchor . point . _data . index ) {
if ( ! previousAnchor || this . anchors [ i ] . point . _data . index > previousAnchor . point . _data . index ) {
previousAnchor = this . anchors [ i ] ;
}
} else if ( this . anchors [ i ] . point . _data . index > anchor . point . _data . index ) {
if ( ! nextAnchor || this . anchors [ i ] . point . _data . index < nextAnchor . point . _data . index ) {
nextAnchor = this . anchors [ i ] ;
}
2024-04-26 14:16:59 +02:00
}
}
}
return [ previousAnchor , nextAnchor ] ;
}
2024-04-30 15:19:50 +02:00
async routeBetweenAnchors ( anchors : Anchor [ ] , targetCoordinates : Coordinates [ ] ) : Promise < boolean > {
let segment = anchors [ 0 ] . segment ;
if ( anchors . length === 1 ) { // Only one anchor, update the point in the segment
2024-05-24 16:37:26 +02:00
dbUtils . applyToFile ( this . fileId , ( file ) = > file . replaceTrackPoints ( anchors [ 0 ] . trackIndex , anchors [ 0 ] . segmentIndex , 0 , 0 , [ new TrackPoint ( {
2024-05-15 15:30:02 +02:00
attributes : targetCoordinates [ 0 ] ,
} ) ] ) ) ;
2024-04-27 09:42:55 +02:00
return true ;
2024-04-26 14:16:59 +02:00
}
2024-04-27 09:42:55 +02:00
let response : TrackPoint [ ] ;
try {
response = await route ( targetCoordinates ) ;
2024-04-30 15:19:50 +02:00
} catch ( e : any ) {
2024-04-27 11:16:59 +02:00
if ( e . message . includes ( 'from-position not mapped in existing datafile' ) ) {
toast . error ( get ( _ ) ( "toolbar.routing.error.from" ) ) ;
2024-04-27 11:32:11 +02:00
} else if ( e . message . includes ( 'via1-position not mapped in existing datafile' ) ) {
toast . error ( get ( _ ) ( "toolbar.routing.error.via" ) ) ;
2024-04-27 11:16:59 +02:00
} else if ( e . message . includes ( 'to-position not mapped in existing datafile' ) ) {
toast . error ( get ( _ ) ( "toolbar.routing.error.to" ) ) ;
2024-04-27 11:38:34 +02:00
} else if ( e . message . includes ( 'Time-out' ) ) {
toast . error ( get ( _ ) ( "toolbar.routing.error.timeout" ) ) ;
2024-04-27 11:16:59 +02:00
} else {
toast . error ( e . message ) ;
}
2024-04-27 09:42:55 +02:00
return false ;
}
2024-04-26 14:16:59 +02:00
if ( anchors [ 0 ] . point . _data . index === 0 ) { // First anchor is the first point of the segment
2024-06-04 15:30:49 +02:00
anchors [ 0 ] . point = response [ 0 ] ; // replace the first anchor
2024-05-16 13:27:12 +02:00
anchors [ 0 ] . point . _data . index = 0 ;
} else {
2024-05-23 12:57:24 +02:00
anchors [ 0 ] . point = anchors [ 0 ] . point . clone ( ) ; // Clone the anchor to assign new properties
response . splice ( 0 , 0 , anchors [ 0 ] . point ) ; // Insert it in the response to keep it
2024-04-26 14:16:59 +02:00
}
2024-04-30 15:19:50 +02:00
if ( anchors [ anchors . length - 1 ] . point . _data . index === segment . trkpt . length - 1 ) { // Last anchor is the last point of the segment
2024-06-04 15:30:49 +02:00
anchors [ anchors . length - 1 ] . point = response [ response . length - 1 ] ; // replace the last anchor
2024-05-16 13:27:12 +02:00
anchors [ anchors . length - 1 ] . point . _data . index = segment . trkpt . length - 1 ;
} else {
2024-05-23 12:57:24 +02:00
anchors [ anchors . length - 1 ] . point = anchors [ anchors . length - 1 ] . point . clone ( ) ; // Clone the anchor to assign new properties
response . push ( anchors [ anchors . length - 1 ] . point ) ; // Insert it in the response to keep it
2024-04-26 14:16:59 +02:00
}
for ( let i = 1 ; i < anchors . length - 1 ; i ++ ) {
// Find the closest point to the intermediate anchor
// and transfer the marker to that point
let minDistance = Number . MAX_VALUE ;
let minIndex = 0 ;
for ( let j = 1 ; j < response . length - 1 ; j ++ ) {
let dist = distance ( response [ j ] . getCoordinates ( ) , targetCoordinates [ i ] ) ;
if ( dist < minDistance ) {
minDistance = dist ;
minIndex = j ;
}
}
anchors [ i ] . point = response [ minIndex ] ;
}
anchors . forEach ( ( anchor ) = > {
2024-04-30 15:19:50 +02:00
anchor . point . _data . anchor = true ;
anchor . point . _data . zoom = 0 ; // Make these anchors permanent
2024-04-26 14:16:59 +02:00
} ) ;
2024-04-26 10:18:08 +02:00
2024-05-24 16:37:26 +02:00
dbUtils . applyToFile ( this . fileId , ( file ) = > file . replaceTrackPoints ( anchors [ 0 ] . trackIndex , anchors [ 0 ] . segmentIndex , anchors [ 0 ] . point . _data . index , anchors [ anchors . length - 1 ] . point . _data . index , response ) ) ;
2024-04-27 09:42:55 +02:00
return true ;
2024-04-25 16:41:06 +02:00
}
2024-05-24 16:37:26 +02:00
destroy() {
this . remove ( ) ;
this . unsubscribes . forEach ( ( unsubscribe ) = > unsubscribe ( ) ) ;
}
2024-04-30 15:19:50 +02:00
}
type Anchor = {
segment : TrackSegment ;
2024-05-24 16:37:26 +02:00
trackIndex : number ;
2024-04-30 20:55:47 +02:00
segmentIndex : number ;
2024-04-30 15:19:50 +02:00
point : TrackPoint ;
} ;
type AnchorWithMarker = Anchor & {
marker : mapboxgl.Marker ;
inZoom : boolean ;
} ;