1
1
import type { TFunction } from 'i18next' ;
2
2
import { uniqBy } from 'lodash' ;
3
3
4
+ import type { TimetableItemRoundTripGroups } from 'applications/operationalStudies/types' ;
4
5
import {
5
6
getUniqueOpRefsFromTimetableItems ,
6
7
addPathOpsToTimetableItems ,
8
+ groupRoundTrips ,
9
+ checkRoundTripCompatible ,
7
10
} from 'applications/operationalStudies/utils' ;
8
11
import { osrdEditoastApi , type TrainSchedule } from 'common/api/osrdEditoastApi' ;
9
12
import type { TimetableItem , TimetableItemWithPathOps } from 'reducers/osrdconf/types' ;
@@ -330,13 +333,14 @@ export const loadAndIndexNge = async (
330
333
*/
331
334
const getNgeTrainruns = (
332
335
state : MacroEditorState ,
333
- timetableItems : TimetableItem [ ] ,
336
+ groupedTimetableItems : ( readonly [ TimetableItem , TimetableItem | null ] ) [ ] ,
334
337
labels : LabelDto [ ]
335
338
) : TrainrunDto [ ] =>
336
- timetableItems
339
+ groupedTimetableItems
340
+ . map ( ( [ a , b ] ) => ( { ...a , returnId : b ?. id ?? null } ) )
337
341
. filter ( ( timetableItem ) => timetableItem . path . length >= 2 )
338
342
. map ( ( timetableItem , index ) => {
339
- state . timetableItemIdByNgeId . set ( index + 1 , [ timetableItem . id , null ] ) ;
343
+ state . timetableItemIdByNgeId . set ( index + 1 , [ timetableItem . id , timetableItem . returnId ] ) ;
340
344
const trainrunFrequency = getTrainrunFrequencyFromTimetableItem ( timetableItem , state ) ;
341
345
return {
342
346
id : index + 1 ,
@@ -347,7 +351,7 @@ const getNgeTrainruns = (
347
351
labelIds : ( timetableItem . labels || [ ] ) . map ( ( l ) =>
348
352
labels . findIndex ( ( e ) => e . label === l && e . labelGroupId === TRAINRUN_LABEL_GROUP . id )
349
353
) ,
350
- trainrunDirection : 'one_way' ,
354
+ trainrunDirection : timetableItem . returnId ? 'round_trip' : 'one_way' ,
351
355
} ;
352
356
} ) ;
353
357
@@ -386,7 +390,7 @@ const createDepartureTimeLock = (scheduleItem: ScheduleItem | undefined, startTi
386
390
*/
387
391
const getNgeTrainrunSectionsWithNodes = (
388
392
state : MacroEditorState ,
389
- timetableItems : TimetableItem [ ] ,
393
+ groupedTimetableItems : ( readonly [ TimetableItem , TimetableItem | null ] ) [ ] ,
390
394
labels : LabelDto [ ]
391
395
) => {
392
396
let portId = 1 ;
@@ -416,104 +420,133 @@ const getNgeTrainrunSectionsWithNodes = (
416
420
// Track nge nodes
417
421
const ngeNodesByPathKey : Record < string , NetzgrafikDto [ 'nodes' ] [ 0 ] > = { } ;
418
422
let trainrunSectionId = 0 ;
419
- const trainrunSections : TrainrunSectionDto [ ] = timetableItems . flatMap ( ( timetableItem , index ) => {
420
- // Figure out the primary node key for each path item
421
- const pathNodeKeys = timetableItem . path . map ( ( pathItem ) => {
422
- const node = state . getNodeByKey ( MacroEditorState . getPathKey ( pathItem ) ) ;
423
- return node ! . path_item_key ;
424
- } ) ;
423
+ const trainrunSections : TrainrunSectionDto [ ] = groupedTimetableItems . flatMap (
424
+ ( [ timetableItem , returnTimetableItem ] , index ) => {
425
+ // Figure out the primary node key for each path item
426
+ const pathNodeKeys = timetableItem . path . map ( ( pathItem ) => {
427
+ const node = state . getNodeByKey ( MacroEditorState . getPathKey ( pathItem ) ) ;
428
+ return node ! . path_item_key ;
429
+ } ) ;
425
430
426
- const startTime = new Date ( timetableItem . start_time ) ;
427
-
428
- // OSRD describes the path in terms of nodes, NGE describes it in terms
429
- // of sections between nodes. Iterate over path items two-by-two to
430
- // convert them.
431
- let prevPort : PortDto | null = null ;
432
- return pathNodeKeys . slice ( 0 , - 1 ) . map ( ( sourceNodeKey , i ) => {
433
- // Get the source node or created it
434
- if ( ! ngeNodesByPathKey [ sourceNodeKey ] ) {
435
- ngeNodesByPathKey [ sourceNodeKey ] = castNodeToNge (
436
- state ,
437
- state . getNodeByKey ( sourceNodeKey ) ! ,
438
- labels
431
+ const startTime = new Date ( timetableItem . start_time ) ;
432
+ const returnStartTime = returnTimetableItem ? new Date ( returnTimetableItem . start_time ) : null ;
433
+
434
+ // OSRD describes the path in terms of nodes, NGE describes it in terms
435
+ // of sections between nodes. Iterate over path items two-by-two to
436
+ // convert them.
437
+ let prevPort : PortDto | null = null ;
438
+ return pathNodeKeys . slice ( 0 , - 1 ) . map ( ( sourceNodeKey , i ) => {
439
+ // returnTimetableItem contains the same path as timetableItem but in
440
+ // reverse order. `timetableItem.path.length - 1` is the index of the
441
+ // last path item, subtracting `i` will iterate from the end of the
442
+ // list to the start.
443
+ const returnIndex = timetableItem . path . length - 1 - i ;
444
+
445
+ // Get the source node or created it
446
+ if ( ! ngeNodesByPathKey [ sourceNodeKey ] ) {
447
+ ngeNodesByPathKey [ sourceNodeKey ] = castNodeToNge (
448
+ state ,
449
+ state . getNodeByKey ( sourceNodeKey ) ! ,
450
+ labels
451
+ ) ;
452
+ }
453
+ const sourceNode = ngeNodesByPathKey [ sourceNodeKey ] ;
454
+
455
+ // Get the target node or created it
456
+ const targetNodeKey = pathNodeKeys [ i + 1 ] ;
457
+ if ( ! ngeNodesByPathKey [ targetNodeKey ] ) {
458
+ ngeNodesByPathKey [ targetNodeKey ] = castNodeToNge (
459
+ state ,
460
+ state . getNodeByKey ( targetNodeKey ) ! ,
461
+ labels
462
+ ) ;
463
+ }
464
+ const targetNode = ngeNodesByPathKey [ targetNodeKey ] ;
465
+
466
+ // Adding port
467
+ const sourcePort = createPort ( trainrunSectionId ) ;
468
+ sourceNode . ports . push ( sourcePort ) ;
469
+ const targetPort = createPort ( trainrunSectionId ) ;
470
+ targetNode . ports . push ( targetPort ) ;
471
+
472
+ // Adding schedule
473
+ const sourceScheduleEntry = timetableItem . schedule ! . find (
474
+ ( entry ) => entry . at === timetableItem . path [ i ] . id
439
475
) ;
440
- }
441
- const sourceNode = ngeNodesByPathKey [ sourceNodeKey ] ;
442
-
443
- // Get the target node or created it
444
- const targetNodeKey = pathNodeKeys [ i + 1 ] ;
445
- if ( ! ngeNodesByPathKey [ targetNodeKey ] ) {
446
- ngeNodesByPathKey [ targetNodeKey ] = castNodeToNge (
447
- state ,
448
- state . getNodeByKey ( targetNodeKey ) ! ,
449
- labels
476
+ const targetScheduleEntry = timetableItem . schedule ! . find (
477
+ ( entry ) => entry . at === timetableItem . path [ i + 1 ] . id
478
+ ) ;
479
+ const returnSourceScheduleEntry = returnTimetableItem ?. schedule ?. find (
480
+ ( entry ) => entry . at === returnTimetableItem . path [ returnIndex ] . id
481
+ ) ;
482
+ const returnTargetScheduleEntry = returnTimetableItem ?. schedule ?. find (
483
+ ( entry ) => entry . at === returnTimetableItem . path [ returnIndex - 1 ] . id
450
484
) ;
451
- }
452
- const targetNode = ngeNodesByPathKey [ targetNodeKey ] ;
453
-
454
- // Adding port
455
- const sourcePort = createPort ( trainrunSectionId ) ;
456
- sourceNode . ports . push ( sourcePort ) ;
457
- const targetPort = createPort ( trainrunSectionId ) ;
458
- targetNode . ports . push ( targetPort ) ;
459
-
460
- // Adding schedule
461
- const sourceScheduleEntry = timetableItem . schedule ! . find (
462
- ( entry ) => entry . at === timetableItem . path [ i ] . id
463
- ) ;
464
- const targetScheduleEntry = timetableItem . schedule ! . find (
465
- ( entry ) => entry . at === timetableItem . path [ i + 1 ] . id
466
- ) ;
467
-
468
- // Create a transition between the previous section and the one we're creating
469
- if ( prevPort ) {
470
- const transition = createTransition ( prevPort . id , sourcePort . id ) ;
471
- transition . isNonStopTransit = ! sourceScheduleEntry ?. stop_for ;
472
- sourceNode . transitions . push ( transition ) ;
473
- }
474
- prevPort = targetPort ;
475
-
476
- let sourceDeparture ;
477
- if ( i === 0 ) {
478
- sourceDeparture = createTimeLock ( startTime , startTime ) ;
479
- } else {
480
- sourceDeparture = createDepartureTimeLock ( sourceScheduleEntry , startTime ) ;
481
- }
482
-
483
- const targetArrival = createArrivalTimeLock ( targetScheduleEntry , startTime ) ;
484
-
485
- const travelTime = { ...DEFAULT_TIME_LOCK } ;
486
- if ( targetArrival . consecutiveTime !== null && sourceDeparture . consecutiveTime !== null ) {
487
- travelTime . time = targetArrival . consecutiveTime - sourceDeparture . consecutiveTime ;
488
- travelTime . consecutiveTime = travelTime . time ;
489
- }
490
485
491
- const trainrunSection = {
492
- id : trainrunSectionId ,
493
- sourceNodeId : sourceNode . id ,
494
- sourcePortId : sourcePort . id ,
495
- targetNodeId : targetNode . id ,
496
- targetPortId : targetPort . id ,
497
- travelTime,
498
- sourceDeparture,
499
- sourceArrival : { ...DEFAULT_TIME_LOCK } ,
500
- targetDeparture : { ...DEFAULT_TIME_LOCK } ,
501
- targetArrival,
502
- numberOfStops : 0 ,
503
- trainrunId : index + 1 ,
504
- resourceId : state . ngeResource . id ,
505
- path : {
506
- path : [ ] ,
507
- textPositions : [ ] ,
508
- } ,
509
- specificTrainrunSectionFrequencyId : 0 ,
510
- warnings : [ ] ,
511
- } ;
486
+ // Create a transition between the previous section and the one we're creating
487
+ if ( prevPort ) {
488
+ const transition = createTransition ( prevPort . id , sourcePort . id ) ;
489
+ transition . isNonStopTransit = ! sourceScheduleEntry ?. stop_for ;
490
+ sourceNode . transitions . push ( transition ) ;
491
+ }
492
+ prevPort = targetPort ;
493
+
494
+ let sourceDeparture ;
495
+ if ( i === 0 ) {
496
+ sourceDeparture = createTimeLock ( startTime , startTime ) ;
497
+ } else {
498
+ sourceDeparture = createDepartureTimeLock ( sourceScheduleEntry , startTime ) ;
499
+ }
500
+
501
+ const targetArrival = createArrivalTimeLock ( targetScheduleEntry , startTime ) ;
502
+
503
+ let targetDeparture = { ...DEFAULT_TIME_LOCK } ;
504
+ if ( returnStartTime ) {
505
+ if ( returnIndex === 1 ) {
506
+ targetDeparture = createTimeLock ( returnStartTime , returnStartTime ) ;
507
+ } else {
508
+ targetDeparture = createDepartureTimeLock ( returnTargetScheduleEntry , returnStartTime ) ;
509
+ }
510
+ }
511
+
512
+ let sourceArrival = { ...DEFAULT_TIME_LOCK } ;
513
+ if ( returnStartTime ) {
514
+ sourceArrival = createArrivalTimeLock ( returnSourceScheduleEntry , returnStartTime ) ;
515
+ }
516
+
517
+ const travelTime = { ...DEFAULT_TIME_LOCK } ;
518
+ if ( targetArrival . consecutiveTime !== null && sourceDeparture . consecutiveTime !== null ) {
519
+ travelTime . time = targetArrival . consecutiveTime - sourceDeparture . consecutiveTime ;
520
+ travelTime . consecutiveTime = travelTime . time ;
521
+ }
522
+
523
+ const trainrunSection = {
524
+ id : trainrunSectionId ,
525
+ sourceNodeId : sourceNode . id ,
526
+ sourcePortId : sourcePort . id ,
527
+ targetNodeId : targetNode . id ,
528
+ targetPortId : targetPort . id ,
529
+ travelTime,
530
+ sourceDeparture,
531
+ sourceArrival,
532
+ targetDeparture,
533
+ targetArrival,
534
+ numberOfStops : 0 ,
535
+ trainrunId : index + 1 ,
536
+ resourceId : state . ngeResource . id ,
537
+ path : {
538
+ path : [ ] ,
539
+ textPositions : [ ] ,
540
+ } ,
541
+ specificTrainrunSectionFrequencyId : 0 ,
542
+ warnings : [ ] ,
543
+ } ;
512
544
513
- trainrunSectionId += 1 ;
514
- return trainrunSection ;
515
- } ) ;
516
- } ) ;
545
+ trainrunSectionId += 1 ;
546
+ return trainrunSection ;
547
+ } ) ;
548
+ }
549
+ ) ;
517
550
518
551
return {
519
552
trainrunSections,
@@ -540,12 +573,12 @@ const getNgeLabels = (state: MacroEditorState): LabelDto[] =>
540
573
*/
541
574
export const getNgeDto = (
542
575
state : MacroEditorState ,
543
- timetableItems : TimetableItem [ ]
576
+ groupedTimetableItems : ( readonly [ TimetableItem , TimetableItem | null ] ) [ ]
544
577
) : NetzgrafikDto => {
545
578
const labels = getNgeLabels ( state ) ;
546
579
return {
547
- ...getNgeTrainrunSectionsWithNodes ( state , timetableItems , labels ) ,
548
- trainruns : getNgeTrainruns ( state , timetableItems , labels ) ,
580
+ ...getNgeTrainrunSectionsWithNodes ( state , groupedTimetableItems , labels ) ,
581
+ trainruns : getNgeTrainruns ( state , groupedTimetableItems , labels ) ,
549
582
resources : [ state . ngeResource ] ,
550
583
metadata : {
551
584
netzgrafikColors : getNetzgrafikColors ( ) ,
@@ -580,6 +613,24 @@ const fetchTimetableItemPathOps = async (
580
613
return addPathOpsToTimetableItems ( timetableItems , opRefs , ops ) ;
581
614
} ;
582
615
616
+ const groupCompatibleRoundTrips = (
617
+ roundTripGroups : TimetableItemRoundTripGroups
618
+ ) : ( readonly [ TimetableItemWithPathOps , TimetableItemWithPathOps | null ] ) [ ] => {
619
+ const incompatible = [ ] ;
620
+ const compatible = [ ] ;
621
+ for ( const [ a , b ] of roundTripGroups . roundTrips ) {
622
+ if ( checkRoundTripCompatible ( a , b ) ) {
623
+ compatible . push ( [ a , b ] as const ) ;
624
+ } else {
625
+ incompatible . push ( a , b ) ;
626
+ }
627
+ }
628
+ const oneWays = [ ...roundTripGroups . oneWays , ...roundTripGroups . others , ...incompatible ] . map (
629
+ ( timetableItem ) => [ timetableItem , null ] as const
630
+ ) ;
631
+ return [ ...oneWays , ...compatible ] ;
632
+ } ;
633
+
583
634
export const loadNgeDto = async (
584
635
state : MacroEditorState ,
585
636
timetableId : number ,
@@ -618,6 +669,30 @@ export const loadNgeDto = async (
618
669
dispatch
619
670
) ;
620
671
672
+ const timetableItemsById = new Map (
673
+ timetableItems . map ( ( timetableItem ) => [ timetableItem . id , timetableItem ] )
674
+ ) ;
675
+
676
+ const trainScheduleRoundTripsPromise = dispatch (
677
+ osrdEditoastApi . endpoints . getTimetableByIdRoundTripsTrainSchedules . initiate (
678
+ { id : timetableId } ,
679
+ { subscribe : false }
680
+ )
681
+ ) ;
682
+ const pacedTrainRoundTripsPromise = dispatch (
683
+ osrdEditoastApi . endpoints . getTimetableByIdRoundTripsPacedTrains . initiate (
684
+ { id : timetableId } ,
685
+ { subscribe : false }
686
+ )
687
+ ) ;
688
+ const { results : trainScheduleRoundTrips } = await trainScheduleRoundTripsPromise . unwrap ( ) ;
689
+ const { results : pacedTrainRoundTrips } = await pacedTrainRoundTripsPromise . unwrap ( ) ;
690
+ const roundTripGroups = groupRoundTrips ( timetableItemsById , {
691
+ trainSchedules : trainScheduleRoundTrips ,
692
+ pacedTrains : pacedTrainRoundTrips ,
693
+ } ) ;
694
+ const groupedTimetableItems = groupCompatibleRoundTrips ( roundTripGroups ) ;
695
+
621
696
await loadAndIndexNge ( state , timetableItems , dispatch , t ) ;
622
- return getNgeDto ( state , timetableItems ) ;
697
+ return getNgeDto ( state , groupedTimetableItems ) ;
623
698
} ;
0 commit comments