From 62fe845671a1d40aad83e623193832d24c73a533 Mon Sep 17 00:00:00 2001 From: u221638 Date: Thu, 9 Jan 2025 13:33:02 +0100 Subject: [PATCH] feat: added SFERA mapping for graduated station speed (#82) --- das_client/.gitignore | 2 + das_client/ios/Podfile.lock | 4 +- .../train_journey/train_journey_overview.dart | 7 - das_client/lib/model/journey/curve_point.dart | 2 +- .../journey/graduated_station_speeds.dart | 41 +++++ .../lib/model/journey/service_point.dart | 5 +- das_client/lib/model/journey/speed.dart | 35 +++++ .../lib/model/journey/station_speed_data.dart | 15 ++ .../sfera/src/mapper/sfera_model_mapper.dart | 52 +++++-- .../sfera/src/model/graduated_speed_info.dart | 10 ++ .../model/graduated_speed_info_entity.dart | 17 ++ .../src/model/network_specific_parameter.dart | 3 + .../sfera/src/model/station_speed_nsp.dart | 3 + .../src/model/xml_graduated_speed_info.dart | 9 ++ .../lib/sfera/src/sfera_reply_parser.dart | 6 + .../graduated_station_speeds_test.dart | 145 ++++++++++++++++++ .../test/sfera/mapper/sfera_mapper_test.dart | 104 ++++++++++++- .../SFERA_JP_T4.xml | 40 +++++ .../SFERA_SP_T4_1.xml | 135 ++++++++++++++++ .../SFERA_TC_T4_1.xml | 8 + 20 files changed, 621 insertions(+), 22 deletions(-) create mode 100644 das_client/lib/model/journey/graduated_station_speeds.dart create mode 100644 das_client/lib/model/journey/speed.dart create mode 100644 das_client/lib/model/journey/station_speed_data.dart create mode 100644 das_client/lib/sfera/src/model/graduated_speed_info.dart create mode 100644 das_client/lib/sfera/src/model/graduated_speed_info_entity.dart create mode 100644 das_client/lib/sfera/src/model/xml_graduated_speed_info.dart create mode 100644 das_client/test/model/journey/graduated_station_speeds_test.dart create mode 100644 das_client/test_resources/T4_graduated_station_speeds/SFERA_JP_T4.xml create mode 100644 das_client/test_resources/T4_graduated_station_speeds/SFERA_SP_T4_1.xml create mode 100644 das_client/test_resources/T4_graduated_station_speeds/SFERA_TC_T4_1.xml diff --git a/das_client/.gitignore b/das_client/.gitignore index 306a60d4..417046a6 100644 --- a/das_client/.gitignore +++ b/das_client/.gitignore @@ -7,9 +7,11 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ migrate_working_dir/ # IntelliJ related diff --git a/das_client/ios/Podfile.lock b/das_client/ios/Podfile.lock index 2e39e64c..1318dfb1 100644 --- a/das_client/ios/Podfile.lock +++ b/das_client/ios/Podfile.lock @@ -57,13 +57,13 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: AppAuth: 501c04eda8a8d11f179dbe8637b7a91bb7e5d2fa - device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d + device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_appauth: aef998cfbcc307dff7f2fbe1f59a50323748dc25 flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573 isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073 - package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c + package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 PODFILE CHECKSUM: d9dad56c0cd0b4fd8b4fe3034a53fd42a0b990f6 diff --git a/das_client/lib/app/pages/journey/train_journey/train_journey_overview.dart b/das_client/lib/app/pages/journey/train_journey/train_journey_overview.dart index 23ae9000..e49d7457 100644 --- a/das_client/lib/app/pages/journey/train_journey/train_journey_overview.dart +++ b/das_client/lib/app/pages/journey/train_journey/train_journey_overview.dart @@ -1,11 +1,8 @@ -import 'package:das_client/app/pages/journey/train_journey/widgets/header/adl_notification.dart'; import 'package:das_client/app/pages/journey/train_journey/widgets/header/header.dart'; import 'package:das_client/app/pages/journey/train_journey/widgets/train_journey.dart'; -import 'package:sbb_design_system_mobile/sbb_design_system_mobile.dart'; import 'package:flutter/material.dart'; // TODO: handle extraLarge font sizes (diff to figma) globally. -// TODO: Add testing class TrainJourneyOverview extends StatelessWidget { const TrainJourneyOverview({super.key}); @@ -14,10 +11,6 @@ class TrainJourneyOverview extends StatelessWidget { return const Column( children: [ Header(), - ADLNotification( - message: 'VMax fahren bis Wettingen', - margin: EdgeInsets.fromLTRB(sbbDefaultSpacing * 0.5, 0, sbbDefaultSpacing * 0.5, sbbDefaultSpacing), - ), Expanded(child: TrainJourney()), ], ); diff --git a/das_client/lib/model/journey/curve_point.dart b/das_client/lib/model/journey/curve_point.dart index de4f7dcd..116d674a 100644 --- a/das_client/lib/model/journey/curve_point.dart +++ b/das_client/lib/model/journey/curve_point.dart @@ -37,7 +37,7 @@ enum CurvePointType { } } -/// Type of curve. Is provided only if curvePointType = 'begin' +/// Type of curve. Is provided only if type is [CurvePointType.begin] enum CurveType { /// begins on the line and ends on the line or a station or a halt curve, diff --git a/das_client/lib/model/journey/graduated_station_speeds.dart b/das_client/lib/model/journey/graduated_station_speeds.dart new file mode 100644 index 00000000..eeea7ba9 --- /dev/null +++ b/das_client/lib/model/journey/graduated_station_speeds.dart @@ -0,0 +1,41 @@ +import 'package:das_client/model/journey/speed.dart'; +import 'package:das_client/model/journey/train_series.dart'; + +class GraduatedStationSpeeds { + GraduatedStationSpeeds({ + required this.trainSeries, + this.text, + this.incomingSpeeds = const [], + this.outgoingSpeeds = const [], + }) : assert(trainSeries.isNotEmpty); + + final List trainSeries; + final String? text; + final List incomingSpeeds; + final List outgoingSpeeds; + + factory GraduatedStationSpeeds.from(List trainSeries, String speedString, {String? text}) { + final parts = speedString.split('/'); + final incomingSpeeds = parts[0].split('-'); + final outgoingSpeeds = parts.length > 1 ? parts[1].split('-') : []; + + // validate station speed format + final speedRegex = RegExp(Speed.speedRegex); + final formatSatisfied = [...incomingSpeeds, ...outgoingSpeeds].every((speed) => speedRegex.hasMatch(speed)); + if (incomingSpeeds.isNotEmpty && !formatSatisfied) { + throw ArgumentError('Invalid graduated station speed format: $speedString'); + } + + return GraduatedStationSpeeds( + trainSeries: trainSeries, + text: text, + incomingSpeeds: incomingSpeeds.map((speed) => Speed.from(speed)).toList(), + outgoingSpeeds: outgoingSpeeds.map((speed) => Speed.from(speed)).toList(), + ); + } + + @override + String toString() { + return 'GraduatedStationSpeeds(trainSeries: $trainSeries, incomingSpeeds: $incomingSpeeds, outgoingSpeeds: $outgoingSpeeds)'; + } +} diff --git a/das_client/lib/model/journey/service_point.dart b/das_client/lib/model/journey/service_point.dart index 36b6d91b..2e7914a8 100644 --- a/das_client/lib/model/journey/service_point.dart +++ b/das_client/lib/model/journey/service_point.dart @@ -1,6 +1,7 @@ import 'package:das_client/model/journey/base_data.dart'; import 'package:das_client/model/journey/bracket_station.dart'; import 'package:das_client/model/journey/datatype.dart'; +import 'package:das_client/model/journey/station_speed_data.dart'; import 'package:das_client/model/localized_string.dart'; class ServicePoint extends BaseData { @@ -13,6 +14,7 @@ class ServicePoint extends BaseData { this.isStop = false, this.isStation = false, this.bracketStation, + this.stationSpeedData, }) : super(type: Datatype.servicePoint); final LocalizedString name; @@ -20,9 +22,10 @@ class ServicePoint extends BaseData { final bool isStop; final bool isStation; final BracketStation? bracketStation; + final StationSpeedData? stationSpeedData; @override String toString() { - return 'ServicePoint(order: $order, kilometre: $kilometre, name: $name, mandatoryStop: $mandatoryStop, isStop: $isStop, isStation: $isStation, bracketStation: $bracketStation, speedData: $speedData)'; + return 'ServicePoint(order: $order, kilometre: $kilometre, name: $name, mandatoryStop: $mandatoryStop, isStop: $isStop, isStation: $isStation, bracketStation: $bracketStation, speedData: $speedData, stationSpeedData: $stationSpeedData)'; } } diff --git a/das_client/lib/model/journey/speed.dart b/das_client/lib/model/journey/speed.dart new file mode 100644 index 00000000..fde4d954 --- /dev/null +++ b/das_client/lib/model/journey/speed.dart @@ -0,0 +1,35 @@ +class Speed { + const Speed({ + required this.speed, + this.isSquared = false, + this.isCircled = false, + }); + + factory Speed.from(String value) { + if (RegExp(speedRegex).hasMatch(value)) { + final digit = RegExp(r'\d+').stringMatch(value); + return Speed( + speed: int.parse(digit!), + isCircled: value.contains('{'), + isSquared: value.contains('['), + ); + } + + throw ArgumentError('Invalid speed format: $value'); + } + + static const speedRegex = r'^\d+$|^\[\d+\]$|^\{\d+\}$'; // exp. 50, {60} or [70] + + final int speed; + + /// Normally means that speed is higher than given by signaling. Can have other meanings so this variable is named generically. + final bool isSquared; + + /// Normally means that speed is lower than given by signaling. Can have other meanings so this variable is named generically. + final bool isCircled; + + @override + String toString() { + return 'Speed(speed: $speed, isSquared: $isSquared, isCircled: $isCircled)'; + } +} diff --git a/das_client/lib/model/journey/station_speed_data.dart b/das_client/lib/model/journey/station_speed_data.dart new file mode 100644 index 00000000..eb9f3074 --- /dev/null +++ b/das_client/lib/model/journey/station_speed_data.dart @@ -0,0 +1,15 @@ +import 'package:collection/collection.dart'; +import 'package:das_client/model/journey/graduated_station_speeds.dart'; +import 'package:das_client/model/journey/train_series.dart'; + +class StationSpeedData { + StationSpeedData({this.graduatedStationSpeeds = const []}); + + final List graduatedStationSpeeds; + + GraduatedStationSpeeds? graduatedSpeedsFor(TrainSeries? trainSeries) { + if (trainSeries == null) return null; + + return graduatedStationSpeeds.firstWhereOrNull((it) => it.trainSeries.contains(trainSeries)); + } +} diff --git a/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart b/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart index 4f62f9fe..0cf8042c 100644 --- a/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart +++ b/das_client/lib/sfera/src/mapper/sfera_model_mapper.dart @@ -8,6 +8,7 @@ import 'package:das_client/model/journey/cab_signaling.dart'; import 'package:das_client/model/journey/connection_track.dart'; import 'package:das_client/model/journey/curve_point.dart'; import 'package:das_client/model/journey/datatype.dart'; +import 'package:das_client/model/journey/graduated_station_speeds.dart'; import 'package:das_client/model/journey/journey.dart'; import 'package:das_client/model/journey/metadata.dart'; import 'package:das_client/model/journey/protection_section.dart'; @@ -15,7 +16,9 @@ import 'package:das_client/model/journey/service_point.dart'; import 'package:das_client/model/journey/signal.dart'; import 'package:das_client/model/journey/speed_change.dart'; import 'package:das_client/model/journey/speed_data.dart'; +import 'package:das_client/model/journey/station_speed_data.dart'; import 'package:das_client/model/journey/track_equipment.dart'; +import 'package:das_client/model/journey/train_series.dart'; import 'package:das_client/model/journey/velocity.dart'; import 'package:das_client/model/localized_string.dart'; import 'package:das_client/sfera/src/mapper/track_equipment_mapper.dart'; @@ -24,6 +27,7 @@ import 'package:das_client/sfera/src/model/enums/start_end_qualifier.dart'; import 'package:das_client/sfera/src/model/enums/stop_skip_pass.dart'; import 'package:das_client/sfera/src/model/enums/taf_tap_location_type.dart'; import 'package:das_client/sfera/src/model/enums/xml_enum.dart'; +import 'package:das_client/sfera/src/model/graduated_speed_info.dart'; import 'package:das_client/sfera/src/model/journey_profile.dart'; import 'package:das_client/sfera/src/model/multilingual_text.dart'; import 'package:das_client/sfera/src/model/network_specific_parameter.dart'; @@ -96,14 +100,17 @@ class SferaModelMapper { .first; journeyData.add(ServicePoint( - name: _localizedStringFromMultilingualText(tafTapLocation.locationNames), - order: calculateOrder(segmentIndex, timingPoint.location), - mandatoryStop: tpConstraint.stoppingPointInformation?.stopType?.mandatoryStop ?? true, - isStop: tpConstraint.stopSkipPass == StopSkipPass.stoppingPoint, - isStation: tafTapLocation.locationType != TafTapLocationType.stoppingLocation, - bracketStation: _parseBracketStation(tafTapLocations, tafTapLocation), - kilometre: kilometreMap[timingPoint.location] ?? [], - speedData: _speedDataFromSpeeds(tafTapLocation.stationSpeed?.xmlStationSpeed.element))); + name: _localizedStringFromMultilingualText(tafTapLocation.locationNames), + order: calculateOrder(segmentIndex, timingPoint.location), + mandatoryStop: tpConstraint.stoppingPointInformation?.stopType?.mandatoryStop ?? true, + isStop: tpConstraint.stopSkipPass == StopSkipPass.stoppingPoint, + isStation: tafTapLocation.locationType != TafTapLocationType.stoppingLocation, + bracketStation: _parseBracketStation(tafTapLocations, tafTapLocation), + kilometre: kilometreMap[timingPoint.location] ?? [], + speedData: _speedDataFromSpeeds(tafTapLocation.stationSpeed?.xmlStationSpeed.element), + stationSpeedData: + _stationSpeedDataFromGraduatedSpeedInfo(tafTapLocation.stationSpeed?.xmlGraduatedSpeedInfo?.element), + )); } _parseAndAddProtectionSections(journeyData, segmentIndex, segmentProfile, kilometreMap); @@ -381,11 +388,36 @@ class SferaModelMapper { .toList()); } + static StationSpeedData? _stationSpeedDataFromGraduatedSpeedInfo(GraduatedSpeedInfo? graduatedSpeedInfo) { + if (graduatedSpeedInfo == null) return null; + + final graduatedStationSpeeds = []; + for (final entity in graduatedSpeedInfo.entities) { + if (entity.adSpeed != null) { + final speeds = GraduatedStationSpeeds.from([TrainSeries.A, TrainSeries.D], entity.adSpeed!, text: entity.text); + graduatedStationSpeeds.add(speeds); + } + if (entity.nSpeed != null) { + final speeds = GraduatedStationSpeeds.from([TrainSeries.N], entity.nSpeed!, text: entity.text); + graduatedStationSpeeds.add(speeds); + } + if (entity.sSpeed != null) { + final speeds = GraduatedStationSpeeds.from([TrainSeries.S], entity.sSpeed!, text: entity.text); + graduatedStationSpeeds.add(speeds); + } + if (entity.roSpeed != null) { + final speeds = GraduatedStationSpeeds.from([TrainSeries.R, TrainSeries.O], entity.roSpeed!, text: entity.text); + graduatedStationSpeeds.add(speeds); + } + } + + return StationSpeedData(graduatedStationSpeeds: graduatedStationSpeeds); + } + static Set _parseAvailableBreakSeries(List journeyData) { return journeyData .where((it) => it.speedData != null) - .map((it) => it.speedData!.velocities) - .expand((it) => it) + .expand((it) => it.speedData!.velocities) .where((it) => it.breakSeries != null) .map((it) => BreakSeries(trainSeries: it.trainSeries, breakSeries: it.breakSeries!)) .toSet(); diff --git a/das_client/lib/sfera/src/model/graduated_speed_info.dart b/das_client/lib/sfera/src/model/graduated_speed_info.dart new file mode 100644 index 00000000..60612d09 --- /dev/null +++ b/das_client/lib/sfera/src/model/graduated_speed_info.dart @@ -0,0 +1,10 @@ +import 'package:das_client/sfera/src/model/graduated_speed_info_entity.dart'; +import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; + +class GraduatedSpeedInfo extends SferaXmlElement { + static const String elementType = 'entries'; + + GraduatedSpeedInfo({super.type = elementType, super.attributes, super.children, super.value}); + + Iterable get entities => children.whereType(); +} diff --git a/das_client/lib/sfera/src/model/graduated_speed_info_entity.dart b/das_client/lib/sfera/src/model/graduated_speed_info_entity.dart new file mode 100644 index 00000000..4dadcacc --- /dev/null +++ b/das_client/lib/sfera/src/model/graduated_speed_info_entity.dart @@ -0,0 +1,17 @@ +import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; + +class GraduatedSpeedInfoEntity extends SferaXmlElement { + static const String elementType = 'entry'; + + GraduatedSpeedInfoEntity({super.type = elementType, super.attributes, super.children, super.value}); + + String? get nSpeed => attributes['nSpeed']; + + String? get roSpeed => attributes['roSpeed']; + + String? get adSpeed => attributes['adSpeed']; + + String? get sSpeed => attributes['sSpeed']; + + String? get text => attributes['text']; +} diff --git a/das_client/lib/sfera/src/model/network_specific_parameter.dart b/das_client/lib/sfera/src/model/network_specific_parameter.dart index ea536adf..0f6aa60b 100644 --- a/das_client/lib/sfera/src/model/network_specific_parameter.dart +++ b/das_client/lib/sfera/src/model/network_specific_parameter.dart @@ -1,6 +1,7 @@ import 'package:das_client/sfera/src/model/sfera_xml_element.dart'; import 'package:das_client/sfera/src/model/track_equipment_type_wrapper.dart'; import 'package:das_client/sfera/src/model/xml_curve_speed.dart'; +import 'package:das_client/sfera/src/model/xml_graduated_speed_info.dart'; import 'package:das_client/sfera/src/model/xml_new_line_speed.dart'; import 'package:das_client/sfera/src/model/xml_station_speed.dart'; @@ -19,6 +20,8 @@ class NetworkSpecificParameter extends SferaXmlElement { return XmlCurveSpeed(attributes: attributes, children: children, value: value); } else if (attributes?['name'] == XmlStationSpeed.elementName) { return XmlStationSpeed(attributes: attributes, children: children, value: value); + } else if (attributes?['name'] == XmlGraduatedSpeedInfo.elementName) { + return XmlGraduatedSpeedInfo(attributes: attributes, children: children, value: value); } return NetworkSpecificParameter(attributes: attributes, children: children, value: value); } diff --git a/das_client/lib/sfera/src/model/station_speed_nsp.dart b/das_client/lib/sfera/src/model/station_speed_nsp.dart index 804c1c8f..6e81889c 100644 --- a/das_client/lib/sfera/src/model/station_speed_nsp.dart +++ b/das_client/lib/sfera/src/model/station_speed_nsp.dart @@ -1,4 +1,5 @@ import 'package:das_client/sfera/src/model/taf_tap_location_nsp.dart'; +import 'package:das_client/sfera/src/model/xml_graduated_speed_info.dart'; import 'package:das_client/sfera/src/model/xml_station_speed.dart'; class StationSpeedNsp extends TafTapLocationNsp { @@ -8,6 +9,8 @@ class StationSpeedNsp extends TafTapLocationNsp { XmlStationSpeed get xmlStationSpeed => children.whereType().first; + XmlGraduatedSpeedInfo? get xmlGraduatedSpeedInfo => children.whereType().firstOrNull; + @override bool validate() { return validateHasChildOfType() && super.validate(); diff --git a/das_client/lib/sfera/src/model/xml_graduated_speed_info.dart b/das_client/lib/sfera/src/model/xml_graduated_speed_info.dart new file mode 100644 index 00000000..c660d075 --- /dev/null +++ b/das_client/lib/sfera/src/model/xml_graduated_speed_info.dart @@ -0,0 +1,9 @@ +import 'package:das_client/sfera/src/model/graduated_speed_info.dart'; +import 'package:das_client/sfera/src/model/network_specific_parameter.dart'; +import 'package:das_client/sfera/src/model/nsp_xml_element.dart'; + +class XmlGraduatedSpeedInfo extends NetworkSpecificParameter with NspXmlElement { + static const String elementName = 'xmlGraduatedSpeedInfo'; + + XmlGraduatedSpeedInfo({super.attributes, super.children, super.value}); +} diff --git a/das_client/lib/sfera/src/sfera_reply_parser.dart b/das_client/lib/sfera/src/sfera_reply_parser.dart index 2a5dfdd1..04c52b08 100644 --- a/das_client/lib/sfera/src/sfera_reply_parser.dart +++ b/das_client/lib/sfera/src/sfera_reply_parser.dart @@ -6,6 +6,8 @@ import 'package:das_client/sfera/src/model/current_limitation_start.dart'; import 'package:das_client/sfera/src/model/curve_speed.dart'; import 'package:das_client/sfera/src/model/das_operating_modes_selected.dart'; import 'package:das_client/sfera/src/model/g2b_reply_payload.dart'; +import 'package:das_client/sfera/src/model/graduated_speed_info.dart'; +import 'package:das_client/sfera/src/model/graduated_speed_info_entity.dart'; import 'package:das_client/sfera/src/model/handshake_acknowledgement.dart'; import 'package:das_client/sfera/src/model/handshake_reject.dart'; import 'package:das_client/sfera/src/model/journey_profile.dart'; @@ -200,6 +202,10 @@ class SferaReplyParser { return TrainCharacteristics(type: type, attributes: attributes, children: children, value: value); case TcFeatures.elementType: return TcFeatures(type: type, attributes: attributes, children: children, value: value); + case GraduatedSpeedInfoEntity.elementType: + return GraduatedSpeedInfoEntity(type: type, attributes: attributes, children: children, value: value); + case GraduatedSpeedInfo.elementType: + return GraduatedSpeedInfo(type: type, attributes: attributes, children: children, value: value); default: return SferaXmlElement(type: type, attributes: attributes, children: children, value: value); } diff --git a/das_client/test/model/journey/graduated_station_speeds_test.dart b/das_client/test/model/journey/graduated_station_speeds_test.dart new file mode 100644 index 00000000..dceafbf7 --- /dev/null +++ b/das_client/test/model/journey/graduated_station_speeds_test.dart @@ -0,0 +1,145 @@ +import 'package:das_client/model/journey/graduated_station_speeds.dart'; +import 'package:das_client/model/journey/speed.dart'; +import 'package:das_client/model/journey/train_series.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('test with only incoming station speeds', () { + // GIVEN WHEN + final speed1 = GraduatedStationSpeeds.from([TrainSeries.R], '100'); + final speed2 = GraduatedStationSpeeds.from([TrainSeries.R], '100-90'); + final speed3 = GraduatedStationSpeeds.from([TrainSeries.R], '100-90-80'); + + // THEN + expect(speed1.incomingSpeeds, hasLength(1)); + checkSpeed(speed1.incomingSpeeds[0], 100); + expect(speed1.outgoingSpeeds, isEmpty); + + expect(speed2.incomingSpeeds, hasLength(2)); + checkSpeed(speed2.incomingSpeeds[0], 100); + checkSpeed(speed2.incomingSpeeds[1], 90); + expect(speed2.outgoingSpeeds, isEmpty); + + expect(speed3.incomingSpeeds, hasLength(3)); + checkSpeed(speed3.incomingSpeeds[0], 100); + checkSpeed(speed3.incomingSpeeds[1], 90); + checkSpeed(speed3.incomingSpeeds[2], 80); + expect(speed3.outgoingSpeeds, isEmpty); + }); + test('test with incoming and outgoing station speeds', () { + // GIVEN WHEN + final speed1 = GraduatedStationSpeeds.from([TrainSeries.R], '100/70'); + final speed2 = GraduatedStationSpeeds.from([TrainSeries.R], '100-90/70'); + final speed3 = GraduatedStationSpeeds.from([TrainSeries.R], '100-90-80/70'); + final speed4 = GraduatedStationSpeeds.from([TrainSeries.R], '100/70-60'); + final speed5 = GraduatedStationSpeeds.from([TrainSeries.R], '100-90/70-60'); + final speed6 = GraduatedStationSpeeds.from([TrainSeries.R], '100-90-80/70-60'); + final speed7 = GraduatedStationSpeeds.from([TrainSeries.R], '100/70-60-50'); + final speed8 = GraduatedStationSpeeds.from([TrainSeries.R], '100-90/70-60-50'); + final speed9 = GraduatedStationSpeeds.from([TrainSeries.R], '100-90-80/70-60-50'); + + // THEN + expect(speed1.incomingSpeeds, hasLength(1)); + checkSpeed(speed1.incomingSpeeds[0], 100); + expect(speed1.outgoingSpeeds, hasLength(1)); + checkSpeed(speed1.outgoingSpeeds[0], 70); + + expect(speed2.incomingSpeeds, hasLength(2)); + checkSpeed(speed2.incomingSpeeds[0], 100); + checkSpeed(speed2.incomingSpeeds[1], 90); + expect(speed2.outgoingSpeeds, hasLength(1)); + checkSpeed(speed2.outgoingSpeeds[0], 70); + + expect(speed3.incomingSpeeds, hasLength(3)); + checkSpeed(speed3.incomingSpeeds[0], 100); + checkSpeed(speed3.incomingSpeeds[1], 90); + checkSpeed(speed3.incomingSpeeds[2], 80); + expect(speed3.outgoingSpeeds, hasLength(1)); + checkSpeed(speed3.outgoingSpeeds[0], 70); + + expect(speed4.incomingSpeeds, hasLength(1)); + checkSpeed(speed4.incomingSpeeds[0], 100); + expect(speed4.outgoingSpeeds, hasLength(2)); + checkSpeed(speed4.outgoingSpeeds[0], 70); + checkSpeed(speed4.outgoingSpeeds[1], 60); + + expect(speed5.incomingSpeeds, hasLength(2)); + checkSpeed(speed5.incomingSpeeds[0], 100); + checkSpeed(speed5.incomingSpeeds[1], 90); + expect(speed5.outgoingSpeeds, hasLength(2)); + checkSpeed(speed5.outgoingSpeeds[0], 70); + checkSpeed(speed5.outgoingSpeeds[1], 60); + + expect(speed6.incomingSpeeds, hasLength(3)); + checkSpeed(speed6.incomingSpeeds[0], 100); + checkSpeed(speed6.incomingSpeeds[1], 90); + checkSpeed(speed6.incomingSpeeds[2], 80); + expect(speed6.outgoingSpeeds, hasLength(2)); + checkSpeed(speed6.outgoingSpeeds[0], 70); + checkSpeed(speed6.outgoingSpeeds[1], 60); + + expect(speed7.incomingSpeeds, hasLength(1)); + checkSpeed(speed7.incomingSpeeds[0], 100); + expect(speed7.outgoingSpeeds, hasLength(3)); + checkSpeed(speed7.outgoingSpeeds[0], 70); + checkSpeed(speed7.outgoingSpeeds[1], 60); + checkSpeed(speed7.outgoingSpeeds[2], 50); + + expect(speed8.incomingSpeeds, hasLength(2)); + checkSpeed(speed8.incomingSpeeds[0], 100); + checkSpeed(speed8.incomingSpeeds[1], 90); + expect(speed8.outgoingSpeeds, hasLength(3)); + checkSpeed(speed8.outgoingSpeeds[0], 70); + checkSpeed(speed8.outgoingSpeeds[1], 60); + checkSpeed(speed8.outgoingSpeeds[2], 50); + + expect(speed9.incomingSpeeds, hasLength(3)); + checkSpeed(speed9.incomingSpeeds[0], 100); + checkSpeed(speed9.incomingSpeeds[1], 90); + checkSpeed(speed9.incomingSpeeds[2], 80); + expect(speed9.outgoingSpeeds, hasLength(3)); + checkSpeed(speed9.outgoingSpeeds[0], 70); + checkSpeed(speed9.outgoingSpeeds[1], 60); + checkSpeed(speed9.outgoingSpeeds[2], 50); + }); + test('test station speeds with circled or squared values', () { + // GIVEN WHEN + final speed1 = GraduatedStationSpeeds.from([TrainSeries.R], '100-{90}/70'); + final speed2 = GraduatedStationSpeeds.from([TrainSeries.R], '100/70-[60]'); + final speed3 = GraduatedStationSpeeds.from([TrainSeries.R], '[100]-90/{70}-[60]'); + + // THEN + expect(speed1.incomingSpeeds, hasLength(2)); + checkSpeed(speed1.incomingSpeeds[0], 100); + checkSpeed(speed1.incomingSpeeds[1], 90, isCircled: true); + expect(speed1.outgoingSpeeds, hasLength(1)); + checkSpeed(speed1.outgoingSpeeds[0], 70); + + expect(speed2.incomingSpeeds, hasLength(1)); + checkSpeed(speed2.incomingSpeeds[0], 100); + expect(speed2.outgoingSpeeds, hasLength(2)); + checkSpeed(speed2.outgoingSpeeds[0], 70); + checkSpeed(speed2.outgoingSpeeds[1], 60, isSquared: true); + + expect(speed3.incomingSpeeds, hasLength(2)); + checkSpeed(speed3.incomingSpeeds[0], 100, isSquared: true); + checkSpeed(speed3.incomingSpeeds[1], 90); + expect(speed3.outgoingSpeeds, hasLength(2)); + checkSpeed(speed3.outgoingSpeeds[0], 70, isCircled: true); + checkSpeed(speed3.outgoingSpeeds[1], 60, isSquared: true); + }); + + test('test invalid speed format', () { + expect(() => GraduatedStationSpeeds.from([TrainSeries.R], 'ABC'), throwsArgumentError); + expect(() => GraduatedStationSpeeds.from([TrainSeries.R], '1A-{90}/70'), throwsArgumentError); + expect(() => GraduatedStationSpeeds.from([TrainSeries.R], '100--20'), throwsArgumentError); + expect(() => GraduatedStationSpeeds.from([TrainSeries.R], '100-{{90}'), throwsArgumentError); + expect(() => GraduatedStationSpeeds.from([TrainSeries.R], '100-{90}//70'), throwsArgumentError); + }); +} + +void checkSpeed(Speed speed, int speedValue, {bool isCircled = false, bool isSquared = false}) { + expect(speed.speed, speedValue); + expect(speed.isCircled, isCircled); + expect(speed.isSquared, isSquared); +} diff --git a/das_client/test/sfera/mapper/sfera_mapper_test.dart b/das_client/test/sfera/mapper/sfera_mapper_test.dart index cb55e0cb..f11ffd7d 100644 --- a/das_client/test/sfera/mapper/sfera_mapper_test.dart +++ b/das_client/test/sfera/mapper/sfera_mapper_test.dart @@ -10,6 +10,7 @@ import 'package:das_client/model/journey/journey.dart'; import 'package:das_client/model/journey/protection_section.dart'; import 'package:das_client/model/journey/service_point.dart'; import 'package:das_client/model/journey/signal.dart'; +import 'package:das_client/model/journey/speed.dart'; import 'package:das_client/model/journey/speed_change.dart'; import 'package:das_client/model/journey/track_equipment.dart'; import 'package:das_client/model/journey/train_series.dart'; @@ -571,11 +572,112 @@ void main() { expect(servicePoints[2].speedData!.velocities, hasLength(16)); }); - test('Test train characterists break series is parsed correctly', () async { + test('Test train characteristics break series is parsed correctly', () async { final journey = getJourney('T5', 1, tcCount: 1); expect(journey.valid, true); expect(journey.metadata.breakSeries, isNotNull); expect(journey.metadata.breakSeries!.trainSeries, TrainSeries.R); expect(journey.metadata.breakSeries!.breakSeries, 115); }); + + test('Test graduated station speeds are parsed correctly', () async { + final journey = getJourney('T4', 1); + expect(journey.valid, true); + + final servicePoints = journey.data.where((it) => it.type == Datatype.servicePoint).cast().toList(); + expect(servicePoints, hasLength(4)); + + // check ServicePoint Bern + + expect(servicePoints[0].stationSpeedData, isNotNull); + final graduatedStationSpeeds1 = servicePoints[0].stationSpeedData!.graduatedStationSpeeds; + expect(graduatedStationSpeeds1, hasLength(3)); + + final roSpeedEntry1 = graduatedStationSpeeds1.firstWhereOrNull((speeds) => speeds.trainSeries.contains(TrainSeries.R)); + expect(roSpeedEntry1, isNotNull); + expect(roSpeedEntry1!.text, 'Zusatzinformation A'); + expect(roSpeedEntry1.trainSeries, containsAll([TrainSeries.R, TrainSeries.O])); + expect(roSpeedEntry1.incomingSpeeds, hasLength(3)); + _checkSpeed(roSpeedEntry1.incomingSpeeds[0], 75); + _checkSpeed(roSpeedEntry1.incomingSpeeds[1], 70); + _checkSpeed(roSpeedEntry1.incomingSpeeds[2], 60); + expect(roSpeedEntry1.outgoingSpeeds, isEmpty); + + final sSpeedEntry1 = graduatedStationSpeeds1.firstWhereOrNull((speeds) => speeds.trainSeries.contains(TrainSeries.S)); + expect(sSpeedEntry1, isNotNull); + expect(sSpeedEntry1!.text, 'Zusatzinformation A'); + expect(sSpeedEntry1.trainSeries, containsAll([TrainSeries.S])); + expect(sSpeedEntry1.incomingSpeeds, hasLength(2)); + _checkSpeed(sSpeedEntry1.incomingSpeeds[0], 70); + _checkSpeed(sSpeedEntry1.incomingSpeeds[1], 60); + expect(sSpeedEntry1.outgoingSpeeds, hasLength(1)); + _checkSpeed(sSpeedEntry1.outgoingSpeeds[0], 50); + + final nSpeedEntry1 = graduatedStationSpeeds1.firstWhereOrNull((speeds) => speeds.trainSeries.contains(TrainSeries.N)); + expect(nSpeedEntry1, isNotNull); + expect(nSpeedEntry1!.text, 'Zusatzinformation B'); + expect(nSpeedEntry1.trainSeries, containsAll([TrainSeries.N])); + expect(nSpeedEntry1.incomingSpeeds, hasLength(1)); + _checkSpeed(nSpeedEntry1.incomingSpeeds[0], 70); + expect(nSpeedEntry1.outgoingSpeeds, hasLength(1)); + _checkSpeed(nSpeedEntry1.outgoingSpeeds[0], 60); + + // check ServicePoint Wankdorf + + expect(servicePoints[1].stationSpeedData, isNull); + + // check ServicePoint Burgdorf + + final graduatedStationSpeeds2 = servicePoints[2].stationSpeedData!.graduatedStationSpeeds; + expect(graduatedStationSpeeds2, hasLength(2)); + + final roSpeedEntry2 = graduatedStationSpeeds2.firstWhereOrNull((speeds) => speeds.trainSeries.contains(TrainSeries.R)); + expect(roSpeedEntry2, isNotNull); + expect(roSpeedEntry2!.text, 'Zusatzinformation A'); + expect(roSpeedEntry2.trainSeries, containsAll([TrainSeries.R, TrainSeries.O])); + expect(roSpeedEntry2.incomingSpeeds, hasLength(2)); + _checkSpeed(roSpeedEntry2.incomingSpeeds[0], 75); + _checkSpeed(roSpeedEntry2.incomingSpeeds[1], 70, isCircled: true); + expect(roSpeedEntry2.outgoingSpeeds, hasLength(1)); + _checkSpeed(roSpeedEntry2.outgoingSpeeds[0], 60, isSquared: true); + + final adSpeedEntry2 = graduatedStationSpeeds2.firstWhereOrNull((speeds) => speeds.trainSeries.contains(TrainSeries.A)); + expect(adSpeedEntry2, isNotNull); + expect(adSpeedEntry2!.text, 'Zusatzinformation B'); + expect(adSpeedEntry2.trainSeries, containsAll([TrainSeries.A, TrainSeries.D])); + expect(adSpeedEntry2.incomingSpeeds, hasLength(1)); + _checkSpeed(adSpeedEntry2.incomingSpeeds[0], 70); + expect(adSpeedEntry2.outgoingSpeeds, isEmpty); + + // check ServicePoint Olten + + final graduatedStationSpeeds3 = servicePoints[3].stationSpeedData!.graduatedStationSpeeds; + expect(graduatedStationSpeeds3, hasLength(2)); + + final nSpeedEntry3 = graduatedStationSpeeds3.firstWhereOrNull((speeds) => speeds.trainSeries.contains(TrainSeries.N)); + expect(nSpeedEntry3, isNotNull); + expect(nSpeedEntry3!.text, 'Zusatzinformation A'); + expect(nSpeedEntry3.trainSeries, containsAll([TrainSeries.N])); + expect(nSpeedEntry3.incomingSpeeds, hasLength(1)); + _checkSpeed(nSpeedEntry3.incomingSpeeds[0], 80); + expect(nSpeedEntry3.outgoingSpeeds, hasLength(2)); + _checkSpeed(nSpeedEntry3.outgoingSpeeds[0], 70); + _checkSpeed(nSpeedEntry3.outgoingSpeeds[1], 60); + + final sSpeedEntry3 = graduatedStationSpeeds3.firstWhereOrNull((speeds) => speeds.trainSeries.contains(TrainSeries.S)); + expect(sSpeedEntry3, isNotNull); + expect(sSpeedEntry3!.text, 'Zusatzinformation A'); + expect(sSpeedEntry3.trainSeries, containsAll([TrainSeries.S])); + expect(sSpeedEntry3.incomingSpeeds, hasLength(2)); + _checkSpeed(sSpeedEntry3.incomingSpeeds[0], 70); + _checkSpeed(sSpeedEntry3.incomingSpeeds[1], 60); + expect(sSpeedEntry3.outgoingSpeeds, hasLength(1)); + _checkSpeed(sSpeedEntry3.outgoingSpeeds[0], 50); + }); +} + +void _checkSpeed(Speed speed, int speedValue, {bool isCircled = false, bool isSquared = false}) { + expect(speed.speed, speedValue); + expect(speed.isCircled, isCircled); + expect(speed.isSquared, isSquared); } diff --git a/das_client/test_resources/T4_graduated_station_speeds/SFERA_JP_T4.xml b/das_client/test_resources/T4_graduated_station_speeds/SFERA_JP_T4.xml new file mode 100644 index 00000000..6bfd7ad5 --- /dev/null +++ b/das_client/test_resources/T4_graduated_station_speeds/SFERA_JP_T4.xml @@ -0,0 +1,40 @@ + + + + + 1085 + T4 + 2025-01-01 + + + + + 0085 + + + + + + + + + + + + + + + + + + + + + + + 1185 + + + diff --git a/das_client/test_resources/T4_graduated_station_speeds/SFERA_SP_T4_1.xml b/das_client/test_resources/T4_graduated_station_speeds/SFERA_SP_T4_1.xml new file mode 100644 index 00000000..4c5409c0 --- /dev/null +++ b/das_client/test_resources/T4_graduated_station_speeds/SFERA_SP_T4_1.xml @@ -0,0 +1,135 @@ + + + + 0085 + + + + + + CH + 3001 + + + + + + + CH + 3002 + + + + + + + CH + 3003 + + + + + + + CH + 3004 + + + + + + + + CH + 3001 + + + + + + + + + + + CH + 3002 + + + + + + + CH + 3003 + + + + + + + + + + + CH + 3004 + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/das_client/test_resources/T4_graduated_station_speeds/SFERA_TC_T4_1.xml b/das_client/test_resources/T4_graduated_station_speeds/SFERA_TC_T4_1.xml new file mode 100644 index 00000000..7e51911f --- /dev/null +++ b/das_client/test_resources/T4_graduated_station_speeds/SFERA_TC_T4_1.xml @@ -0,0 +1,8 @@ + + + 1185 + + +