2024-09-04 14:42:26 +02:00
import { distance , type Coordinates , TrackPoint , TrackSegment , Track , projectedPoint } from "gpx" ;
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-07-16 12:17:23 +02:00
import { getOrderedSelection , 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-06-24 19:41:44 +02:00
import { currentTool , streetViewEnabled , Tool } from "$lib/stores" ;
2024-09-04 14:42:26 +02:00
import { getClosestLinePoint , getElevation , resetCursor , 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-07-24 15:17:41 +02:00
function stopPropagation ( e : any ) {
e . stopPropagation ( ) ;
}
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-08-05 16:31:47 +02:00
lastDragEvent = 0 ;
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-07-16 12:17:23 +02:00
let selected = get ( selection ) . hasAnyChildren ( new ListFileItem ( this . fileId ) , true , [ 'waypoints' ] ) ;
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 ( '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-07-24 15:17:41 +02:00
this . map . on ( 'click' , this . fileId , stopPropagation ) ;
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 ( '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-07-24 15:17:41 +02:00
this . map . off ( 'click' , this . fileId , stopPropagation ) ;
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-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-07-13 11:42:21 +02:00
updateMap ( map : mapboxgl.Map ) {
this . map = map ;
}
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-07-24 15:17:41 +02:00
element . className = ` h-5 w-5 xs:h-4 xs:w-4 md:h-3 md: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
marker . on ( 'dragstart' , ( e ) = > {
2024-08-05 16:31:47 +02:00
this . 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-08-05 16:31:47 +02:00
this . 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
} ) ;
2024-08-05 16:31:47 +02:00
let handleAnchorClick = this . handleClickForAnchor ( anchor , marker ) ;
marker . getElement ( ) . addEventListener ( 'click' , handleAnchorClick ) ;
marker . getElement ( ) . addEventListener ( 'contextmenu' , handleAnchorClick ) ;
return anchor ;
}
handleClickForAnchor ( anchor : Anchor , marker : mapboxgl.Marker ) {
return ( e : any ) = > {
e . preventDefault ( ) ;
2024-04-29 17:03:23 +02:00
e . stopPropagation ( ) ;
2024-09-04 14:42:26 +02:00
if ( Date . now ( ) - this . lastDragEvent < 100 ) { // Prevent click event during drag
2024-05-24 20:23:49 +02:00
return ;
}
2024-04-29 17:03:23 +02:00
2024-09-04 14:42:26 +02:00
if ( marker === this . temporaryAnchor . marker ) {
this . turnIntoPermanentAnchor ( ) ;
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-08-05 16:31:47 +02:00
} ;
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-08-19 12:55:17 +02:00
let center = this . map . getCenter ( ) ;
let bottomLeft = this . map . unproject ( [ 0 , this . map . getCanvas ( ) . height ] ) ;
let topRight = this . map . unproject ( [ this . map . getCanvas ( ) . width , 0 ] ) ;
let diagonal = bottomLeft . distanceTo ( topRight ) ;
2024-07-09 23:41:38 +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 ;
2024-08-19 12:55:17 +02:00
if ( anchor . inZoom && center . distanceTo ( anchor . marker . getLngLat ( ) ) < diagonal ) {
2024-04-30 15:19:50 +02:00
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-08-11 17:40:47 +02:00
if ( this . temporaryAnchor . marker . getElement ( ) . classList . contains ( 'cursor-grabbing' ) ) { // Do not not change the source point if it is already being dragged
return ;
}
2024-06-24 19:41:44 +02:00
if ( get ( streetViewEnabled ) ) {
return ;
}
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-08-22 10:41:04 +02:00
let minDetails : any = { distance : 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 ) ) ) {
2024-08-13 14:58:15 +02:00
let details : any = { } ;
let closest = getClosestLinePoint ( segment . trkpt , this . temporaryAnchor . point , details ) ;
2024-08-22 10:41:04 +02:00
if ( details . distance < minDetails . distance ) {
minDetails = details ;
2024-08-13 14:58:15 +02:00
minAnchor = {
point : closest ,
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-08-22 10:41:04 +02:00
if ( minAnchor . point . _data . anchor ) {
minAnchor . point = minAnchor . point . clone ( ) ;
if ( minDetails . before ) {
minAnchor . point . _data . index = minAnchor . point . _data . index + 0.5 ;
} else {
minAnchor . point . _data . index = minAnchor . point . _data . index - 0.5 ;
}
}
2024-04-30 20:55:47 +02:00
return minAnchor ;
2024-04-26 19:34:46 +02:00
}
2024-09-04 14:42:26 +02:00
turnIntoPermanentAnchor() {
let file = get ( this . file ) ? . file ;
// Find the point closest to the temporary anchor
let minDetails : any = { distance : Number.MAX_VALUE } ;
let minInfo = {
point : this.temporaryAnchor.point ,
trackIndex : - 1 ,
segmentIndex : - 1 ,
trkptIndex : - 1
} ;
file ? . forEachSegment ( ( segment , trackIndex , segmentIndex ) = > {
if ( get ( selection ) . hasAnyParent ( new ListTrackSegmentItem ( this . fileId , trackIndex , segmentIndex ) ) ) {
let details : any = { } ;
getClosestLinePoint ( segment . trkpt , this . temporaryAnchor . point , details ) ;
if ( details . distance < minDetails . distance ) {
minDetails = details ;
let before = details . before ? details.index : details.index - 1 ;
let projectedPt = projectedPoint ( segment . trkpt [ before ] , segment . trkpt [ before + 1 ] , this . temporaryAnchor . point ) ;
let ratio = distance ( segment . trkpt [ before ] , projectedPt ) / distance ( segment . trkpt [ before ] , segment . trkpt [ before + 1 ] ) ;
2024-09-04 14:46:08 +02:00
let point = segment . trkpt [ before ] . clone ( ) ;
point . setCoordinates ( projectedPt ) ;
point . ele = ( 1 - ratio ) * ( segment . trkpt [ before ] . ele ? ? 0 ) + ratio * ( segment . trkpt [ before + 1 ] . ele ? ? 0 ) ;
point . time = ( segment . trkpt [ before ] . time && segment . trkpt [ before + 1 ] . time ) ? new Date ( ( 1 - ratio ) * segment . trkpt [ before ] . time . getTime ( ) + ratio * segment . trkpt [ before + 1 ] . time . getTime ( ) ) : undefined ;
point . _data = {
anchor : true ,
zoom : 0
} ;
2024-09-04 14:42:26 +02:00
minInfo = {
2024-09-04 14:46:08 +02:00
point ,
2024-09-04 14:42:26 +02:00
trackIndex ,
segmentIndex ,
trkptIndex : before + 1
} ;
}
}
} ) ;
if ( minInfo . trackIndex !== - 1 ) {
dbUtils . applyToFile ( this . fileId , ( file ) = > file . replaceTrackPoints ( minInfo . trackIndex , minInfo . segmentIndex , minInfo . trkptIndex , minInfo . trkptIndex - 1 , [ minInfo . point ] ) ) ;
}
}
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 ) ;
2024-07-04 02:17:50 +02:00
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 ( ) ;
2024-06-18 12:35:24 +02:00
let fileWithStats = get ( this . file ) ;
if ( ! fileWithStats ) {
2024-06-12 14:56:29 +02:00
return ;
}
2024-06-18 12:35:24 +02:00
let speed = fileWithStats . statistics . getStatisticsFor ( new ListTrackSegmentItem ( this . fileId , anchor . trackIndex , anchor . segmentIndex ) ) . global . speed . moving ;
2024-06-12 14:56:29 +02:00
let segment = anchor . segment ;
dbUtils . applyToFile ( this . fileId , ( file ) = > {
2024-07-04 02:17:50 +02:00
file . replaceTrackPoints ( anchor . trackIndex , anchor . segmentIndex , segment . trkpt . length , segment . trkpt . length - 1 , segment . trkpt . slice ( 0 , anchor . point . _data . index ) , speed > 0 ? speed : undefined ) ;
file . crop ( anchor . point . _data . index , anchor . point . _data . index + segment . trkpt . length - 1 , [ anchor . trackIndex ] , [ anchor . segmentIndex ] ) ;
2024-06-12 14:56:29 +02:00
} ) ;
}
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-06-24 19:41:44 +02:00
if ( get ( streetViewEnabled ) ) {
return ;
}
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-07-16 12:17:23 +02:00
let selected = getOrderedSelection ( ) ;
if ( selected . length === 0 || selected [ selected . length - 1 ] . getFileId ( ) !== this . fileId ) {
return ;
}
let item = selected [ selected . length - 1 ] ;
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 trackIndex = file . trk . length > 0 ? file . trk . length - 1 : 0 ;
if ( item instanceof ListTrackItem || item instanceof ListTrackSegmentItem ) {
trackIndex = item . getTrackIndex ( ) ;
}
2024-06-27 00:29:43 +02:00
let segmentIndex = ( file . trk . length > 0 && file . trk [ trackIndex ] . trkseg . length > 0 ) ? file . trk [ trackIndex ] . trkseg . length - 1 : 0 ;
2024-06-13 19:11:54 +02:00
if ( item instanceof ListTrackSegmentItem ) {
segmentIndex = item . getSegmentIndex ( ) ;
}
2024-06-13 17:59:16 +02:00
if ( file . trk . length === 0 ) {
let track = new Track ( ) ;
2024-07-04 02:17:50 +02:00
track . replaceTrackPoints ( 0 , 0 , 0 , [ newPoint ] ) ;
2024-07-04 16:25:13 +02:00
file . replaceTracks ( 0 , 0 , [ track ] ) ;
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 ( ) ;
2024-07-04 02:17:50 +02:00
segment . replaceTrackPoints ( 0 , 0 , [ newPoint ] ) ;
file . replaceTrackSegments ( trackIndex , 0 , 0 , [ segment ] ) ;
2024-06-13 17:59:16 +02:00
} else {
2024-07-04 02:17:50 +02:00
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-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 ;
2024-06-18 12:35:24 +02:00
let fileWithStats = get ( this . file ) ;
if ( ! fileWithStats ) {
return false ;
}
2024-04-30 15:19:50 +02:00
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-07-18 10:43:38 +02:00
anchors [ 0 ] . point . _data . index = 0 ;
2024-08-06 16:08:17 +02:00
} else if ( anchors [ 0 ] . point . _data . index === segment . trkpt . length - 1 && distance ( anchors [ 0 ] . point . getCoordinates ( ) , response [ 0 ] . getCoordinates ( ) ) < 1 ) { // First anchor is the last point of the segment, and the new point is close enough
anchors [ 0 ] . point = response [ 0 ] ; // replace the first anchor
anchors [ 0 ] . point . _data . index = segment . trkpt . length - 1 ;
2024-05-16 13:27:12 +02:00
} 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-07-18 10:43:38 +02:00
anchors [ anchors . length - 1 ] . point . _data . index = segment . trkpt . length - 1 ;
2024-05-16 13:27:12 +02:00
} 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
2024-08-13 14:58:15 +02:00
anchors [ i ] . point = getClosestLinePoint ( response . slice ( 1 , - 1 ) , targetCoordinates [ i ] ) ;
2024-04-26 14:16:59 +02:00
}
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-06-18 12:35:24 +02:00
let stats = fileWithStats . statistics . getStatisticsFor ( new ListTrackSegmentItem ( this . fileId , anchors [ 0 ] . trackIndex , anchors [ 0 ] . segmentIndex ) ) ;
let speed : number | undefined = undefined ;
let startTime = anchors [ 0 ] . point . time ;
if ( stats . global . speed . moving > 0 ) {
let replacingDistance = 0 ;
for ( let i = 1 ; i < response . length ; i ++ ) {
replacingDistance += distance ( response [ i - 1 ] . getCoordinates ( ) , response [ i ] . getCoordinates ( ) ) / 1000 ;
}
let replacedDistance = stats . local . distance . moving [ anchors [ anchors . length - 1 ] . point . _data . index ] - stats . local . distance . moving [ anchors [ 0 ] . point . _data . index ] ;
let newDistance = stats . global . distance . moving + replacingDistance - replacedDistance ;
let newTime = newDistance / stats . global . speed . moving * 3600 ;
let remainingTime = stats . global . time . moving - ( stats . local . time . moving [ anchors [ anchors . length - 1 ] . point . _data . index ] - stats . local . time . moving [ anchors [ 0 ] . point . _data . index ] ) ;
let replacingTime = newTime - remainingTime ;
2024-08-07 16:22:22 +02:00
if ( replacingTime <= 0 ) { // Fallback to simple time difference
replacingTime = stats . local . time . total [ anchors [ anchors . length - 1 ] . point . _data . index ] - stats . local . time . total [ anchors [ 0 ] . point . _data . index ] ;
}
2024-06-18 12:35:24 +02:00
speed = replacingDistance / replacingTime * 3600 ;
2024-07-18 10:43:38 +02:00
if ( startTime === undefined ) { // Replacing the first point
let endIndex = anchors [ anchors . length - 1 ] . point . _data . index ;
startTime = new Date ( ( segment . trkpt [ endIndex ] . time ? . getTime ( ) ? ? 0 ) - ( replacingTime + stats . local . time . total [ endIndex ] - stats . local . time . moving [ endIndex ] ) * 1000 ) ;
}
2024-06-18 12:35:24 +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 , speed , startTime ) ) ;
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 ;
} ;