Skip to content

Commit

Permalink
fix: 320 bug graphical timetable streckengrafik renders only one trai…
Browse files Browse the repository at this point in the history
…nrun segement (#325)

* Initial fix for sorting/orientation issue - partial progress
* Added documentation and comments
* Propagated consecutiveTime over each train run segment separately
* Removed debug console logs (clean)
* Fixed lint issues
* Updated CREATE_TRAINRUN.md with images
* Improved code (array.concat and indexOf) for data filtering
* Fixed train run part issue in pearls view
* Refactored and cleaned up code, added tests
* Enhanced UX and documentation
---------

Co-authored-by: Serge Croisé <[email protected]>
  • Loading branch information
aiAdrian and SergeCroise authored Oct 28, 2024
1 parent ff3e72c commit 790f53b
Show file tree
Hide file tree
Showing 13 changed files with 584 additions and 185 deletions.
28 changes: 28 additions & 0 deletions documentation/CREATE_TRAINRUN.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,31 @@ To switch a train from a stop to a non-stop at a node, follow these steps:
For more details have a look into
- [Split/Combine two trainruns](https://github.com/SchweizerischeBundesbahnen/netzgrafik-editor-frontend/blob/main/documentation/Split_Combine_Trainruns.md)
- [Merge Netzgrafik](https://github.com/SchweizerischeBundesbahnen/netzgrafik-editor-frontend/blob/main/documentation/Merge_Netzgrafik.md)

## Special cases

### Trainrun path with "holes" (missing sections)

![image](https://github.com/user-attachments/assets/d87b842c-7696-4e81-aa78-75cc966b5306)
*Example Netzgrafik with missing sections (See the cargo trainrun GTwo_Part_trainrun)*


When creating a trainrun, the trairnun path should connect all nodes from start to destination using trainrun sections.
However, it can happen during the creation that not all trainrun sections have been drawn in the meantime.
Gaps may occur along the trainrun path where at least one trainrun section is missing.
These “holes” usually occur if the trainrun path has not yet been drawn completely or correctly.
For example, a trainrun path could look like this with a "hole" in the middle,

A - B - C ---- (missing section) ---- E - F - G

The trainrun section between C and E is missing here. However, these gaps can also occur if a partial cancelation is made for a train run.

![image](https://github.com/user-attachments/assets/5d1ef657-e421-41ff-ae57-622eee82f295)
*Graphical timetable.*


In each of these cases, the trainrun has at least two parts, e.g. [(A B), (A C)] and [(E F)(F G)].
In this case, the trainrun is interpreted as two separate trainruns.
The Netzgrafik has no information about whether the missing part could be closed between C - E, C - D - E or
another possible variant, therefore the assumption that there is an independent trainrun for each trainrun part is
the best assumption. This avoids many new problems.
3 changes: 3 additions & 0 deletions documentation/USERMANUAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,6 @@ creation of comprehensive and visually appealing network representations.
## Links
- [Netzgrafik-Editor data export/import (JSON)](DATA_MODEL_JSON.md)
- [DATA MODEL](DATA_MODEL.md)

## Technical documentation
- [Trainrun iterator](/technical/trainrun_iterations.md)
100 changes: 100 additions & 0 deletions documentation/technical/trainrun_iterations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Trainrun iterator

## Sample code

https://github.com/SchweizerischeBundesbahnen/netzgrafik-editor-frontend/pull/325

### Simple trainrun iterator - just one trainrun part
To iterate starting from a node of interest with the orientation passed through the trainrun section, you can use the sample code (pattern) below.
The iteration will proceed along the trainrun. Be aware that this does not ensure traveling through the full train run.

https://github.com/SchweizerischeBundesbahnen/netzgrafik-editor-frontend/blob/main/documentation/CREATE_TRAINRUN.md#trainrun-path-with-holes-missing-sections

```typescript

// create forward iterator
const iterator: TrainrunIterator = this.trainrunService.getIterator(
startForwardNode,
startTrainrunSection,
);
while (iterator.hasNext()) {
// move iterator forward
const currentTrainrunSectionNodePair = iterator.next();

// get data
const trainrunSection = currentTrainrunSectionNodePair.trainrunSection;
const node = currentTrainrunSectionNodePair.node;

...
user
defined
data
processing
...

}
}
```

### Complete Train Run Iterator - Over All Train Run Parts
Iterator pattern for iterating through all train run parts separately.

```typescript
getForwardTrainrunPartIterator(trainrunSection : TrainrunSection) {
// find both start nodes ( N1 - N2 - N3 - N4 ) => N1 , N2
const bothEndNodes =
this.trainrunService.getBothEndNodesFromTrainrunPart(trainrunSection);

// get start / end node from Top/Left -> Botton / Right
const startForwardNode = GeneralViewFunctions.getLeftOrTopNode(
bothEndNodes.endNode1,
bothEndNodes.endNode2,
);

// get start trainrun section -> forward direction/orientation
const startTrainrunSection = startForwardNode.getStartTrainrunSection(
trainrun.getId(),
);

// create forward iterator
const iterator: TrainrunIterator = this.trainrunService.getIterator(
startForwardNode,
startTrainrunSection,
);
return iterator;
}

simpleTrainrunAllPartsIterator(trainrun: Trainrun) {
let alltrainrunsections =
this.trainrunSectionService
.getAllTrainrunSectionsForTrainrun(trainrun.getId());

while (alltrainrunsections.length > 0) {
// traverse over all trainrun parts
const iterator= getForwardTrainrunPartIterator(alltrainrunsections[0]);

while (iterator.hasNext()) {
// move iterator forward
const currentTrainrunSectionNodePair = iterator.next();

// get data
const trainrunSection = currentTrainrunSectionNodePair.trainrunSection;
const node = currentTrainrunSectionNodePair.node;

...
user
defined
data
processing
...

// filter all still visited trainrun sections
alltrainrunsections = alltrainrunsections.filter(ts =>
ts.getId() !== trainrunSection.getId()
);
}


}
}
```
16 changes: 15 additions & 1 deletion src/app/perlenkette/model/perlenkette-tests.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,18 @@ import {PerlenketteSection} from "./perlenketteSection";

describe("PerlenketteModelTests", () => {
it("Perlenkette-Model - Test - PerlenketteNode - 001", () => {
const node = new PerlenketteNode(0, "BN", "Berm", 10, [], undefined);
const node = new PerlenketteNode(0,
"BN",
"Berm",
10,
[],
undefined,
false,
true
);

expect(node.isFristTrainrunPartNode()).toBe(false);
expect(node.isLastTrainrunPartNode()).toBe(true);
expect(node.isPerlenketteNode()).toBe(true);
expect(node.isPerlenketteSection()).toBe(false);
expect(node.getPerlenketteNode()).toEqual(node);
Expand All @@ -19,8 +29,12 @@ describe("PerlenketteModelTests", () => {
undefined,
0,
false,
false,
true
);

expect(section.isFristTrainrunPartSection()).toBe(false);
expect(section.isLastTrainrunPartSection()).toBe(true);
expect(section.isPerlenketteNode()).toBe(false);
expect(section.isPerlenketteSection()).toBe(true);
expect(section.getPerlenketteNode()).toEqual(undefined);
Expand Down
17 changes: 16 additions & 1 deletion src/app/perlenkette/model/perlenketteNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,22 @@ export class PerlenketteNode implements PerlenketteItem {
public connectionTime: number,
public connections: PerlenketteConnection[],
public transition: Transition,
) {}
public fristTrainrunPartNode: boolean,
public lastTrainrunPartNode: boolean
) {
}

isFristTrainrunPartNode(): boolean {
return this.fristTrainrunPartNode;
}

isLastTrainrunPartNode(): boolean {
return this.lastTrainrunPartNode;
}

setLastTrainrunPartNode(flag : boolean) {
this.lastTrainrunPartNode = flag;
}

isPerlenketteNode(): boolean {
return true;
Expand Down
17 changes: 16 additions & 1 deletion src/app/perlenkette/model/perlenketteSection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,22 @@ export class PerlenketteSection implements PerlenketteItem {
public toNode: Node,
public numberOfStops: number,
public isBeingEdited: boolean = false,
) {}
public fristTrainrunPartSection: boolean = false,
public lastTrainrunPartSection: boolean = false
) {
}

isFristTrainrunPartSection(): boolean {
return this.fristTrainrunPartSection;
}

isLastTrainrunPartSection(): boolean {
return this.lastTrainrunPartSection;
}

setLastTrainrunPartSection(flag : boolean) {
this.lastTrainrunPartSection = flag;
}

isPerlenketteNode(): boolean {
return false;
Expand Down
13 changes: 10 additions & 3 deletions src/app/perlenkette/perlenkette.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,10 @@
(click)="goto(pathItem)"
>{{ pathItem.getPerlenketteNode().shortName }}</span
>
</p>

<ng-container *ngIf="isLastNodeButNotVeryLast(pathItem)">
<span [class]="getSmallstationClassTag(pathItem)"> &#x2022; </span>
</ng-container>
</div>
</ng-container>

Expand Down Expand Up @@ -126,6 +129,10 @@
[isBottomNode]="idx === perlenketteTrainrun.pathItems.length-1"
>
</sbb-perlenkette-node>
<div *ngIf="doSplitTrainrun(perlenketteTrainrun.pathItems, idx)">
<br>
<br>
</div>
<sbb-perlenkette-section
*ngIf="pathItem.isPerlenketteSection()"
[perlenketteSection]="pathItem.getPerlenketteSection()"
Expand All @@ -136,8 +143,8 @@
[notificationIsBeingEdited]="
getSignalAllChildrenIsBeingEditedObservable()
"
[isFirstSection]="idx === 1"
[isLastSection]="idx === perlenketteTrainrun.pathItems.length-2"
[isFirstSection]="isFirstSection(pathItem)"
[isLastSection]="isLastSection(pathItem)"
>
</sbb-perlenkette-section>
</ng-container>
Expand Down
44 changes: 42 additions & 2 deletions src/app/perlenkette/perlenkette.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export class PerlenketteComponent implements AfterContentChecked, OnDestroy {
readonly filterService: FilterService,
private readonly uiInteractionService: UiInteractionService,
private readonly nodeService: NodeService,
private versionControlService : VersionControlService,
private versionControlService: VersionControlService,
private changeDetectorRef: ChangeDetectorRef,
) {
this.selectedPerlenketteConnection = undefined;
Expand Down Expand Up @@ -215,6 +215,15 @@ export class PerlenketteComponent implements AfterContentChecked, OnDestroy {
this.signalAllChildrenIsBeingEditedSubject.next(event);
}

doSplitTrainrun(pathItems: PerlenketteItem[], idx: number): boolean {
if (idx >= pathItems.length - 1) {
return false;
}
const item = pathItems[idx];
const previousItem = pathItems[idx + 1];
return item.isPerlenketteNode() === previousItem.isPerlenketteNode();
}

signalHeightChanged(height: number, pathItem: PerlenketteItem) {
this.perlenketteRenderingElementsHeight.push([pathItem, height]);
this.renderedElementsHeight = 0;
Expand All @@ -225,11 +234,42 @@ export class PerlenketteComponent implements AfterContentChecked, OnDestroy {
});
}

isFirstSection(item: PerlenketteItem): boolean {
if (item.isPerlenketteSection()) {
const psi = item.getPerlenketteSection();
return psi.isFristTrainrunPartSection();
}
return false;
}

isLastSection(item: PerlenketteItem): boolean {
if (item.isPerlenketteSection()) {
const psi = item.getPerlenketteSection();
return psi.isLastTrainrunPartSection();
}
return false;
}

isLastNode(item: PerlenketteItem): boolean {
if (item.isPerlenketteNode()) {
const pni = item.getPerlenketteNode();
return pni.isLastTrainrunPartNode();
}
return false;
}

isLastNodeButNotVeryLast(item: PerlenketteItem) {
if (this.perlenketteTrainrun.pathItems.indexOf(item) === this.perlenketteTrainrun.pathItems.length - 1) {
return false;
}
return this.isLastNode(item);
}

getSignalAllChildrenIsBeingEditedObservable() {
return this.signalAllChildrenIsBeingEditedSubject.asObservable();
}

getVariantIsWritable() : boolean {
getVariantIsWritable(): boolean {
return this.versionControlService.getVariantIsWritable();
}

Expand Down
Loading

0 comments on commit 790f53b

Please sign in to comment.