diff options
| author | Rutger Broekhoff | 2024-05-02 20:27:40 +0200 |
|---|---|---|
| committer | Rutger Broekhoff | 2024-05-02 20:27:40 +0200 |
| commit | 17a3ea880402338420699e03bcb24181e4ff3924 (patch) | |
| tree | da666ef91e0b60d20aa0b01529644c136fd1f4ab /lib/libtmi8/src | |
| download | oeuf-17a3ea880402338420699e03bcb24181e4ff3924.tar.gz oeuf-17a3ea880402338420699e03bcb24181e4ff3924.zip | |
Initial commit
Based on dc4ba6a
Diffstat (limited to 'lib/libtmi8/src')
| -rw-r--r-- | lib/libtmi8/src/kv1_index.cpp | 461 | ||||
| -rw-r--r-- | lib/libtmi8/src/kv1_lexer.cpp | 152 | ||||
| -rw-r--r-- | lib/libtmi8/src/kv1_parser.cpp | 1258 | ||||
| -rw-r--r-- | lib/libtmi8/src/kv1_types.cpp | 773 | ||||
| -rw-r--r-- | lib/libtmi8/src/kv6_parquet.cpp | 102 |
5 files changed, 2746 insertions, 0 deletions
diff --git a/lib/libtmi8/src/kv1_index.cpp b/lib/libtmi8/src/kv1_index.cpp new file mode 100644 index 0000000..23e9596 --- /dev/null +++ b/lib/libtmi8/src/kv1_index.cpp | |||
| @@ -0,0 +1,461 @@ | |||
| 1 | // vim:set sw=2 ts=2 sts et: | ||
| 2 | |||
| 3 | #include <tmi8/kv1_index.hpp> | ||
| 4 | |||
| 5 | Kv1Index::Kv1Index(Kv1Records *records) : records(records) { | ||
| 6 | organizational_units.reserve(records->organizational_units.size()); | ||
| 7 | for (size_t i = 0; i < records->organizational_units.size(); i++) { | ||
| 8 | auto *it = &records->organizational_units[i]; | ||
| 9 | organizational_units[it->key] = it; | ||
| 10 | } | ||
| 11 | higher_organizational_units.reserve(records->higher_organizational_units.size()); | ||
| 12 | for (size_t i = 0; i < records->higher_organizational_units.size(); i++) { | ||
| 13 | auto *it = &records->higher_organizational_units[i]; | ||
| 14 | higher_organizational_units[it->key] = it; | ||
| 15 | } | ||
| 16 | user_stop_points.reserve(records->user_stop_points.size()); | ||
| 17 | for (size_t i = 0; i < records->user_stop_points.size(); i++) { | ||
| 18 | auto *it = &records->user_stop_points[i]; | ||
| 19 | user_stop_points[it->key] = it; | ||
| 20 | } | ||
| 21 | user_stop_areas.reserve(records->user_stop_areas.size()); | ||
| 22 | for (size_t i = 0; i < records->user_stop_areas.size(); i++) { | ||
| 23 | auto *it = &records->user_stop_areas[i]; | ||
| 24 | user_stop_areas[it->key] = it; | ||
| 25 | } | ||
| 26 | timing_links.reserve(records->timing_links.size()); | ||
| 27 | for (size_t i = 0; i < records->timing_links.size(); i++) { | ||
| 28 | auto *it = &records->timing_links[i]; | ||
| 29 | timing_links[it->key] = it; | ||
| 30 | } | ||
| 31 | links.reserve(records->links.size()); | ||
| 32 | for (size_t i = 0; i < records->links.size(); i++) { | ||
| 33 | auto *it = &records->links[i]; | ||
| 34 | links[it->key] = it; | ||
| 35 | } | ||
| 36 | lines.reserve(records->lines.size()); | ||
| 37 | for (size_t i = 0; i < records->lines.size(); i++) { | ||
| 38 | auto *it = &records->lines[i]; | ||
| 39 | lines[it->key] = it; | ||
| 40 | } | ||
| 41 | destinations.reserve(records->destinations.size()); | ||
| 42 | for (size_t i = 0; i < records->destinations.size(); i++) { | ||
| 43 | auto *it = &records->destinations[i]; | ||
| 44 | destinations[it->key] = it; | ||
| 45 | } | ||
| 46 | journey_patterns.reserve(records->journey_patterns.size()); | ||
| 47 | for (size_t i = 0; i < records->journey_patterns.size(); i++) { | ||
| 48 | auto *it = &records->journey_patterns[i]; | ||
| 49 | journey_patterns[it->key] = it; | ||
| 50 | } | ||
| 51 | concession_financer_relations.reserve(records->concession_financer_relations.size()); | ||
| 52 | for (size_t i = 0; i < records->concession_financer_relations.size(); i++) { | ||
| 53 | auto *it = &records->concession_financer_relations[i]; | ||
| 54 | concession_financer_relations[it->key] = it; | ||
| 55 | } | ||
| 56 | concession_areas.reserve(records->concession_areas.size()); | ||
| 57 | for (size_t i = 0; i < records->concession_areas.size(); i++) { | ||
| 58 | auto *it = &records->concession_areas[i]; | ||
| 59 | concession_areas[it->key] = it; | ||
| 60 | } | ||
| 61 | financers.reserve(records->financers.size()); | ||
| 62 | for (size_t i = 0; i < records->financers.size(); i++) { | ||
| 63 | auto *it = &records->financers[i]; | ||
| 64 | financers[it->key] = it; | ||
| 65 | } | ||
| 66 | journey_pattern_timing_links.reserve(records->journey_pattern_timing_links.size()); | ||
| 67 | for (size_t i = 0; i < records->journey_pattern_timing_links.size(); i++) { | ||
| 68 | auto *it = &records->journey_pattern_timing_links[i]; | ||
| 69 | journey_pattern_timing_links[it->key] = it; | ||
| 70 | } | ||
| 71 | points.reserve(records->points.size()); | ||
| 72 | for (size_t i = 0; i < records->points.size(); i++) { | ||
| 73 | auto *it = &records->points[i]; | ||
| 74 | points[it->key] = it; | ||
| 75 | } | ||
| 76 | point_on_links.reserve(records->point_on_links.size()); | ||
| 77 | for (size_t i = 0; i < records->point_on_links.size(); i++) { | ||
| 78 | auto *it = &records->point_on_links[i]; | ||
| 79 | point_on_links[it->key] = it; | ||
| 80 | } | ||
| 81 | icons.reserve(records->icons.size()); | ||
| 82 | for (size_t i = 0; i < records->icons.size(); i++) { | ||
| 83 | auto *it = &records->icons[i]; | ||
| 84 | icons[it->key] = it; | ||
| 85 | } | ||
| 86 | notices.reserve(records->notices.size()); | ||
| 87 | for (size_t i = 0; i < records->notices.size(); i++) { | ||
| 88 | auto *it = &records->notices[i]; | ||
| 89 | notices[it->key] = it; | ||
| 90 | } | ||
| 91 | time_demand_groups.reserve(records->time_demand_groups.size()); | ||
| 92 | for (size_t i = 0; i < records->time_demand_groups.size(); i++) { | ||
| 93 | auto *it = &records->time_demand_groups[i]; | ||
| 94 | time_demand_groups[it->key] = it; | ||
| 95 | } | ||
| 96 | time_demand_group_run_times.reserve(records->time_demand_group_run_times.size()); | ||
| 97 | for (size_t i = 0; i < records->time_demand_group_run_times.size(); i++) { | ||
| 98 | auto *it = &records->time_demand_group_run_times[i]; | ||
| 99 | time_demand_group_run_times[it->key] = it; | ||
| 100 | } | ||
| 101 | period_groups.reserve(records->period_groups.size()); | ||
| 102 | for (size_t i = 0; i < records->period_groups.size(); i++) { | ||
| 103 | auto *it = &records->period_groups[i]; | ||
| 104 | period_groups[it->key] = it; | ||
| 105 | } | ||
| 106 | specific_days.reserve(records->specific_days.size()); | ||
| 107 | for (size_t i = 0; i < records->specific_days.size(); i++) { | ||
| 108 | auto *it = &records->specific_days[i]; | ||
| 109 | specific_days[it->key] = it; | ||
| 110 | } | ||
| 111 | timetable_versions.reserve(records->timetable_versions.size()); | ||
| 112 | for (size_t i = 0; i < records->timetable_versions.size(); i++) { | ||
| 113 | auto *it = &records->timetable_versions[i]; | ||
| 114 | timetable_versions[it->key] = it; | ||
| 115 | } | ||
| 116 | public_journeys.reserve(records->public_journeys.size()); | ||
| 117 | for (size_t i = 0; i < records->public_journeys.size(); i++) { | ||
| 118 | auto *it = &records->public_journeys[i]; | ||
| 119 | public_journeys[it->key] = it; | ||
| 120 | } | ||
| 121 | period_group_validities.reserve(records->period_group_validities.size()); | ||
| 122 | for (size_t i = 0; i < records->period_group_validities.size(); i++) { | ||
| 123 | auto *it = &records->period_group_validities[i]; | ||
| 124 | period_group_validities[it->key] = it; | ||
| 125 | } | ||
| 126 | exceptional_operating_days.reserve(records->exceptional_operating_days.size()); | ||
| 127 | for (size_t i = 0; i < records->exceptional_operating_days.size(); i++) { | ||
| 128 | auto *it = &records->exceptional_operating_days[i]; | ||
| 129 | exceptional_operating_days[it->key] = it; | ||
| 130 | } | ||
| 131 | schedule_versions.reserve(records->schedule_versions.size()); | ||
| 132 | for (size_t i = 0; i < records->schedule_versions.size(); i++) { | ||
| 133 | auto *it = &records->schedule_versions[i]; | ||
| 134 | schedule_versions[it->key] = it; | ||
| 135 | } | ||
| 136 | public_journey_passing_times.reserve(records->public_journey_passing_times.size()); | ||
| 137 | for (size_t i = 0; i < records->public_journey_passing_times.size(); i++) { | ||
| 138 | auto *it = &records->public_journey_passing_times[i]; | ||
| 139 | public_journey_passing_times[it->key] = it; | ||
| 140 | } | ||
| 141 | operating_days.reserve(records->operating_days.size()); | ||
| 142 | for (size_t i = 0; i < records->operating_days.size(); i++) { | ||
| 143 | auto *it = &records->operating_days[i]; | ||
| 144 | operating_days[it->key] = it; | ||
| 145 | } | ||
| 146 | } | ||
| 147 | |||
| 148 | size_t Kv1Index::size() const { | ||
| 149 | return organizational_units.size() | ||
| 150 | + higher_organizational_units.size() | ||
| 151 | + user_stop_points.size() | ||
| 152 | + user_stop_areas.size() | ||
| 153 | + timing_links.size() | ||
| 154 | + links.size() | ||
| 155 | + lines.size() | ||
| 156 | + destinations.size() | ||
| 157 | + journey_patterns.size() | ||
| 158 | + concession_financer_relations.size() | ||
| 159 | + concession_areas.size() | ||
| 160 | + financers.size() | ||
| 161 | + journey_pattern_timing_links.size() | ||
| 162 | + points.size() | ||
| 163 | + point_on_links.size() | ||
| 164 | + icons.size() | ||
| 165 | + notices.size() | ||
| 166 | + time_demand_groups.size() | ||
| 167 | + time_demand_group_run_times.size() | ||
| 168 | + period_groups.size() | ||
| 169 | + specific_days.size() | ||
| 170 | + timetable_versions.size() | ||
| 171 | + public_journeys.size() | ||
| 172 | + period_group_validities.size() | ||
| 173 | + exceptional_operating_days.size() | ||
| 174 | + schedule_versions.size() | ||
| 175 | + public_journey_passing_times.size() | ||
| 176 | + operating_days.size(); | ||
| 177 | } | ||
| 178 | |||
| 179 | void kv1LinkRecords(Kv1Index &index) { | ||
| 180 | for (auto &orunorun : index.records->higher_organizational_units) { | ||
| 181 | Kv1OrganizationalUnit::Key orun_parent_key( | ||
| 182 | orunorun.key.data_owner_code, | ||
| 183 | orunorun.key.organizational_unit_code_parent); | ||
| 184 | Kv1OrganizationalUnit::Key orun_child_key( | ||
| 185 | orunorun.key.data_owner_code, | ||
| 186 | orunorun.key.organizational_unit_code_child); | ||
| 187 | orunorun.p_organizational_unit_parent = index.organizational_units[orun_parent_key]; | ||
| 188 | orunorun.p_organizational_unit_child = index.organizational_units[orun_child_key]; | ||
| 189 | } | ||
| 190 | for (auto &usrstop : index.records->user_stop_points) { | ||
| 191 | Kv1Point::Key point_key( | ||
| 192 | usrstop.key.data_owner_code, | ||
| 193 | usrstop.key.user_stop_code); | ||
| 194 | usrstop.p_point = index.points[point_key]; | ||
| 195 | if (!usrstop.user_stop_area_code.empty()) { | ||
| 196 | Kv1UserStopArea::Key usrstar_key( | ||
| 197 | usrstop.key.data_owner_code, | ||
| 198 | usrstop.user_stop_area_code); | ||
| 199 | usrstop.p_user_stop_area = index.user_stop_areas[usrstar_key]; | ||
| 200 | } | ||
| 201 | } | ||
| 202 | for (auto &tili : index.records->timing_links) { | ||
| 203 | Kv1UserStopPoint::Key usrstop_begin_key( | ||
| 204 | tili.key.data_owner_code, | ||
| 205 | tili.key.user_stop_code_begin); | ||
| 206 | Kv1UserStopPoint::Key usrstop_end_key( | ||
| 207 | tili.key.data_owner_code, | ||
| 208 | tili.key.user_stop_code_end); | ||
| 209 | tili.p_user_stop_begin = index.user_stop_points[usrstop_begin_key]; | ||
| 210 | tili.p_user_stop_end = index.user_stop_points[usrstop_end_key]; | ||
| 211 | } | ||
| 212 | for (auto &link : index.records->links) { | ||
| 213 | Kv1UserStopPoint::Key usrstop_begin_key( | ||
| 214 | link.key.data_owner_code, | ||
| 215 | link.key.user_stop_code_begin); | ||
| 216 | Kv1UserStopPoint::Key usrstop_end_key( | ||
| 217 | link.key.data_owner_code, | ||
| 218 | link.key.user_stop_code_end); | ||
| 219 | link.p_user_stop_begin = index.user_stop_points[usrstop_begin_key]; | ||
| 220 | link.p_user_stop_end = index.user_stop_points[usrstop_end_key]; | ||
| 221 | } | ||
| 222 | for (auto &line : index.records->lines) { | ||
| 223 | if (!line.line_icon) | ||
| 224 | continue; | ||
| 225 | Kv1Icon::Key icon_key( | ||
| 226 | line.key.data_owner_code, | ||
| 227 | *line.line_icon); | ||
| 228 | line.p_line_icon = index.icons[icon_key]; | ||
| 229 | } | ||
| 230 | for (auto &jopa : index.records->journey_patterns) { | ||
| 231 | Kv1Line::Key line_key( | ||
| 232 | jopa.key.data_owner_code, | ||
| 233 | jopa.key.line_planning_number); | ||
| 234 | jopa.p_line = index.lines[line_key]; | ||
| 235 | } | ||
| 236 | for (auto &confinrel : index.records->concession_financer_relations) { | ||
| 237 | Kv1ConcessionArea::Key conarea_key( | ||
| 238 | confinrel.key.data_owner_code, | ||
| 239 | confinrel.concession_area_code); | ||
| 240 | confinrel.p_concession_area = index.concession_areas[conarea_key]; | ||
| 241 | if (!confinrel.financer_code.empty()) { | ||
| 242 | Kv1Financer::Key financer_key( | ||
| 243 | confinrel.key.data_owner_code, | ||
| 244 | confinrel.financer_code); | ||
| 245 | confinrel.p_financer = index.financers[financer_key]; | ||
| 246 | } | ||
| 247 | } | ||
| 248 | for (auto &jopatili : index.records->journey_pattern_timing_links) { | ||
| 249 | Kv1Line::Key line_key( | ||
| 250 | jopatili.key.data_owner_code, | ||
| 251 | jopatili.key.line_planning_number); | ||
| 252 | Kv1JourneyPattern::Key jopa_key( | ||
| 253 | jopatili.key.data_owner_code, | ||
| 254 | jopatili.key.line_planning_number, | ||
| 255 | jopatili.key.journey_pattern_code); | ||
| 256 | Kv1UserStopPoint::Key usrstop_begin_key( | ||
| 257 | jopatili.key.data_owner_code, | ||
| 258 | jopatili.user_stop_code_begin); | ||
| 259 | Kv1UserStopPoint::Key usrstop_end_key( | ||
| 260 | jopatili.key.data_owner_code, | ||
| 261 | jopatili.user_stop_code_end); | ||
| 262 | Kv1ConcessionFinancerRelation::Key confinrel_key( | ||
| 263 | jopatili.key.data_owner_code, | ||
| 264 | jopatili.con_fin_rel_code); | ||
| 265 | Kv1Destination::Key dest_key( | ||
| 266 | jopatili.key.data_owner_code, | ||
| 267 | jopatili.dest_code); | ||
| 268 | jopatili.p_line = index.lines[line_key]; | ||
| 269 | jopatili.p_journey_pattern = index.journey_patterns[jopa_key]; | ||
| 270 | jopatili.p_user_stop_begin = index.user_stop_points[usrstop_begin_key]; | ||
| 271 | jopatili.p_user_stop_end = index.user_stop_points[usrstop_end_key]; | ||
| 272 | jopatili.p_con_fin_rel = index.concession_financer_relations[confinrel_key]; | ||
| 273 | jopatili.p_dest = index.destinations[dest_key]; | ||
| 274 | if (jopatili.line_dest_icon) { | ||
| 275 | Kv1Icon::Key icon_key{ | ||
| 276 | jopatili.key.data_owner_code, | ||
| 277 | *jopatili.line_dest_icon, | ||
| 278 | }; | ||
| 279 | jopatili.p_line_dest_icon = index.icons[icon_key]; | ||
| 280 | } | ||
| 281 | } | ||
| 282 | for (auto &pool : index.records->point_on_links) { | ||
| 283 | Kv1UserStopPoint::Key usrstop_begin_key( | ||
| 284 | pool.key.data_owner_code, | ||
| 285 | pool.key.user_stop_code_begin); | ||
| 286 | Kv1UserStopPoint::Key usrstop_end_key( | ||
| 287 | pool.key.data_owner_code, | ||
| 288 | pool.key.user_stop_code_end); | ||
| 289 | Kv1Point::Key point_key( | ||
| 290 | pool.key.point_data_owner_code, | ||
| 291 | pool.key.point_code); | ||
| 292 | pool.p_user_stop_begin = index.user_stop_points[usrstop_begin_key]; | ||
| 293 | pool.p_user_stop_end = index.user_stop_points[usrstop_end_key]; | ||
| 294 | pool.p_point = index.points[point_key]; | ||
| 295 | } | ||
| 296 | for (auto &ntcassgnm : index.records->notice_assignments) { | ||
| 297 | Kv1Notice::Key notice_key( | ||
| 298 | ntcassgnm.data_owner_code, | ||
| 299 | ntcassgnm.notice_code); | ||
| 300 | ntcassgnm.p_notice = index.notices[notice_key]; | ||
| 301 | } | ||
| 302 | for (auto &timdemgrp : index.records->time_demand_groups) { | ||
| 303 | Kv1Line::Key line_key( | ||
| 304 | timdemgrp.key.data_owner_code, | ||
| 305 | timdemgrp.key.line_planning_number); | ||
| 306 | Kv1JourneyPattern::Key jopa_key( | ||
| 307 | timdemgrp.key.data_owner_code, | ||
| 308 | timdemgrp.key.line_planning_number, | ||
| 309 | timdemgrp.key.journey_pattern_code); | ||
| 310 | timdemgrp.p_line = index.lines[line_key]; | ||
| 311 | timdemgrp.p_journey_pattern = index.journey_patterns[jopa_key]; | ||
| 312 | } | ||
| 313 | for (auto &timdemrnt : index.records->time_demand_group_run_times) { | ||
| 314 | Kv1Line::Key line_key( | ||
| 315 | timdemrnt.key.data_owner_code, | ||
| 316 | timdemrnt.key.line_planning_number); | ||
| 317 | Kv1JourneyPattern::Key jopa_key( | ||
| 318 | timdemrnt.key.data_owner_code, | ||
| 319 | timdemrnt.key.line_planning_number, | ||
| 320 | timdemrnt.key.journey_pattern_code); | ||
| 321 | Kv1TimeDemandGroup::Key timdemgrp_key( | ||
| 322 | timdemrnt.key.data_owner_code, | ||
| 323 | timdemrnt.key.line_planning_number, | ||
| 324 | timdemrnt.key.journey_pattern_code, | ||
| 325 | timdemrnt.key.time_demand_group_code); | ||
| 326 | Kv1UserStopPoint::Key usrstop_begin_key( | ||
| 327 | timdemrnt.key.data_owner_code, | ||
| 328 | timdemrnt.user_stop_code_begin); | ||
| 329 | Kv1UserStopPoint::Key usrstop_end_key( | ||
| 330 | timdemrnt.key.data_owner_code, | ||
| 331 | timdemrnt.user_stop_code_end); | ||
| 332 | Kv1JourneyPatternTimingLink::Key jopatili_key( | ||
| 333 | timdemrnt.key.data_owner_code, | ||
| 334 | timdemrnt.key.line_planning_number, | ||
| 335 | timdemrnt.key.journey_pattern_code, | ||
| 336 | timdemrnt.key.timing_link_order); | ||
| 337 | timdemrnt.p_line = index.lines[line_key]; | ||
| 338 | timdemrnt.p_user_stop_end = index.user_stop_points[usrstop_end_key]; | ||
| 339 | timdemrnt.p_user_stop_begin = index.user_stop_points[usrstop_begin_key]; | ||
| 340 | timdemrnt.p_journey_pattern = index.journey_patterns[jopa_key]; | ||
| 341 | timdemrnt.p_time_demand_group = index.time_demand_groups[timdemgrp_key]; | ||
| 342 | timdemrnt.p_journey_pattern_timing_link = index.journey_pattern_timing_links[jopatili_key]; | ||
| 343 | } | ||
| 344 | for (auto &tive : index.records->timetable_versions) { | ||
| 345 | Kv1OrganizationalUnit::Key orun_key( | ||
| 346 | tive.key.data_owner_code, | ||
| 347 | tive.key.organizational_unit_code); | ||
| 348 | Kv1PeriodGroup::Key pegr_key( | ||
| 349 | tive.key.data_owner_code, | ||
| 350 | tive.key.period_group_code); | ||
| 351 | Kv1SpecificDay::Key specday_key( | ||
| 352 | tive.key.data_owner_code, | ||
| 353 | tive.key.specific_day_code); | ||
| 354 | tive.p_organizational_unit = index.organizational_units[orun_key]; | ||
| 355 | tive.p_period_group = index.period_groups[pegr_key]; | ||
| 356 | tive.p_specific_day = index.specific_days[specday_key]; | ||
| 357 | } | ||
| 358 | for (auto &pujo : index.records->public_journeys) { | ||
| 359 | Kv1TimetableVersion::Key tive_key( | ||
| 360 | pujo.key.data_owner_code, | ||
| 361 | pujo.key.organizational_unit_code, | ||
| 362 | pujo.key.timetable_version_code, | ||
| 363 | pujo.key.period_group_code, | ||
| 364 | pujo.key.specific_day_code); | ||
| 365 | Kv1OrganizationalUnit::Key orun_key( | ||
| 366 | pujo.key.data_owner_code, | ||
| 367 | pujo.key.organizational_unit_code); | ||
| 368 | Kv1PeriodGroup::Key pegr_key( | ||
| 369 | pujo.key.data_owner_code, | ||
| 370 | pujo.key.period_group_code); | ||
| 371 | Kv1SpecificDay::Key specday_key( | ||
| 372 | pujo.key.data_owner_code, | ||
| 373 | pujo.key.specific_day_code); | ||
| 374 | Kv1Line::Key line_key( | ||
| 375 | pujo.key.data_owner_code, | ||
| 376 | pujo.key.line_planning_number); | ||
| 377 | Kv1TimeDemandGroup::Key timdemgrp_key( | ||
| 378 | pujo.key.data_owner_code, | ||
| 379 | pujo.key.line_planning_number, | ||
| 380 | pujo.journey_pattern_code, | ||
| 381 | pujo.time_demand_group_code); | ||
| 382 | Kv1JourneyPattern::Key jopa_key( | ||
| 383 | pujo.key.data_owner_code, | ||
| 384 | pujo.key.line_planning_number, | ||
| 385 | pujo.journey_pattern_code); | ||
| 386 | pujo.p_timetable_version = index.timetable_versions[tive_key]; | ||
| 387 | pujo.p_organizational_unit = index.organizational_units[orun_key]; | ||
| 388 | pujo.p_period_group = index.period_groups[pegr_key]; | ||
| 389 | pujo.p_specific_day = index.specific_days[specday_key]; | ||
| 390 | pujo.p_line = index.lines[line_key]; | ||
| 391 | pujo.p_time_demand_group = index.time_demand_groups[timdemgrp_key]; | ||
| 392 | pujo.p_journey_pattern = index.journey_patterns[jopa_key]; | ||
| 393 | } | ||
| 394 | for (auto &pegrval : index.records->period_group_validities) { | ||
| 395 | Kv1OrganizationalUnit::Key orun_key( | ||
| 396 | pegrval.key.data_owner_code, | ||
| 397 | pegrval.key.organizational_unit_code); | ||
| 398 | Kv1PeriodGroup::Key pegr_key( | ||
| 399 | pegrval.key.data_owner_code, | ||
| 400 | pegrval.key.period_group_code); | ||
| 401 | pegrval.p_organizational_unit = index.organizational_units[orun_key]; | ||
| 402 | pegrval.p_period_group = index.period_groups[pegr_key]; | ||
| 403 | } | ||
| 404 | for (auto &excopday : index.records->exceptional_operating_days) { | ||
| 405 | Kv1OrganizationalUnit::Key orun_key( | ||
| 406 | excopday.key.data_owner_code, | ||
| 407 | excopday.key.organizational_unit_code); | ||
| 408 | Kv1SpecificDay::Key specday_key( | ||
| 409 | excopday.key.data_owner_code, | ||
| 410 | excopday.specific_day_code); | ||
| 411 | Kv1PeriodGroup::Key pegr_key( | ||
| 412 | excopday.key.data_owner_code, | ||
| 413 | excopday.period_group_code); | ||
| 414 | excopday.p_organizational_unit = index.organizational_units[orun_key]; | ||
| 415 | excopday.p_specific_day = index.specific_days[specday_key]; | ||
| 416 | excopday.p_period_group = index.period_groups[pegr_key]; | ||
| 417 | } | ||
| 418 | for (auto &schedvers : index.records->schedule_versions) { | ||
| 419 | Kv1OrganizationalUnit::Key orun_key( | ||
| 420 | schedvers.key.data_owner_code, | ||
| 421 | schedvers.key.organizational_unit_code); | ||
| 422 | schedvers.p_organizational_unit = index.organizational_units[orun_key]; | ||
| 423 | } | ||
| 424 | for (auto &pujopass : index.records->public_journey_passing_times) { | ||
| 425 | Kv1OrganizationalUnit::Key orun_key( | ||
| 426 | pujopass.key.data_owner_code, | ||
| 427 | pujopass.key.organizational_unit_code); | ||
| 428 | Kv1ScheduleVersion::Key schedvers_key( | ||
| 429 | pujopass.key.data_owner_code, | ||
| 430 | pujopass.key.organizational_unit_code, | ||
| 431 | pujopass.key.schedule_code, | ||
| 432 | pujopass.key.schedule_type_code); | ||
| 433 | Kv1Line::Key line_key( | ||
| 434 | pujopass.key.data_owner_code, | ||
| 435 | pujopass.key.line_planning_number); | ||
| 436 | Kv1JourneyPattern::Key jopa_key( | ||
| 437 | pujopass.key.data_owner_code, | ||
| 438 | pujopass.key.line_planning_number, | ||
| 439 | pujopass.journey_pattern_code); | ||
| 440 | Kv1UserStopPoint::Key usrstop_key( | ||
| 441 | pujopass.key.data_owner_code, | ||
| 442 | pujopass.user_stop_code); | ||
| 443 | pujopass.p_organizational_unit = index.organizational_units[orun_key]; | ||
| 444 | pujopass.p_schedule_version = index.schedule_versions[schedvers_key]; | ||
| 445 | pujopass.p_line = index.lines[line_key]; | ||
| 446 | pujopass.p_journey_pattern = index.journey_patterns[jopa_key]; | ||
| 447 | pujopass.p_user_stop = index.user_stop_points[usrstop_key]; | ||
| 448 | } | ||
| 449 | for (auto &operday : index.records->operating_days) { | ||
| 450 | Kv1OrganizationalUnit::Key orun_key( | ||
| 451 | operday.key.data_owner_code, | ||
| 452 | operday.key.organizational_unit_code); | ||
| 453 | Kv1ScheduleVersion::Key schedvers_key( | ||
| 454 | operday.key.data_owner_code, | ||
| 455 | operday.key.organizational_unit_code, | ||
| 456 | operday.key.schedule_code, | ||
| 457 | operday.key.schedule_type_code); | ||
| 458 | operday.p_organizational_unit = index.organizational_units[orun_key]; | ||
| 459 | operday.p_schedule_version = index.schedule_versions[schedvers_key]; | ||
| 460 | } | ||
| 461 | } | ||
diff --git a/lib/libtmi8/src/kv1_lexer.cpp b/lib/libtmi8/src/kv1_lexer.cpp new file mode 100644 index 0000000..028127b --- /dev/null +++ b/lib/libtmi8/src/kv1_lexer.cpp | |||
| @@ -0,0 +1,152 @@ | |||
| 1 | // vim:set sw=2 ts=2 sts et: | ||
| 2 | |||
| 3 | #include <tmi8/kv1_lexer.hpp> | ||
| 4 | |||
| 5 | Kv1Lexer::Kv1Lexer(std::string_view input) | ||
| 6 | : input(input), slice(input) | ||
| 7 | {} | ||
| 8 | |||
| 9 | // Does not eat newline character. | ||
| 10 | void Kv1Lexer::eatRestOfLine() { | ||
| 11 | size_t end = slice.size(); | ||
| 12 | for (size_t i = 0; i < slice.size(); i++) { | ||
| 13 | if (slice[i] == '\r' || slice[i] == '\n') { | ||
| 14 | end = i; | ||
| 15 | break; | ||
| 16 | } | ||
| 17 | } | ||
| 18 | slice = slice.substr(end); | ||
| 19 | } | ||
| 20 | |||
| 21 | void Kv1Lexer::lexOptionalHeader() { | ||
| 22 | if (slice.starts_with('[')) eatRestOfLine(); | ||
| 23 | } | ||
| 24 | |||
| 25 | void Kv1Lexer::lexOptionalComment() { | ||
| 26 | if (slice.starts_with(';')) eatRestOfLine(); | ||
| 27 | } | ||
| 28 | |||
| 29 | inline bool Kv1Lexer::isWhitespace(int c) { | ||
| 30 | return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == '\v'; | ||
| 31 | } | ||
| 32 | |||
| 33 | void Kv1Lexer::readQuotedColumn() { | ||
| 34 | Kv1Token token{ .type = KV1_TOKEN_CELL }; | ||
| 35 | |||
| 36 | if (slice.size() == 0 || slice[0] != '"') { | ||
| 37 | errors.push_back("(internal error) readQuotedColumn: slice[0] != '\"'"); | ||
| 38 | return; | ||
| 39 | } | ||
| 40 | slice = slice.substr(1); | ||
| 41 | while (true) { | ||
| 42 | size_t quote = slice.find('"'); | ||
| 43 | if (quote == std::string_view::npos) { | ||
| 44 | errors.push_back("readQuotedColumn: no matching closing quote found"); | ||
| 45 | return; | ||
| 46 | } | ||
| 47 | if (quote+1 == slice.size() || slice[quote + 1] != '"') { | ||
| 48 | token.data.append(slice.substr(0, quote)); | ||
| 49 | break; | ||
| 50 | } | ||
| 51 | token.data.append(slice.substr(0, quote + 1)); | ||
| 52 | slice = slice.substr(quote + 2); | ||
| 53 | } | ||
| 54 | |||
| 55 | size_t end = slice.size(); | ||
| 56 | for (size_t i = 0; i < slice.size(); i++) { | ||
| 57 | if (slice[i] == '|' || slice[i] == '\r' || slice[i] == '\n') { | ||
| 58 | end = i; | ||
| 59 | break; | ||
| 60 | } | ||
| 61 | if (!isWhitespace(slice[i])) { | ||
| 62 | errors.push_back("readQuotedColumn: encountered non-whitespace character after closing quote"); | ||
| 63 | return; | ||
| 64 | } | ||
| 65 | } | ||
| 66 | if (end != std::string_view::npos) slice = slice.substr(end); | ||
| 67 | else slice = slice.substr(slice.size()); | ||
| 68 | |||
| 69 | tokens.push_back(std::move(token)); | ||
| 70 | } | ||
| 71 | |||
| 72 | void Kv1Lexer::readUnquotedColumn() { | ||
| 73 | size_t end = slice.size(); | ||
| 74 | size_t content_end = 0; | ||
| 75 | for (size_t i = 0; i < slice.size(); i++) { | ||
| 76 | if (slice[i] == '|' || slice[i] == '\r' || slice[i] == '\n') { | ||
| 77 | end = i; | ||
| 78 | break; | ||
| 79 | } else if (!isWhitespace(slice[i])) { | ||
| 80 | content_end = i + 1; | ||
| 81 | } | ||
| 82 | } | ||
| 83 | tokens.emplace_back(KV1_TOKEN_CELL, std::string(slice.substr(0, content_end))); | ||
| 84 | if (end != std::string_view::npos) slice = slice.substr(end); | ||
| 85 | else slice = slice.substr(slice.size()); | ||
| 86 | } | ||
| 87 | |||
| 88 | void Kv1Lexer::lexRow() { | ||
| 89 | size_t cols = 0; | ||
| 90 | while (slice.size() > 0 && slice[0] != '\r' && slice[0] != '\n') { | ||
| 91 | if (slice[0] == '"') readQuotedColumn(); | ||
| 92 | else readUnquotedColumn(); | ||
| 93 | if (!errors.empty()) return; | ||
| 94 | cols++; | ||
| 95 | if (slice.size() != 0) { | ||
| 96 | if (slice[0] == '|') { | ||
| 97 | slice = slice.substr(1); | ||
| 98 | // A newline/eof right after pipe? That means an empty field at the end | ||
| 99 | // of the record, we also want to emit that as a token. | ||
| 100 | if (slice.size() == 0 || slice[0] == '\r' || slice[0] == '\n') { | ||
| 101 | tokens.push_back({ .type = KV1_TOKEN_CELL }); | ||
| 102 | } | ||
| 103 | } else if (slice[0] == '\r') { | ||
| 104 | if (slice.size() > 1 && slice[1] == '\n') slice = slice.substr(2); | ||
| 105 | else slice = slice.substr(1); | ||
| 106 | break; | ||
| 107 | } else if (slice[0] == '\n') { | ||
| 108 | slice = slice.substr(1); | ||
| 109 | break; | ||
| 110 | } else { | ||
| 111 | errors.push_back("lexRow: expected CR, LF or |"); | ||
| 112 | return; | ||
| 113 | } | ||
| 114 | } | ||
| 115 | } | ||
| 116 | tokens.push_back({ .type = KV1_TOKEN_ROW_END }); | ||
| 117 | } | ||
| 118 | |||
| 119 | // Returns true when a line ending was consumed. | ||
| 120 | bool Kv1Lexer::eatWhitespace() { | ||
| 121 | for (size_t i = 0; i < slice.size(); i++) { | ||
| 122 | if (slice[i] == '\r') { | ||
| 123 | slice = slice.substr(i + 1); | ||
| 124 | if (slice.size() > 1 && slice[i + 1] == '\n') | ||
| 125 | slice = slice.substr(i + 2); | ||
| 126 | return true; | ||
| 127 | } | ||
| 128 | if (slice[i] == '\n') { | ||
| 129 | slice = slice.substr(i + 1); | ||
| 130 | return true; | ||
| 131 | } | ||
| 132 | |||
| 133 | if (slice[i] != ' ' && slice[i] != '\f' && slice[i] != '\t' && slice[i] != '\v') { | ||
| 134 | slice = slice.substr(i); | ||
| 135 | return false; | ||
| 136 | } | ||
| 137 | } | ||
| 138 | return false; | ||
| 139 | } | ||
| 140 | |||
| 141 | void Kv1Lexer::lex() { | ||
| 142 | lexOptionalHeader(); | ||
| 143 | eatWhitespace(); | ||
| 144 | |||
| 145 | while (errors.empty() && !slice.empty()) { | ||
| 146 | lexOptionalComment(); | ||
| 147 | bool newline = eatWhitespace(); | ||
| 148 | if (newline) continue; | ||
| 149 | // We are now either (1) at the end of the file or (2) at the start of some column data | ||
| 150 | if (errors.empty()) lexRow(); | ||
| 151 | } | ||
| 152 | } | ||
diff --git a/lib/libtmi8/src/kv1_parser.cpp b/lib/libtmi8/src/kv1_parser.cpp new file mode 100644 index 0000000..ac0c6bf --- /dev/null +++ b/lib/libtmi8/src/kv1_parser.cpp | |||
| @@ -0,0 +1,1258 @@ | |||
| 1 | // vim:set sw=2 ts=2 sts et: | ||
| 2 | |||
| 3 | #include <tmi8/kv1_parser.hpp> | ||
| 4 | |||
| 5 | using rune = uint32_t; | ||
| 6 | |||
| 7 | static size_t decodeUtf8Cp(std::string_view s, rune *dest = nullptr) { | ||
| 8 | rune res = 0xFFFD; | ||
| 9 | size_t length = 1; | ||
| 10 | |||
| 11 | if (s.size() == 0) | ||
| 12 | return 0; | ||
| 13 | const uint8_t *b = reinterpret_cast<const uint8_t *>(s.data()); | ||
| 14 | if (!(b[0] & 0x80)) | ||
| 15 | res = static_cast<rune>(b[0]); | ||
| 16 | else if ((b[0] & 0xE0) == 0xC0) { | ||
| 17 | length = 2; | ||
| 18 | if (s.size() >= 2 && (b[1] & 0xC0) == 0x80) { | ||
| 19 | res = static_cast<rune>(b[0] & ~0xC0) << 6; | ||
| 20 | res |= static_cast<rune>(b[1] & ~0x80); | ||
| 21 | } | ||
| 22 | } else if ((b[0] & 0xF0) == 0xE0) { | ||
| 23 | length = 3; | ||
| 24 | if (s.size() >= 3 && (b[1] & 0xC0) == 0x80 && (b[2] & 0xC0) == 0x80) { | ||
| 25 | res = static_cast<rune>(b[0] & ~0xE0) << 12; | ||
| 26 | res |= static_cast<rune>(b[1] & ~0x80) << 6; | ||
| 27 | res |= static_cast<rune>(b[2] & ~0x80); | ||
| 28 | } | ||
| 29 | } else if (b[0] == 0xF0) { | ||
| 30 | length = 4; | ||
| 31 | if (s.size() >= 4 && (b[1] & 0xC0) == 0x80 && (b[2] & 0xC0) == 0x80 && (b[3] & 0xC0) == 0x80) { | ||
| 32 | res = static_cast<rune>(b[0] & ~0xF0) << 18; | ||
| 33 | res |= static_cast<rune>(b[1] & ~0x80) << 12; | ||
| 34 | res |= static_cast<rune>(b[2] & ~0x80) << 6; | ||
| 35 | res |= static_cast<rune>(b[3] & ~0x80); | ||
| 36 | } | ||
| 37 | } | ||
| 38 | |||
| 39 | if (dest) | ||
| 40 | *dest = res; | ||
| 41 | return length; | ||
| 42 | } | ||
| 43 | |||
| 44 | // Counts the number of codepoints in a valid UTF-8 string. Returns SIZE_MAX if | ||
| 45 | // the string contains invalid UTF-8 codepoints. | ||
| 46 | static size_t stringViewLengthUtf8(std::string_view sv) { | ||
| 47 | size_t codepoints = 0; | ||
| 48 | while (sv.size() > 0) { | ||
| 49 | size_t codepoint_size = decodeUtf8Cp(sv); | ||
| 50 | if (codepoint_size == 0) return SIZE_MAX; | ||
| 51 | codepoints++; | ||
| 52 | sv = sv.substr(codepoint_size); | ||
| 53 | } | ||
| 54 | return codepoints; | ||
| 55 | } | ||
| 56 | |||
| 57 | Kv1Parser::Kv1Parser(std::vector<Kv1Token> tokens, Kv1Records &parse_into) | ||
| 58 | : tokens(std::move(tokens)), | ||
| 59 | records(parse_into) | ||
| 60 | {} | ||
| 61 | |||
| 62 | bool Kv1Parser::atEnd() const { | ||
| 63 | return pos >= tokens.size(); | ||
| 64 | } | ||
| 65 | |||
| 66 | void Kv1Parser::eatRowEnds() { | ||
| 67 | while (!atEnd() && tokens[pos].type == KV1_TOKEN_ROW_END) pos++; | ||
| 68 | } | ||
| 69 | |||
| 70 | const Kv1Token *Kv1Parser::cur() const { | ||
| 71 | if (atEnd()) return nullptr; | ||
| 72 | return &tokens[pos]; | ||
| 73 | } | ||
| 74 | |||
| 75 | const std::string *Kv1Parser::eatCell(std::string_view parsing_what) { | ||
| 76 | const Kv1Token *tok = cur(); | ||
| 77 | if (!tok) { | ||
| 78 | record_errors.push_back(std::format("Expected cell but got end of file when parsing {}", parsing_what)); | ||
| 79 | return nullptr; | ||
| 80 | } | ||
| 81 | if (tok->type == KV1_TOKEN_ROW_END) { | ||
| 82 | record_errors.push_back(std::format("Expected cell but got end of row when parsing {}", parsing_what)); | ||
| 83 | return nullptr; | ||
| 84 | } | ||
| 85 | pos++; | ||
| 86 | return &tok->data; | ||
| 87 | } | ||
| 88 | |||
| 89 | void Kv1Parser::requireString(std::string_view field, bool mandatory, size_t max_length, std::string_view value) { | ||
| 90 | if (value.empty() && mandatory) { | ||
| 91 | record_errors.push_back(std::format("{} has length zero but is required", field)); | ||
| 92 | return; | ||
| 93 | } | ||
| 94 | size_t codepoints = stringViewLengthUtf8(value); | ||
| 95 | if (codepoints == SIZE_MAX) { | ||
| 96 | global_errors.push_back(std::format("{} contains invalid UTF-8 code points", field)); | ||
| 97 | return; | ||
| 98 | } | ||
| 99 | if (codepoints > max_length) { | ||
| 100 | record_errors.push_back(std::format("{} has length ({}) that is greater than maximum length ({})", | ||
| 101 | field, value.size(), max_length)); | ||
| 102 | } | ||
| 103 | } | ||
| 104 | |||
| 105 | static inline std::optional<bool> parseBoolean(std::string_view src) { | ||
| 106 | if (src == "1") return true; | ||
| 107 | if (src == "0") return false; | ||
| 108 | if (src == "true") return true; | ||
| 109 | if (src == "false") return false; | ||
| 110 | return std::nullopt; | ||
| 111 | } | ||
| 112 | |||
| 113 | std::optional<bool> Kv1Parser::requireBoolean(std::string_view field, bool mandatory, std::string_view value) { | ||
| 114 | if (value.empty()) { | ||
| 115 | if (mandatory) | ||
| 116 | record_errors.push_back(std::format("{} is required, but has no value", field)); | ||
| 117 | return std::nullopt; | ||
| 118 | } | ||
| 119 | auto parsed = parseBoolean(value); | ||
| 120 | if (!parsed.has_value()) | ||
| 121 | record_errors.push_back(std::format("{} should have value \"1\", \"0\", \"true\" or \"false\"", field)); | ||
| 122 | return parsed; | ||
| 123 | } | ||
| 124 | |||
| 125 | static inline size_t countDigits(long x) { | ||
| 126 | size_t digits = 0; | ||
| 127 | while (x != 0) { digits++; x /= 10; } | ||
| 128 | return digits; | ||
| 129 | } | ||
| 130 | |||
| 131 | std::optional<double> Kv1Parser::requireNumber(std::string_view field, bool mandatory, size_t max_digits, std::string_view value) { | ||
| 132 | if (value.empty()) { | ||
| 133 | if (mandatory) | ||
| 134 | record_errors.push_back(std::format("{} has no value but is required", field)); | ||
| 135 | return std::nullopt; | ||
| 136 | } | ||
| 137 | |||
| 138 | double parsed; | ||
| 139 | auto [ptr, ec] = std::from_chars(value.data(), value.data() + value.size(), parsed, std::chars_format::fixed); | ||
| 140 | if (ec != std::errc()) { | ||
| 141 | record_errors.push_back(std::format("{} has a bad value that cannot be parsed as a number", field)); | ||
| 142 | return std::nullopt; | ||
| 143 | } | ||
| 144 | if (ptr != value.data() + value.size()) { | ||
| 145 | record_errors.push_back(std::format("{} contains characters that were not parsed as a number", field)); | ||
| 146 | return std::nullopt; | ||
| 147 | } | ||
| 148 | |||
| 149 | size_t digits = countDigits(static_cast<long>(parsed)); | ||
| 150 | if (digits > max_digits) { | ||
| 151 | record_errors.push_back(std::format("{} contains more digits (in the integral part) ({}) than allowed ({})", | ||
| 152 | field, digits, max_digits)); | ||
| 153 | return std::nullopt; | ||
| 154 | } | ||
| 155 | |||
| 156 | return parsed; | ||
| 157 | } | ||
| 158 | |||
| 159 | static inline bool isHexDigit(char c) { | ||
| 160 | return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F'); | ||
| 161 | } | ||
| 162 | |||
| 163 | static inline uint8_t fromHex(char c) { | ||
| 164 | if (c >= '0' && c <= '9') return static_cast<uint8_t>(c - '0'); | ||
| 165 | else if (c >= 'A' && c <= 'F') return static_cast<uint8_t>(c - 'A' + 10); | ||
| 166 | return 0; | ||
| 167 | } | ||
| 168 | |||
| 169 | static std::optional<RgbColor> parseRgbColor(std::string_view src) { | ||
| 170 | bool valid = src.size() == 6 | ||
| 171 | && isHexDigit(src[0]) && isHexDigit(src[1]) | ||
| 172 | && isHexDigit(src[2]) && isHexDigit(src[3]) | ||
| 173 | && isHexDigit(src[4]) && isHexDigit(src[5]); | ||
| 174 | if (!valid) return std::nullopt; | ||
| 175 | uint8_t r = static_cast<uint8_t>(fromHex(src[0]) << 4) + fromHex(src[1]); | ||
| 176 | uint8_t g = static_cast<uint8_t>(fromHex(src[2]) << 4) + fromHex(src[3]); | ||
| 177 | uint8_t b = static_cast<uint8_t>(fromHex(src[4]) << 4) + fromHex(src[5]); | ||
| 178 | return RgbColor{ r, g, b }; | ||
| 179 | } | ||
| 180 | |||
| 181 | std::optional<RgbColor> Kv1Parser::requireRgbColor(std::string_view field, bool mandatory, std::string_view value) { | ||
| 182 | if (value.empty()) { | ||
| 183 | if (mandatory) | ||
| 184 | record_errors.push_back(std::format("{} is required, but has no value", field)); | ||
| 185 | return std::nullopt; | ||
| 186 | } | ||
| 187 | auto parsed = parseRgbColor(value); | ||
| 188 | if (!parsed.has_value()) | ||
| 189 | record_errors.push_back(std::format("{} should be an RGB color, i.e. a sequence of six hexadecimally represented nibbles", field)); | ||
| 190 | return parsed; | ||
| 191 | } | ||
| 192 | |||
| 193 | std::optional<double> Kv1Parser::requireRdCoord(std::string_view field, bool mandatory, size_t min_digits, std::string_view value) { | ||
| 194 | if (value.empty()) { | ||
| 195 | if (mandatory) | ||
| 196 | record_errors.push_back(std::format("{} is required, but has no value", field)); | ||
| 197 | return std::nullopt; | ||
| 198 | } | ||
| 199 | if (value.size() > 15) { | ||
| 200 | record_errors.push_back(std::format("{} may not have more than 15 characters", field)); | ||
| 201 | return std::nullopt; | ||
| 202 | } | ||
| 203 | |||
| 204 | double parsed; | ||
| 205 | auto [ptr, ec] = std::from_chars(value.data(), value.data() + value.size(), parsed, std::chars_format::fixed); | ||
| 206 | if (ec != std::errc()) { | ||
| 207 | record_errors.push_back(std::format("{} has a bad value that cannot be parsed as a number", field)); | ||
| 208 | return std::nullopt; | ||
| 209 | } | ||
| 210 | if (ptr != value.data() + value.size()) { | ||
| 211 | record_errors.push_back(std::format("{} contains characters that were not parsed as a number", field)); | ||
| 212 | return std::nullopt; | ||
| 213 | } | ||
| 214 | |||
| 215 | size_t digits = countDigits(static_cast<long>(parsed)); | ||
| 216 | if (digits < min_digits) { | ||
| 217 | record_errors.push_back(std::format("{} contains less digits (in the integral part) ({}) than required ({}) [value: {}]", | ||
| 218 | field, digits, min_digits, value)); | ||
| 219 | return std::nullopt; | ||
| 220 | } | ||
| 221 | |||
| 222 | return parsed; | ||
| 223 | } | ||
| 224 | |||
| 225 | std::string Kv1Parser::eatString(std::string_view field, bool mandatory, size_t max_length) { | ||
| 226 | auto value = eatCell(field); | ||
| 227 | if (!record_errors.empty()) return {}; | ||
| 228 | requireString(field, mandatory, max_length, *value); | ||
| 229 | return std::move(*value); | ||
| 230 | } | ||
| 231 | |||
| 232 | std::optional<bool> Kv1Parser::eatBoolean(std::string_view field, bool mandatory) { | ||
| 233 | auto value = eatCell(field); | ||
| 234 | if (!record_errors.empty()) return {}; | ||
| 235 | return requireBoolean(field, mandatory, *value); | ||
| 236 | } | ||
| 237 | |||
| 238 | std::optional<double> Kv1Parser::eatNumber(std::string_view field, bool mandatory, size_t max_digits) { | ||
| 239 | auto value = eatCell(field); | ||
| 240 | if (!record_errors.empty()) return {}; | ||
| 241 | return requireNumber(field, mandatory, max_digits, *value); | ||
| 242 | } | ||
| 243 | |||
| 244 | std::optional<RgbColor> Kv1Parser::eatRgbColor(std::string_view field, bool mandatory) { | ||
| 245 | auto value = eatCell(field); | ||
| 246 | if (!record_errors.empty()) return {}; | ||
| 247 | return requireRgbColor(field, mandatory, *value); | ||
| 248 | } | ||
| 249 | |||
| 250 | std::optional<double> Kv1Parser::eatRdCoord(std::string_view field, bool mandatory, size_t min_digits) { | ||
| 251 | auto value = eatCell(field); | ||
| 252 | if (!record_errors.empty()) return {}; | ||
| 253 | return requireRdCoord(field, mandatory, min_digits, *value); | ||
| 254 | } | ||
| 255 | |||
| 256 | std::string Kv1Parser::parseHeader() { | ||
| 257 | auto record_type = eatString("<header>.Recordtype", true, 10); | ||
| 258 | auto version_number = eatString("<header>.VersionNumber", true, 2); | ||
| 259 | auto implicit_explicit = eatString("<header>.Implicit/Explicit", true, 1); | ||
| 260 | if (!record_errors.empty()) return {}; | ||
| 261 | |||
| 262 | if (version_number != "1") { | ||
| 263 | record_errors.push_back("<header>.VersionNumber should be 1"); | ||
| 264 | return ""; | ||
| 265 | } | ||
| 266 | if (implicit_explicit != "I") { | ||
| 267 | record_errors.push_back("<header>.Implicit/Explicit should be 'I'"); | ||
| 268 | return ""; | ||
| 269 | } | ||
| 270 | |||
| 271 | return record_type; | ||
| 272 | } | ||
| 273 | |||
| 274 | void Kv1Parser::eatRestOfRow() { | ||
| 275 | while (!atEnd() && cur()->type != KV1_TOKEN_ROW_END) pos++; | ||
| 276 | } | ||
| 277 | |||
| 278 | void Kv1Parser::parse() { | ||
| 279 | while (!atEnd()) { | ||
| 280 | eatRowEnds(); | ||
| 281 | if (atEnd()) return; | ||
| 282 | |||
| 283 | std::string record_type = parseHeader(); | ||
| 284 | if (!record_errors.empty()) break; | ||
| 285 | if (!type_parsers.contains(record_type)) { | ||
| 286 | warns.push_back(std::format("Recordtype ({}) is bad or names a record type that this program cannot process", | ||
| 287 | record_type)); | ||
| 288 | eatRestOfRow(); | ||
| 289 | continue; | ||
| 290 | } | ||
| 291 | |||
| 292 | ParseFunc parseType = Kv1Parser::type_parsers.at(record_type); | ||
| 293 | (this->*parseType)(); | ||
| 294 | if (cur() && cur()->type != KV1_TOKEN_ROW_END) { | ||
| 295 | record_errors.push_back(std::format("Parser function for Recordtype ({}) did not eat all record fields", | ||
| 296 | record_type)); | ||
| 297 | eatRestOfRow(); | ||
| 298 | } | ||
| 299 | if (!record_errors.empty()) { | ||
| 300 | global_errors.insert(global_errors.end(), record_errors.begin(), record_errors.end()); | ||
| 301 | record_errors.clear(); | ||
| 302 | } | ||
| 303 | } | ||
| 304 | } | ||
| 305 | |||
| 306 | void Kv1Parser::parseOrganizationalUnit() { | ||
| 307 | auto data_owner_code = eatString("ORUN.DataOwnerCode", true, 10); | ||
| 308 | auto organizational_unit_code = eatString("ORUN.OrganizationalUnitCode", true, 10); | ||
| 309 | auto name = eatString("ORUN.Name", true, 50); | ||
| 310 | auto organizational_unit_type = eatString("ORUN.OrganizationalUnitType", true, 10); | ||
| 311 | auto description = eatString("ORUN.Description", false, 255); | ||
| 312 | if (!record_errors.empty()) return; | ||
| 313 | |||
| 314 | records.organizational_units.emplace_back( | ||
| 315 | Kv1OrganizationalUnit::Key( | ||
| 316 | data_owner_code, | ||
| 317 | organizational_unit_code), | ||
| 318 | name, | ||
| 319 | organizational_unit_type, | ||
| 320 | description); | ||
| 321 | } | ||
| 322 | |||
| 323 | static inline bool isDigit(char c) { | ||
| 324 | return c >= '0' && c <= '9'; | ||
| 325 | } | ||
| 326 | |||
| 327 | // Parse a string of the format YYYY-MM-DD. | ||
| 328 | static std::optional<std::chrono::year_month_day> parseYyyymmdd(std::string_view src) { | ||
| 329 | bool valid = src.size() == 10 | ||
| 330 | && isDigit(src[0]) && isDigit(src[1]) | ||
| 331 | && isDigit(src[2]) && isDigit(src[3]) && src[4] == '-' | ||
| 332 | && isDigit(src[5]) && isDigit(src[6]) && src[7] == '-' | ||
| 333 | && isDigit(src[8]) && isDigit(src[9]); | ||
| 334 | if (!valid) return std::nullopt; | ||
| 335 | int year = (src[0] - '0') * 1000 + (src[1] - '0') * 100 + (src[2] - '0') * 10 + src[3] - '0'; | ||
| 336 | int month = (src[5] - '0') * 10 + src[6] - '0'; | ||
| 337 | int day = (src[8] - '0') * 10 + src[9] - '0'; | ||
| 338 | return std::chrono::year(year) / std::chrono::month(month) / std::chrono::day(day); | ||
| 339 | } | ||
| 340 | |||
| 341 | // Parse a string of the format HH:MM:SS. | ||
| 342 | static std::optional<std::chrono::hh_mm_ss<std::chrono::seconds>> parseHhmmss(std::string_view src) { | ||
| 343 | bool valid = src.size() == 8 | ||
| 344 | && isDigit(src[0]) && isDigit(src[1]) && src[2] == ':' | ||
| 345 | && isDigit(src[3]) && isDigit(src[4]) && src[5] == ':' | ||
| 346 | && isDigit(src[6]) && isDigit(src[7]); | ||
| 347 | if (!valid) return std::nullopt; | ||
| 348 | int hh = (src[0] - '0') * 10 + src[1] - '0'; | ||
| 349 | int mm = (src[3] - '0') * 10 + src[4] - '0'; | ||
| 350 | int ss = (src[6] - '0') * 10 + src[7] - '0'; | ||
| 351 | // The check for the hour not being greater than 32 comes from the fact the | ||
| 352 | // specification explicitly allows hours greater than 23, noting that the | ||
| 353 | // period 24:00-32:00 is equivalent to 00:00-08:00 in the next day, for | ||
| 354 | // exploitation of two days. | ||
| 355 | if (hh > 32 || mm > 59 || ss > 59) return std::nullopt; | ||
| 356 | return std::chrono::hh_mm_ss(std::chrono::hours(hh) + std::chrono::minutes(mm) + std::chrono::seconds(ss)); | ||
| 357 | } | ||
| 358 | |||
| 359 | static std::optional<std::chrono::sys_seconds> parseDateTime(std::string_view src, const std::chrono::time_zone *amsterdam, std::string_view *error = nullptr) { | ||
| 360 | #define ERROR(err) do { if (error) *error = err; return std::nullopt; } while (0) | ||
| 361 | if (src.size() > 23) ERROR("timestamp string is too big"); | ||
| 362 | if (src.size() < 17) ERROR("timestamp string is too small"); | ||
| 363 | |||
| 364 | bool valid_year = isDigit(src[0]) && isDigit(src[1]) && isDigit(src[2]) && isDigit(src[3]); | ||
| 365 | if (!valid_year) ERROR("year has bad format"); | ||
| 366 | |||
| 367 | size_t month_off = src[4] == '-' ? 5 : 4; | ||
| 368 | size_t day_off = src[month_off + 2] == '-' ? month_off + 3 : month_off + 2; | ||
| 369 | size_t time_off = day_off + 2; | ||
| 370 | if (src[time_off] != 'T' && src[time_off] != ' ') | ||
| 371 | ERROR("missing date/time separator"); | ||
| 372 | size_t tzd_off = time_off + 9; | ||
| 373 | // For clarity, TZD stands for Time Zone Designator. It often takes the form | ||
| 374 | // of Z (Zulu, UTC+00:00) or as an offset from UTC in hours and minutes, | ||
| 375 | // formatted as +|-HH:MM (e.g. +01:00, -12:00). | ||
| 376 | |||
| 377 | if (time_off + 8 >= src.size()) ERROR("bad format, not enough space for hh:mm:ss"); | ||
| 378 | |||
| 379 | int year = (src[0] - '0') * 1000 + (src[1] - '0') * 100 + (src[2] - '0') * 10 + src[3] - '0'; | ||
| 380 | int month = (src[month_off] - '0') * 10 + src[month_off + 1] - '0'; | ||
| 381 | int day = (src[day_off] - '0') * 10 + src[day_off + 1] - '0'; | ||
| 382 | int hour = (src[time_off + 1] - '0') * 10 + src[time_off + 2] - '0'; | ||
| 383 | int minute = (src[time_off + 4] - '0') * 10 + src[time_off + 5] - '0'; | ||
| 384 | int second = (src[time_off + 7] - '0') * 10 + src[time_off + 8] - '0'; | ||
| 385 | |||
| 386 | auto date = std::chrono::year(year) / std::chrono::month(month) / std::chrono::day(day); | ||
| 387 | auto time = std::chrono::hours(hour) + std::chrono::minutes(minute) + std::chrono::seconds(second); | ||
| 388 | |||
| 389 | std::chrono::sys_seconds unix_start_of_day; | ||
| 390 | if (tzd_off < src.size()) { | ||
| 391 | unix_start_of_day = std::chrono::sys_days(date); | ||
| 392 | } else { | ||
| 393 | auto local_days = std::chrono::local_days(date); | ||
| 394 | std::chrono::zoned_seconds zoned_start_of_day = std::chrono::zoned_time(amsterdam, local_days); | ||
| 395 | unix_start_of_day = std::chrono::sys_seconds(zoned_start_of_day); | ||
| 396 | } | ||
| 397 | |||
| 398 | std::chrono::minutes offset(0); | ||
| 399 | if (tzd_off + 1 == src.size() && src[tzd_off] != 'Z') { | ||
| 400 | ERROR("bad TZD (missing Zulu indicator)"); | ||
| 401 | } else if (tzd_off + 6 == src.size()) { | ||
| 402 | bool valid_tzd = (src[tzd_off] == '+' || src[tzd_off] == '-') | ||
| 403 | && isDigit(src[tzd_off + 1]) && isDigit(src[tzd_off + 2]) && src[tzd_off + 3] == ':' | ||
| 404 | && isDigit(src[tzd_off + 4]) && isDigit(src[tzd_off + 5]); | ||
| 405 | if (!valid_tzd) ERROR("bad offset TZD format (expected +|-hh:mm)"); | ||
| 406 | int sign = src[tzd_off] == '-' ? -1 : 1; | ||
| 407 | int tzd_hh = (src[tzd_off + 1] - '0') * 10 + src[tzd_off + 2] - '0'; | ||
| 408 | int tzd_mm = (src[tzd_off + 3] - '0') * 10 + src[tzd_off + 4] - '0'; | ||
| 409 | offset = sign * std::chrono::minutes(tzd_hh * 60 + tzd_mm); | ||
| 410 | } else if (tzd_off < src.size()) { | ||
| 411 | // There is a TZD but we literally have no clue how to parse it :/ | ||
| 412 | ERROR("cannot parse TZD of unexpected length"); | ||
| 413 | } | ||
| 414 | |||
| 415 | return unix_start_of_day + time - offset; | ||
| 416 | #undef ERROR | ||
| 417 | } | ||
| 418 | |||
| 419 | void Kv1Parser::parseHigherOrganizationalUnit() { | ||
| 420 | auto data_owner_code = eatString("ORUNORUN.DataOwnerCode", true, 10); | ||
| 421 | auto organizational_unit_code_parent = eatString("ORUNORUN.OrganizationalUnitCodeParent", true, 10); | ||
| 422 | auto organizational_unit_code_child = eatString("ORUNORUN.OrganizationalUnitCodeChild", true, 10); | ||
| 423 | auto valid_from_raw = eatString("ORUNORUN.ValidFrom", true, 10); | ||
| 424 | if (!record_errors.empty()) return; | ||
| 425 | |||
| 426 | auto valid_from = parseYyyymmdd(valid_from_raw); | ||
| 427 | if (!valid_from) { | ||
| 428 | record_errors.push_back("ORUNORUN.ValidFrom has invalid format, should be YYYY-MM-DD"); | ||
| 429 | return; | ||
| 430 | } | ||
| 431 | |||
| 432 | records.higher_organizational_units.emplace_back( | ||
| 433 | Kv1HigherOrganizationalUnit::Key( | ||
| 434 | data_owner_code, | ||
| 435 | organizational_unit_code_parent, | ||
| 436 | organizational_unit_code_child, | ||
| 437 | *valid_from)); | ||
| 438 | } | ||
| 439 | |||
| 440 | void Kv1Parser::parseUserStopPoint() { | ||
| 441 | auto data_owner_code = eatString ("USRSTOP.DataOwnerCode", true, 10); | ||
| 442 | auto user_stop_code = eatString ("USRSTOP.UserStopCode", true, 10); | ||
| 443 | auto timing_point_code = eatString ("USRSTOP.TimingPointCode", false, 10); | ||
| 444 | auto get_in = eatBoolean("USRSTOP.GetIn", true ); | ||
| 445 | auto get_out = eatBoolean("USRSTOP.GetOut", true ); | ||
| 446 | eatCell ("USRSTOP.<deprecated field #1>" ); | ||
| 447 | auto name = eatString ("USRSTOP.Name", true, 50); | ||
| 448 | auto town = eatString ("USRSTOP.Town", true, 50); | ||
| 449 | auto user_stop_area_code = eatString ("USRSTOP.UserStopAreaCode", false, 10); | ||
| 450 | auto stop_side_code = eatString ("USRSTOP.StopSideCode", true, 10); | ||
| 451 | eatCell ("USRSTOP.<deprecated field #2>" ); | ||
| 452 | eatCell ("USRSTOP.<deprecated field #3>" ); | ||
| 453 | auto minimal_stop_time = eatNumber ("USRSTOP.MinimalStopTime", true, 5); | ||
| 454 | auto stop_side_length = eatNumber ("USRSTOP.StopSideLength", false, 3); | ||
| 455 | auto description = eatString ("USRSTOP.Description", false, 255); | ||
| 456 | auto user_stop_type = eatString ("USRSTOP.UserStopType", true, 10); | ||
| 457 | auto quay_code = eatString ("USRSTOP.QuayCode", false, 30); | ||
| 458 | if (!record_errors.empty()) return; | ||
| 459 | |||
| 460 | records.user_stop_points.emplace_back( | ||
| 461 | Kv1UserStopPoint::Key( | ||
| 462 | data_owner_code, | ||
| 463 | user_stop_code), | ||
| 464 | timing_point_code, | ||
| 465 | *get_in, | ||
| 466 | *get_out, | ||
| 467 | name, | ||
| 468 | town, | ||
| 469 | user_stop_area_code, | ||
| 470 | stop_side_code, | ||
| 471 | *minimal_stop_time, | ||
| 472 | stop_side_length, | ||
| 473 | description, | ||
| 474 | user_stop_type, | ||
| 475 | quay_code); | ||
| 476 | } | ||
| 477 | |||
| 478 | void Kv1Parser::parseUserStopArea() { | ||
| 479 | auto data_owner_code = eatString("USRSTAR.DataOwnerCode", true, 10); | ||
| 480 | auto user_stop_area_code = eatString("USRSTAR.UserStopAreaCode", true, 10); | ||
| 481 | auto name = eatString("USRSTAR.Name", true, 50); | ||
| 482 | auto town = eatString("USRSTAR.Town", true, 50); | ||
| 483 | eatCell ("USRSTAR.<deprecated field #1>" ); | ||
| 484 | eatCell ("USRSTAR.<deprecated field #2>" ); | ||
| 485 | auto description = eatString("USRSTAR.Description", false, 255); | ||
| 486 | if (!record_errors.empty()) return; | ||
| 487 | |||
| 488 | records.user_stop_areas.emplace_back( | ||
| 489 | Kv1UserStopArea::Key( | ||
| 490 | data_owner_code, | ||
| 491 | user_stop_area_code), | ||
| 492 | name, | ||
| 493 | town, | ||
| 494 | description); | ||
| 495 | } | ||
| 496 | |||
| 497 | void Kv1Parser::parseTimingLink() { | ||
| 498 | auto data_owner_code = eatString("TILI.DataOwnerCode", true, 10); | ||
| 499 | auto user_stop_code_begin = eatString("TILI.UserStopCodeBegin", true, 10); | ||
| 500 | auto user_stop_code_end = eatString("TILI.UserStopCodeEnd", true, 10); | ||
| 501 | auto minimal_drive_time = eatNumber("TILI.MinimalDriveTime", false, 5); | ||
| 502 | auto description = eatString("TILI.Description", false, 255); | ||
| 503 | if (!record_errors.empty()) return; | ||
| 504 | |||
| 505 | records.timing_links.emplace_back( | ||
| 506 | Kv1TimingLink::Key( | ||
| 507 | data_owner_code, | ||
| 508 | user_stop_code_begin, | ||
| 509 | user_stop_code_end), | ||
| 510 | minimal_drive_time, | ||
| 511 | description); | ||
| 512 | } | ||
| 513 | |||
| 514 | void Kv1Parser::parseLink() { | ||
| 515 | auto data_owner_code = eatString("LINK.DataOwnerCode", true, 10); | ||
| 516 | auto user_stop_code_begin = eatString("LINK.UserStopCodeBegin", true, 10); | ||
| 517 | auto user_stop_code_end = eatString("LINK.UserStopCodeEnd", true, 10); | ||
| 518 | eatCell("LINK.<deprecated field #1>" ); | ||
| 519 | auto distance = eatNumber("LINK.Distance", true, 6); | ||
| 520 | auto description = eatString("LINK.Description", false, 255); | ||
| 521 | auto transport_type = eatString("LINK.TransportType", true, 5); | ||
| 522 | if (!record_errors.empty()) return; | ||
| 523 | |||
| 524 | records.links.emplace_back( | ||
| 525 | Kv1Link::Key( | ||
| 526 | data_owner_code, | ||
| 527 | user_stop_code_begin, | ||
| 528 | user_stop_code_end, | ||
| 529 | transport_type), | ||
| 530 | *distance, | ||
| 531 | description); | ||
| 532 | } | ||
| 533 | |||
| 534 | void Kv1Parser::parseLine() { | ||
| 535 | auto data_owner_code = eatString ("LINE.DataOwnerCode", true, 10); | ||
| 536 | auto line_planning_number = eatString ("LINE.LinePlanningNumber", true, 10); | ||
| 537 | auto line_public_number = eatString ("LINE.LinePublicNumber", true, 4); | ||
| 538 | auto line_name = eatString ("LINE.LineName", true, 50); | ||
| 539 | auto line_ve_tag_number = eatNumber ("LINE.LineVeTagNumber", true, 3); | ||
| 540 | auto description = eatString ("LINE.Description", false, 255); | ||
| 541 | auto transport_type = eatString ("LINE.TransportType", true, 5); | ||
| 542 | auto line_icon = eatNumber ("LINE.LineIcon", false, 4); | ||
| 543 | auto line_color = eatRgbColor("LINE.LineColor", false ); | ||
| 544 | auto line_text_color = eatRgbColor("LINE.LineTextColor", false ); | ||
| 545 | if (!record_errors.empty()) return; | ||
| 546 | |||
| 547 | // NOTE: This check, although it should be performed to comply with the | ||
| 548 | // specification, is not actually honored by transit operators (such as | ||
| 549 | // Connexxion) :/ That's enough reason to keep it disabled here for now. | ||
| 550 | // if (*line_ve_tag_number < 0 || *line_ve_tag_number > 399) { | ||
| 551 | // record_errors.push_back(std::format("LINE.LineVeTagNumber is out of range [0-399] with value {}", *line_ve_tag_number)); | ||
| 552 | // return; | ||
| 553 | // } | ||
| 554 | if (*line_ve_tag_number != static_cast<short>(*line_ve_tag_number)) | ||
| 555 | record_errors.push_back("LINE.LineVeTagNumber should be an integer"); | ||
| 556 | if (line_icon && *line_icon != static_cast<short>(*line_icon)) | ||
| 557 | record_errors.push_back("LINE.LineIcon should be an integer"); | ||
| 558 | if (!record_errors.empty()) return; | ||
| 559 | |||
| 560 | records.lines.emplace_back( | ||
| 561 | Kv1Line::Key( | ||
| 562 | data_owner_code, | ||
| 563 | line_planning_number), | ||
| 564 | line_public_number, | ||
| 565 | line_name, | ||
| 566 | static_cast<short>(*line_ve_tag_number), | ||
| 567 | description, | ||
| 568 | transport_type, | ||
| 569 | static_cast<std::optional<short>>(line_icon), | ||
| 570 | line_color, | ||
| 571 | line_text_color); | ||
| 572 | } | ||
| 573 | |||
| 574 | void Kv1Parser::parseDestination() { | ||
| 575 | auto data_owner_code = eatString ("DEST.DataOwnerCode", true, 10); | ||
| 576 | auto dest_code = eatString ("DEST.DestCode", true, 10); | ||
| 577 | auto dest_name_full = eatString ("DEST.DestNameFull", true, 50); | ||
| 578 | auto dest_name_main = eatString ("DEST.DestNameMain", true, 24); | ||
| 579 | auto dest_name_detail = eatString ("DEST.DestNameDetail", false, 24); | ||
| 580 | auto relevant_dest_name_detail = eatBoolean ("DEST.RelevantDestNameDetail", true ); | ||
| 581 | auto dest_name_main_21 = eatString ("DEST.DestNameMain21", true, 21); | ||
| 582 | auto dest_name_detail_21 = eatString ("DEST.DestNameDetail21", false, 21); | ||
| 583 | auto dest_name_main_19 = eatString ("DEST.DestNameMain19", true, 19); | ||
| 584 | auto dest_name_detail_19 = eatString ("DEST.DestNameDetail19", false, 19); | ||
| 585 | auto dest_name_main_16 = eatString ("DEST.DestNameMain16", true, 16); | ||
| 586 | auto dest_name_detail_16 = eatString ("DEST.DestNameDetail16", false, 16); | ||
| 587 | auto dest_icon = eatNumber ("DEST.DestIcon", false, 4); | ||
| 588 | auto dest_color = eatRgbColor("DEST.DestColor", false ); | ||
| 589 | // NOTE: Deviating from the offical KV1 specification here. It specifies that | ||
| 590 | // the maximum length for this field should be 30, but then proceeds to | ||
| 591 | // specify that it should contain a RGB value comprising of three | ||
| 592 | // hexadecimally encoded octets, i.e. six characters. We assume that the | ||
| 593 | // latter is correct and the intended interpretation. | ||
| 594 | auto dest_text_color = eatRgbColor("DEST.DestTextColor", false ); | ||
| 595 | if (!record_errors.empty()) return; | ||
| 596 | |||
| 597 | if (dest_icon && *dest_icon != static_cast<short>(*dest_icon)) { | ||
| 598 | record_errors.push_back("DEST.DestIcon should be an integer"); | ||
| 599 | return; | ||
| 600 | } | ||
| 601 | |||
| 602 | records.destinations.emplace_back( | ||
| 603 | Kv1Destination::Key( | ||
| 604 | data_owner_code, | ||
| 605 | dest_code), | ||
| 606 | dest_name_full, | ||
| 607 | dest_name_main, | ||
| 608 | dest_name_detail, | ||
| 609 | *relevant_dest_name_detail, | ||
| 610 | dest_name_main_21, | ||
| 611 | dest_name_detail_21, | ||
| 612 | dest_name_main_19, | ||
| 613 | dest_name_detail_19, | ||
| 614 | dest_name_main_16, | ||
| 615 | dest_name_detail_16, | ||
| 616 | dest_icon, | ||
| 617 | dest_color, | ||
| 618 | dest_text_color); | ||
| 619 | } | ||
| 620 | |||
| 621 | void Kv1Parser::parseJourneyPattern() { | ||
| 622 | auto data_owner_code = eatString("JOPA.DataOwnerCode", true, 10); | ||
| 623 | auto line_planning_number = eatString("JOPA.LinePlanningNumber", true, 10); | ||
| 624 | auto journey_pattern_code = eatString("JOPA.JourneyPatternCode", true, 10); | ||
| 625 | auto journey_pattern_type = eatString("JOPA.JourneyPatternType", true, 10); | ||
| 626 | auto direction = eatString("JOPA.Direction", true, 1); | ||
| 627 | auto description = eatString("JOPA.Description", false, 255); | ||
| 628 | if (!record_errors.empty()) return; | ||
| 629 | |||
| 630 | if (direction != "1" && direction != "2" && direction != "A" && direction != "B") { | ||
| 631 | record_errors.push_back("JOPA.Direction should be in [1, 2, A, B]"); | ||
| 632 | return; | ||
| 633 | } | ||
| 634 | |||
| 635 | records.journey_patterns.emplace_back( | ||
| 636 | Kv1JourneyPattern::Key( | ||
| 637 | data_owner_code, | ||
| 638 | line_planning_number, | ||
| 639 | journey_pattern_code), | ||
| 640 | journey_pattern_type, | ||
| 641 | direction[0], | ||
| 642 | description); | ||
| 643 | } | ||
| 644 | |||
| 645 | void Kv1Parser::parseConcessionFinancerRelation() { | ||
| 646 | auto data_owner_code = eatString("CONFINREL.DataOwnerCode", true, 10); | ||
| 647 | auto con_fin_rel_code = eatString("CONFINREL.ConFinRelCode", true, 10); | ||
| 648 | auto concession_area_code = eatString("CONFINREL.ConcessionAreaCode", true, 10); | ||
| 649 | auto financer_code = eatString("CONFINREL.FinancerCode", false, 10); | ||
| 650 | if (!record_errors.empty()) return; | ||
| 651 | |||
| 652 | records.concession_financer_relations.emplace_back( | ||
| 653 | Kv1ConcessionFinancerRelation::Key( | ||
| 654 | data_owner_code, | ||
| 655 | con_fin_rel_code), | ||
| 656 | concession_area_code, | ||
| 657 | financer_code); | ||
| 658 | } | ||
| 659 | |||
| 660 | void Kv1Parser::parseConcessionArea() { | ||
| 661 | auto data_owner_code = eatString("CONAREA.DataOwnerCode", true, 10); | ||
| 662 | auto concession_area_code = eatString("CONAREA.ConcessionAreaCode", true, 10); | ||
| 663 | auto description = eatString("CONAREA.Description", true, 255); | ||
| 664 | if (!record_errors.empty()) return; | ||
| 665 | |||
| 666 | records.concession_areas.emplace_back( | ||
| 667 | Kv1ConcessionArea::Key( | ||
| 668 | data_owner_code, | ||
| 669 | concession_area_code), | ||
| 670 | description); | ||
| 671 | } | ||
| 672 | |||
| 673 | void Kv1Parser::parseFinancer() { | ||
| 674 | auto data_owner_code = eatString("FINANCER.DataOwnerCode", true, 10); | ||
| 675 | auto financer_code = eatString("FINANCER.FinancerCode", true, 10); | ||
| 676 | auto description = eatString("FINANCER.Description", true, 255); | ||
| 677 | if (!record_errors.empty()) return; | ||
| 678 | |||
| 679 | records.financers.emplace_back( | ||
| 680 | Kv1Financer::Key( | ||
| 681 | data_owner_code, | ||
| 682 | financer_code), | ||
| 683 | description); | ||
| 684 | } | ||
| 685 | |||
| 686 | void Kv1Parser::parseJourneyPatternTimingLink() { | ||
| 687 | auto data_owner_code = eatString ("JOPATILI.DataOwnerCode", true, 10); | ||
| 688 | auto line_planning_number = eatString ("JOPATILI.LinePlanningNumber", true, 10); | ||
| 689 | auto journey_pattern_code = eatString ("JOPATILI.JourneyPatternCode", true, 10); | ||
| 690 | auto timing_link_order = eatNumber ("JOPATILI.TimingLinkOrder", true, 3); | ||
| 691 | auto user_stop_code_begin = eatString ("JOPATILI.UserStopCodeBegin", true, 10); | ||
| 692 | auto user_stop_code_end = eatString ("JOPATILI.UserStopCodeEnd", true, 10); | ||
| 693 | auto con_fin_rel_code = eatString ("JOPATILI.ConFinRelCode", true, 10); | ||
| 694 | auto dest_code = eatString ("JOPATILI.DestCode", true, 10); | ||
| 695 | eatCell ("JOPATILI.<deprecated field #1>" ); | ||
| 696 | auto is_timing_stop = eatBoolean ("JOPATILI.IsTimingStop", true ); | ||
| 697 | auto display_public_line = eatString ("JOPATILI.DisplayPublicLine", false, 4); | ||
| 698 | auto product_formula_type = eatNumber ("JOPATILI.ProductFormulaType", false, 4); | ||
| 699 | auto get_in = eatBoolean ("JOPATILI.GetIn", true ); | ||
| 700 | auto get_out = eatBoolean ("JOPATILI.GetOut", true ); | ||
| 701 | auto show_flexible_trip = eatString ("JOPATILI.ShowFlexibleTrip", false, 8); | ||
| 702 | auto line_dest_icon = eatNumber ("JOPATILI.LineDestIcon", false, 4); | ||
| 703 | auto line_dest_color = eatRgbColor("JOPATILI.LineDestColor", false ); | ||
| 704 | auto line_dest_text_color = eatRgbColor("JOPATILI.LineDestTextColor", false ); | ||
| 705 | if (!record_errors.empty()) return; | ||
| 706 | |||
| 707 | if (line_dest_icon && *line_dest_icon != static_cast<short>(*line_dest_icon)) | ||
| 708 | record_errors.push_back("JOPATILI.LineDestIcon should be an integer"); | ||
| 709 | if (!show_flexible_trip.empty() && show_flexible_trip != "TRUE" && | ||
| 710 | show_flexible_trip != "FALSE" && show_flexible_trip != "REALTIME") | ||
| 711 | record_errors.push_back("JOPATILI.ShowFlexibleTrip should be in BISON E21 values [TRUE, FALSE, REALTIME]"); | ||
| 712 | if (!record_errors.empty()) return; | ||
| 713 | |||
| 714 | records.journey_pattern_timing_links.emplace_back( | ||
| 715 | Kv1JourneyPatternTimingLink::Key( | ||
| 716 | data_owner_code, | ||
| 717 | line_planning_number, | ||
| 718 | journey_pattern_code, | ||
| 719 | static_cast<short>(*timing_link_order)), | ||
| 720 | user_stop_code_begin, | ||
| 721 | user_stop_code_end, | ||
| 722 | con_fin_rel_code, | ||
| 723 | dest_code, | ||
| 724 | *is_timing_stop, | ||
| 725 | display_public_line, | ||
| 726 | product_formula_type, | ||
| 727 | *get_in, | ||
| 728 | *get_out, | ||
| 729 | show_flexible_trip, | ||
| 730 | line_dest_icon, | ||
| 731 | line_dest_color, | ||
| 732 | line_dest_text_color); | ||
| 733 | } | ||
| 734 | |||
| 735 | void Kv1Parser::parsePoint() { | ||
| 736 | auto data_owner_code = eatString("POINT.DataOwnerCode", true, 10); | ||
| 737 | auto point_code = eatString("POINT.PointCode", true, 10); | ||
| 738 | eatCell ("POINT.<deprecated field #1>" ); | ||
| 739 | auto point_type = eatString("POINT.PointType", true, 10); | ||
| 740 | auto coordinate_system_type = eatString("POINT.CoordinateSystemType", true, 10); | ||
| 741 | // NOTE: We deviate from the specification here once again. The specification | ||
| 742 | // notes that LocationX_EW should contain 'at least 6 positions'. Assuming | ||
| 743 | // that this is referring to the amount of digits, we have to lower this to | ||
| 744 | // 4. Otherwise, some positions in the Netherlands and Belgium are | ||
| 745 | // unrepresentable. | ||
| 746 | auto location_x_ew = eatRdCoord("POINT.LocationX_EW", true, 4); | ||
| 747 | auto location_y_ew = eatRdCoord("POINT.LocationX_EW", true, 6); | ||
| 748 | auto location_z = eatRdCoord("POINT.LocationZ", false, 0); | ||
| 749 | auto description = eatString ("POINT.Description", false, 255); | ||
| 750 | if (!record_errors.empty()) return; | ||
| 751 | |||
| 752 | records.points.emplace_back( | ||
| 753 | Kv1Point::Key( | ||
| 754 | std::move(data_owner_code), | ||
| 755 | std::move(point_code)), | ||
| 756 | std::move(point_type), | ||
| 757 | std::move(coordinate_system_type), | ||
| 758 | *location_x_ew, | ||
| 759 | *location_y_ew, | ||
| 760 | location_z, | ||
| 761 | std::move(description)); | ||
| 762 | } | ||
| 763 | |||
| 764 | void Kv1Parser::parsePointOnLink() { | ||
| 765 | auto data_owner_code = eatString("POOL.DataOwnerCode", true, 10); | ||
| 766 | auto user_stop_code_begin = eatString("POOL.UserStopCodeBegin", true, 10); | ||
| 767 | auto user_stop_code_end = eatString("POOL.UserStopCodeEnd", true, 10); | ||
| 768 | eatCell ("POOL.<deprecated field #1>" ); | ||
| 769 | auto point_data_owner_code = eatString("POOL.PointDataOwnerCode", true, 10); | ||
| 770 | auto point_code = eatString("POOL.PointCode", true, 10); | ||
| 771 | auto distance_since_start_of_link = eatNumber("POOL.DistanceSinceStartOfLink", true, 5); | ||
| 772 | auto segment_speed = eatNumber("POOL.SegmentSpeed", false, 4); | ||
| 773 | auto local_point_speed = eatNumber("POOL.LocalPointSpeed", false, 4); | ||
| 774 | auto description = eatString("POOL.Description", false, 255); | ||
| 775 | auto transport_type = eatString("POOL.TransportType", true, 5); | ||
| 776 | if (!record_errors.empty()) return; | ||
| 777 | |||
| 778 | records.point_on_links.emplace_back( | ||
| 779 | Kv1PointOnLink::Key( | ||
| 780 | data_owner_code, | ||
| 781 | user_stop_code_begin, | ||
| 782 | user_stop_code_end, | ||
| 783 | point_data_owner_code, | ||
| 784 | point_code, | ||
| 785 | transport_type), | ||
| 786 | *distance_since_start_of_link, | ||
| 787 | segment_speed, | ||
| 788 | local_point_speed, | ||
| 789 | std::move(description)); | ||
| 790 | } | ||
| 791 | |||
| 792 | void Kv1Parser::parseIcon() { | ||
| 793 | auto data_owner_code = eatString("ICON.DataOwnerCode", true, 10); | ||
| 794 | auto icon_number = eatNumber("ICON.IconNumber", true, 4); | ||
| 795 | auto icon_uri = eatString("ICON.IconURI", true, 1024); | ||
| 796 | if (!record_errors.empty()) return; | ||
| 797 | |||
| 798 | if (*icon_number != static_cast<short>(*icon_number)) { | ||
| 799 | record_errors.push_back("ICON.IconNumber should be an integer"); | ||
| 800 | return; | ||
| 801 | } | ||
| 802 | |||
| 803 | records.icons.emplace_back( | ||
| 804 | Kv1Icon::Key( | ||
| 805 | data_owner_code, | ||
| 806 | static_cast<short>(*icon_number)), | ||
| 807 | icon_uri); | ||
| 808 | } | ||
| 809 | |||
| 810 | void Kv1Parser::parseNotice() { | ||
| 811 | auto data_owner_code = eatString("NOTICE.DataOwnerCode", true, 10); | ||
| 812 | auto notice_code = eatString("NOTICE.NoticeCode", true, 20); | ||
| 813 | auto notice_content = eatString("NOTICE.NoticeContent", true, 1024); | ||
| 814 | if (!record_errors.empty()) return; | ||
| 815 | |||
| 816 | records.notices.emplace_back( | ||
| 817 | Kv1Notice::Key( | ||
| 818 | data_owner_code, | ||
| 819 | notice_code), | ||
| 820 | notice_content); | ||
| 821 | } | ||
| 822 | |||
| 823 | void Kv1Parser::parseNoticeAssignment() { | ||
| 824 | auto data_owner_code = eatString("NTCASSGNM.DataOwnerCode", true, 10); | ||
| 825 | auto notice_code = eatString("NTCASSGNM.NoticeCode", true, 20); | ||
| 826 | auto assigned_object = eatString("NTCASSGNM.AssignedObject", true, 8); | ||
| 827 | auto timetable_version_code = eatString("NTCASSGNM.TimetableVersionCode", false, 10); | ||
| 828 | auto organizational_unit_code = eatString("NTCASSGNM.OrganizationalUnitCode", false, 10); | ||
| 829 | auto schedule_code = eatString("NTCASSGNM.ScheduleCode", false, 10); | ||
| 830 | auto schedule_type_code = eatString("NTCASSGNM.ScheduleTypeCode", false, 10); | ||
| 831 | auto period_group_code = eatString("NTCASSGNM.PeriodGroupCode", false, 10); | ||
| 832 | auto specific_day_code = eatString("NTCASSGNM.SpecificDayCode", false, 10); | ||
| 833 | auto day_type = eatString("NTCASSGNM.DayType", false, 7); | ||
| 834 | auto line_planning_number = eatString("NTCASSGNM.LinePlanningNumber", true, 10); | ||
| 835 | auto journey_number = eatNumber("NTCASSGNM.JourneyNumber", false, 6); | ||
| 836 | auto stop_order = eatNumber("NTCASSGNM.StopOrder", false, 4); | ||
| 837 | auto journey_pattern_code = eatString("NTCASSGNM.JourneyPatternCode", false, 10); | ||
| 838 | auto timing_link_order = eatNumber("NTCASSGNM.TimingLinkOrder", false, 3); | ||
| 839 | auto user_stop_code = eatString("NTCASSGNM.UserStopCode", false, 10); | ||
| 840 | if (!record_errors.empty()) return; | ||
| 841 | |||
| 842 | if (journey_number && *journey_number != static_cast<short>(*journey_number)) | ||
| 843 | record_errors.push_back("NTCASSGNM.JourneyNumber should be an integer"); | ||
| 844 | if (journey_number && (*journey_number < 0 || *journey_number > 999'999)) | ||
| 845 | record_errors.push_back("NTCASSGNM.JourneyNumber should be within the range [0-999999]"); | ||
| 846 | if (stop_order && *stop_order != static_cast<short>(*stop_order)) | ||
| 847 | record_errors.push_back("NTCASSGNM.StopOrder should be an integer"); | ||
| 848 | if (!journey_number && (assigned_object == "PUJO" || assigned_object == "PUJOPASS")) | ||
| 849 | record_errors.push_back("NTCASSGNM.JourneyNumber is required for AssignedObject PUJO/PUJOPASS"); | ||
| 850 | if (journey_pattern_code.empty() && assigned_object == "JOPATILI") | ||
| 851 | record_errors.push_back("NTCASSGNM.JourneyPatternCode is required for AssignedObject JOPATILI"); | ||
| 852 | if (!record_errors.empty()) return; | ||
| 853 | |||
| 854 | records.notice_assignments.emplace_back( | ||
| 855 | data_owner_code, | ||
| 856 | notice_code, | ||
| 857 | assigned_object, | ||
| 858 | timetable_version_code, | ||
| 859 | organizational_unit_code, | ||
| 860 | schedule_code, | ||
| 861 | schedule_type_code, | ||
| 862 | period_group_code, | ||
| 863 | specific_day_code, | ||
| 864 | day_type, | ||
| 865 | line_planning_number, | ||
| 866 | static_cast<std::optional<int>>(journey_number), | ||
| 867 | static_cast<std::optional<short>>(stop_order), | ||
| 868 | journey_pattern_code, | ||
| 869 | timing_link_order, | ||
| 870 | user_stop_code); | ||
| 871 | } | ||
| 872 | |||
| 873 | void Kv1Parser::parseTimeDemandGroup() { | ||
| 874 | auto data_owner_code = eatString("TIMDEMGRP.DataOwnerCode", true, 10); | ||
| 875 | auto line_planning_number = eatString("TIMDEMGRP.LinePlanningNumber", true, 10); | ||
| 876 | auto journey_pattern_code = eatString("TIMDEMGRP.JourneyPatternCode", true, 10); | ||
| 877 | auto time_demand_group_code = eatString("TIMDEMGRP.TimeDemandGroupCode", true, 10); | ||
| 878 | if (!record_errors.empty()) return; | ||
| 879 | |||
| 880 | records.time_demand_groups.emplace_back( | ||
| 881 | Kv1TimeDemandGroup::Key( | ||
| 882 | data_owner_code, | ||
| 883 | line_planning_number, | ||
| 884 | journey_pattern_code, | ||
| 885 | time_demand_group_code)); | ||
| 886 | } | ||
| 887 | |||
| 888 | void Kv1Parser::parseTimeDemandGroupRunTime() { | ||
| 889 | auto data_owner_code = eatString("TIMDEMRNT.DataOwnerCode", true, 10); | ||
| 890 | auto line_planning_number = eatString("TIMDEMRNT.LinePlanningNumber", true, 10); | ||
| 891 | auto journey_pattern_code = eatString("TIMDEMRNT.JourneyPatternCode", true, 10); | ||
| 892 | auto time_demand_group_code = eatString("TIMDEMRNT.TimeDemandGroupCode", true, 10); | ||
| 893 | auto timing_link_order = eatNumber("TIMDEMRNT.TimingLinkOrder", true, 3); | ||
| 894 | auto user_stop_code_begin = eatString("TIMDEMRNT.UserStopCodeBegin", true, 10); | ||
| 895 | auto user_stop_code_end = eatString("TIMDEMRNT.UserStopCodeEnd", true, 10); | ||
| 896 | auto total_drive_time = eatNumber("TIMDEMRNT.TotalDriveTime", true, 5); | ||
| 897 | auto drive_time = eatNumber("TIMDEMRNT.DriveTime", true, 5); | ||
| 898 | auto expected_delay = eatNumber("TIMDEMRNT.ExpectedDelay", false, 5); | ||
| 899 | auto layover_time = eatNumber("TIMDEMRNT.LayOverTime", false, 5); | ||
| 900 | auto stop_wait_time = eatNumber("TIMDEMRNT.StopWaitTime", true, 5); | ||
| 901 | auto minimum_stop_time = eatNumber("TIMDEMRNT.MinimumStopTime", false, 5); | ||
| 902 | if (!record_errors.empty()) return; | ||
| 903 | |||
| 904 | if (timing_link_order && *timing_link_order != static_cast<short>(*timing_link_order)) { | ||
| 905 | record_errors.push_back("TIMDEMRNT.TimingLinkOrder should be an integer"); | ||
| 906 | return; | ||
| 907 | } | ||
| 908 | |||
| 909 | records.time_demand_group_run_times.emplace_back( | ||
| 910 | Kv1TimeDemandGroupRunTime::Key( | ||
| 911 | data_owner_code, | ||
| 912 | line_planning_number, | ||
| 913 | journey_pattern_code, | ||
| 914 | time_demand_group_code, | ||
| 915 | static_cast<short>(*timing_link_order)), | ||
| 916 | user_stop_code_begin, | ||
| 917 | user_stop_code_end, | ||
| 918 | *total_drive_time, | ||
| 919 | *drive_time, | ||
| 920 | expected_delay, | ||
| 921 | layover_time, | ||
| 922 | *stop_wait_time, | ||
| 923 | minimum_stop_time); | ||
| 924 | } | ||
| 925 | |||
| 926 | void Kv1Parser::parsePeriodGroup() { | ||
| 927 | auto data_owner_code = eatString("PEGR.DataOwnerCode", true, 10); | ||
| 928 | auto period_group_code = eatString("PEGR.PeriodGroupCode", true, 10); | ||
| 929 | auto description = eatString("PEGR.Description", false, 255); | ||
| 930 | if (!record_errors.empty()) return; | ||
| 931 | |||
| 932 | records.period_groups.emplace_back( | ||
| 933 | Kv1PeriodGroup::Key( | ||
| 934 | data_owner_code, | ||
| 935 | period_group_code), | ||
| 936 | description); | ||
| 937 | } | ||
| 938 | |||
| 939 | void Kv1Parser::parseSpecificDay() { | ||
| 940 | auto data_owner_code = eatString("SPECDAY.DataOwnerCode", true, 10); | ||
| 941 | auto specific_day_code = eatString("SPECDAY.SpecificDayCode", true, 10); | ||
| 942 | auto name = eatString("SPECDAY.Name", true, 50); | ||
| 943 | auto description = eatString("SPECDAY.Description", false, 255); | ||
| 944 | if (!record_errors.empty()) return; | ||
| 945 | |||
| 946 | records.specific_days.emplace_back( | ||
| 947 | Kv1SpecificDay::Key( | ||
| 948 | data_owner_code, | ||
| 949 | specific_day_code), | ||
| 950 | name, | ||
| 951 | description); | ||
| 952 | } | ||
| 953 | |||
| 954 | void Kv1Parser::parseTimetableVersion() { | ||
| 955 | auto data_owner_code = eatString("TIVE.DataOwnerCode", true, 10); | ||
| 956 | auto organizational_unit_code = eatString("TIVE.OrganizationalUnitCode", true, 10); | ||
| 957 | auto timetable_version_code = eatString("TIVE.TimetableVersionCode", true, 10); | ||
| 958 | auto period_group_code = eatString("TIVE.PeriodGroupCode", true, 10); | ||
| 959 | auto specific_day_code = eatString("TIVE.SpecificDayCode", true, 10); | ||
| 960 | auto valid_from_raw = eatString("TIVE.ValidFrom", true, 10); | ||
| 961 | auto timetable_version_type = eatString("TIVE.TimetableVersionType", true, 10); | ||
| 962 | auto valid_thru_raw = eatString("TIVE.ValidThru", false, 10); | ||
| 963 | auto description = eatString("TIVE.Description", false, 255); | ||
| 964 | if (!record_errors.empty()) return; | ||
| 965 | |||
| 966 | auto valid_from = parseYyyymmdd(valid_from_raw); | ||
| 967 | if (!valid_from) | ||
| 968 | record_errors.push_back("TIVE.ValidFrom has invalid format, should be YYYY-MM-DD"); | ||
| 969 | std::optional<std::chrono::year_month_day> valid_thru; | ||
| 970 | if (!valid_thru_raw.empty()) { | ||
| 971 | valid_thru = parseYyyymmdd(valid_thru_raw); | ||
| 972 | if (!valid_thru) { | ||
| 973 | record_errors.push_back("TIVE.ValidFrom has invalid format, should be YYYY-MM-DD"); | ||
| 974 | } | ||
| 975 | } | ||
| 976 | if (!description.empty()) | ||
| 977 | record_errors.push_back("TIVE.Description should be empty"); | ||
| 978 | if (!record_errors.empty()) return; | ||
| 979 | |||
| 980 | records.timetable_versions.emplace_back( | ||
| 981 | Kv1TimetableVersion::Key( | ||
| 982 | data_owner_code, | ||
| 983 | organizational_unit_code, | ||
| 984 | timetable_version_code, | ||
| 985 | period_group_code, | ||
| 986 | specific_day_code), | ||
| 987 | *valid_from, | ||
| 988 | timetable_version_type, | ||
| 989 | valid_thru, | ||
| 990 | description); | ||
| 991 | } | ||
| 992 | |||
| 993 | void Kv1Parser::parsePublicJourney() { | ||
| 994 | auto data_owner_code = eatString ("PUJO.DataOwnerCode", true, 10); | ||
| 995 | auto timetable_version_code = eatString ("PUJO.TimetableVersionCode", true, 10); | ||
| 996 | auto organizational_unit_code = eatString ("PUJO.OrganizationalUnitCode", true, 10); | ||
| 997 | auto period_group_code = eatString ("PUJO.PeriodGroupCode", true, 10); | ||
| 998 | auto specific_day_code = eatString ("PUJO.SpecificDayCode", true, 10); | ||
| 999 | auto day_type = eatString ("PUJO.DayType", true, 7); | ||
| 1000 | auto line_planning_number = eatString ("PUJO.LinePlanningNumber", true, 10); | ||
| 1001 | auto journey_number = eatNumber ("PUJO.JourneyNumber", true, 6); | ||
| 1002 | auto time_demand_group_code = eatString ("PUJO.TimeDemandGroupCode", true, 10); | ||
| 1003 | auto journey_pattern_code = eatString ("PUJO.JourneyPatternCode", true, 10); | ||
| 1004 | auto departure_time_raw = eatString ("PUJO.DepartureTime", true, 8); | ||
| 1005 | auto wheelchair_accessible = eatString ("PUJO.WheelChairAccessible", true, 13); | ||
| 1006 | auto data_owner_is_operator = eatBoolean("PUJO.DataOwnerIsOperator", true ); | ||
| 1007 | auto planned_monitored = eatBoolean("PUJO.PlannedMonitored", true ); | ||
| 1008 | auto product_formula_type = eatNumber ("PUJO.ProductFormulaType", false, 4); | ||
| 1009 | auto show_flexible_trip = eatString ("PUJO.ShowFlexibleTrip", false, 8); | ||
| 1010 | if (!record_errors.empty()) return; | ||
| 1011 | |||
| 1012 | auto departure_time = parseHhmmss(departure_time_raw); | ||
| 1013 | if (!departure_time) | ||
| 1014 | record_errors.push_back("PUJO.DepartureTime has a bad format"); | ||
| 1015 | if (*journey_number < 0 || *journey_number > 999'999) | ||
| 1016 | record_errors.push_back("PUJO.JourneyNumber should be within the range [0-999999]"); | ||
| 1017 | if (*journey_number != static_cast<int>(*journey_number)) | ||
| 1018 | record_errors.push_back("PUJO.JourneyNumber should be an integer"); | ||
| 1019 | if (product_formula_type && *product_formula_type != static_cast<short>(*product_formula_type)) | ||
| 1020 | record_errors.push_back("PUJO.ProductFormulaType should be an integer"); | ||
| 1021 | if (wheelchair_accessible != "ACCESSIBLE" && wheelchair_accessible != "NOTACCESSIBLE" && wheelchair_accessible != "UNKNOWN") | ||
| 1022 | record_errors.push_back("PUJO.WheelChairAccessible should be in BISON E3 values [ACCESSIBLE, NOTACCESSIBLE, UNKNOWN]"); | ||
| 1023 | if (!show_flexible_trip.empty() && show_flexible_trip != "TRUE" && | ||
| 1024 | show_flexible_trip != "FALSE" && show_flexible_trip != "REALTIME") | ||
| 1025 | record_errors.push_back("PUJO.ShowFlexibleTrip should be in BISON E21 values [TRUE, FALSE, REALTIME]"); | ||
| 1026 | if (!record_errors.empty()) return; | ||
| 1027 | |||
| 1028 | records.public_journeys.emplace_back( | ||
| 1029 | Kv1PublicJourney::Key( | ||
| 1030 | data_owner_code, | ||
| 1031 | timetable_version_code, | ||
| 1032 | organizational_unit_code, | ||
| 1033 | period_group_code, | ||
| 1034 | specific_day_code, | ||
| 1035 | day_type, | ||
| 1036 | line_planning_number, | ||
| 1037 | static_cast<int>(*journey_number)), | ||
| 1038 | time_demand_group_code, | ||
| 1039 | journey_pattern_code, | ||
| 1040 | *departure_time, | ||
| 1041 | wheelchair_accessible, | ||
| 1042 | *data_owner_is_operator, | ||
| 1043 | *planned_monitored, | ||
| 1044 | product_formula_type, | ||
| 1045 | show_flexible_trip); | ||
| 1046 | } | ||
| 1047 | |||
| 1048 | void Kv1Parser::parsePeriodGroupValidity() { | ||
| 1049 | auto data_owner_code = eatString("PEGRVAL.DataOwnerCode", true, 10); | ||
| 1050 | auto organizational_unit_code = eatString("PEGRVAL.OrganizationalUnitCode", true, 10); | ||
| 1051 | auto period_group_code = eatString("PEGRVAL.PeriodGroupCode", true, 10); | ||
| 1052 | auto valid_from_raw = eatString("PEGRVAL.ValidFrom", true, 10); | ||
| 1053 | auto valid_thru_raw = eatString("PEGRVAL.ValidThru", true, 10); | ||
| 1054 | if (!record_errors.empty()) return; | ||
| 1055 | |||
| 1056 | auto valid_from = parseYyyymmdd(valid_from_raw); | ||
| 1057 | auto valid_thru = parseYyyymmdd(valid_thru_raw); | ||
| 1058 | if (!valid_from) | ||
| 1059 | record_errors.push_back("PEGRVAL.ValidFrom has invalid format, should be YYYY-MM-DD"); | ||
| 1060 | if (!valid_thru) | ||
| 1061 | record_errors.push_back("PEGRVAL.ValidThru has invalid format, should be YYYY-MM-DD"); | ||
| 1062 | if (!record_errors.empty()) return; | ||
| 1063 | |||
| 1064 | records.period_group_validities.emplace_back( | ||
| 1065 | Kv1PeriodGroupValidity::Key( | ||
| 1066 | data_owner_code, | ||
| 1067 | organizational_unit_code, | ||
| 1068 | period_group_code, | ||
| 1069 | *valid_from), | ||
| 1070 | *valid_thru); | ||
| 1071 | } | ||
| 1072 | |||
| 1073 | void Kv1Parser::parseExceptionalOperatingDay() { | ||
| 1074 | auto data_owner_code = eatString("EXCOPDAY.DataOwnerCode", true, 10); | ||
| 1075 | auto organizational_unit_code = eatString("EXCOPDAY.OrganizationalUnitCode", true, 10); | ||
| 1076 | auto valid_date_raw = eatString("EXCOPDAY.ValidDate", true, 23); | ||
| 1077 | auto day_type_as_on = eatString("EXCOPDAY.DayTypeAsOn", true, 7); | ||
| 1078 | auto specific_day_code = eatString("EXCOPDAY.SpecificDayCode", true, 10); | ||
| 1079 | auto period_group_code = eatString("EXCOPDAY.PeriodGroupCode", false, 10); | ||
| 1080 | auto description = eatString("EXCOPDAY.Description", false, 255); | ||
| 1081 | if (!record_errors.empty()) return; | ||
| 1082 | |||
| 1083 | std::string_view error; | ||
| 1084 | auto valid_date = parseDateTime(valid_date_raw, amsterdam, &error); | ||
| 1085 | if (!valid_date) { | ||
| 1086 | record_errors.push_back(std::format("EXCOPDAY.ValidDate has an bad format (value: {}): {}", valid_date_raw, error)); | ||
| 1087 | return; | ||
| 1088 | } | ||
| 1089 | |||
| 1090 | records.exceptional_operating_days.emplace_back( | ||
| 1091 | Kv1ExceptionalOperatingDay::Key( | ||
| 1092 | data_owner_code, | ||
| 1093 | organizational_unit_code, | ||
| 1094 | *valid_date), | ||
| 1095 | day_type_as_on, | ||
| 1096 | specific_day_code, | ||
| 1097 | period_group_code, | ||
| 1098 | description); | ||
| 1099 | } | ||
| 1100 | |||
| 1101 | void Kv1Parser::parseScheduleVersion() { | ||
| 1102 | auto data_owner_code = eatString("SCHEDVERS.DataOwnerCode", true, 10); | ||
| 1103 | auto organizational_unit_code = eatString("SCHEDVERS.OrganizationalUnitCode", true, 10); | ||
| 1104 | auto schedule_code = eatString("SCHEDVERS.ScheduleCode", true, 10); | ||
| 1105 | auto schedule_type_code = eatString("SCHEDVERS.ScheduleTypeCode", true, 10); | ||
| 1106 | auto valid_from_raw = eatString("SCHEDVERS.ValidFrom", true, 10); | ||
| 1107 | auto valid_thru_raw = eatString("SCHEDVERS.ValidThru", false, 10); | ||
| 1108 | auto description = eatString("SCHEDVERS.Description", false, 255); | ||
| 1109 | if (!record_errors.empty()) return; | ||
| 1110 | |||
| 1111 | auto valid_from = parseYyyymmdd(valid_from_raw); | ||
| 1112 | if (!valid_from) | ||
| 1113 | record_errors.push_back("SCHEDVERS.ValidFrom has invalid format, should be YYYY-MM-DD"); | ||
| 1114 | std::optional<std::chrono::year_month_day> valid_thru; | ||
| 1115 | if (!valid_thru_raw.empty()) { | ||
| 1116 | valid_thru = parseYyyymmdd(valid_thru_raw); | ||
| 1117 | if (!valid_thru) { | ||
| 1118 | record_errors.push_back("SCHEDVERS.ValidFrom has invalid format, should be YYYY-MM-DD"); | ||
| 1119 | } | ||
| 1120 | } | ||
| 1121 | if (!description.empty()) | ||
| 1122 | record_errors.push_back("SCHEDVERS.Description should be empty"); | ||
| 1123 | if (!record_errors.empty()) return; | ||
| 1124 | |||
| 1125 | records.schedule_versions.emplace_back( | ||
| 1126 | Kv1ScheduleVersion::Key( | ||
| 1127 | data_owner_code, | ||
| 1128 | organizational_unit_code, | ||
| 1129 | schedule_code, | ||
| 1130 | schedule_type_code), | ||
| 1131 | *valid_from, | ||
| 1132 | valid_thru, | ||
| 1133 | description); | ||
| 1134 | } | ||
| 1135 | |||
| 1136 | void Kv1Parser::parsePublicJourneyPassingTimes() { | ||
| 1137 | auto data_owner_code = eatString ("PUJOPASS.DataOwnerCode", true, 10); | ||
| 1138 | auto organizational_unit_code = eatString ("PUJOPASS.OrganizationalUnitCode", true, 10); | ||
| 1139 | auto schedule_code = eatString ("PUJOPASS.ScheduleCode", true, 10); | ||
| 1140 | auto schedule_type_code = eatString ("PUJOPASS.ScheduleTypeCode", true, 10); | ||
| 1141 | auto line_planning_number = eatString ("PUJOPASS.LinePlanningNumber", true, 10); | ||
| 1142 | auto journey_number = eatNumber ("PUJOPASS.JourneyNumber", true, 6); | ||
| 1143 | auto stop_order = eatNumber ("PUJOPASS.StopOrder", true, 4); | ||
| 1144 | auto journey_pattern_code = eatString ("PUJOPASS.JourneyPatternCode", true, 10); | ||
| 1145 | auto user_stop_code = eatString ("PUJOPASS.UserStopCode", true, 10); | ||
| 1146 | auto target_arrival_time_raw = eatString ("PUJOPASS.TargetArrivalTime", false, 8); | ||
| 1147 | auto target_departure_time_raw = eatString ("PUJOPASS.TargetDepartureTime", false, 8); | ||
| 1148 | auto wheelchair_accessible = eatString ("PUJOPASS.WheelChairAccessible", true, 13); | ||
| 1149 | auto data_owner_is_operator = eatBoolean("PUJOPASS.DataOwnerIsOperator", true ); | ||
| 1150 | auto planned_monitored = eatBoolean("PUJOPASS.PlannedMonitored", true ); | ||
| 1151 | auto product_formula_type = eatNumber ("PUJOPASS.ProductFormulaType", false, 4); | ||
| 1152 | auto show_flexible_trip = eatString ("PUJOPASS.ShowFlexibleTrip", false, 8); | ||
| 1153 | if (!record_errors.empty()) return; | ||
| 1154 | |||
| 1155 | if (*journey_number < 0 || *journey_number > 999'999) | ||
| 1156 | record_errors.push_back("PUJOPASS.JourneyNumber should be within the range [0-999999]"); | ||
| 1157 | if (*journey_number != static_cast<int>(*journey_number)) | ||
| 1158 | record_errors.push_back("PUJOPASS.JourneyNumber should be an integer"); | ||
| 1159 | if (*stop_order != static_cast<short>(*stop_order)) | ||
| 1160 | record_errors.push_back("PUJOPASS.StopOrder should be an integer"); | ||
| 1161 | if (product_formula_type && *product_formula_type != static_cast<short>(*product_formula_type)) | ||
| 1162 | record_errors.push_back("PUJOPASS.ProductFormulaType should be an integer"); | ||
| 1163 | if (wheelchair_accessible != "ACCESSIBLE" && wheelchair_accessible != "NOTACCESSIBLE" && wheelchair_accessible != "UNKNOWN") | ||
| 1164 | record_errors.push_back("PUJOPASS.WheelChairAccessible should be in BISON E3 values [ACCESSIBLE, NOTACCESSIBLE, UNKNOWN]"); | ||
| 1165 | if (!show_flexible_trip.empty() && show_flexible_trip != "TRUE" && | ||
| 1166 | show_flexible_trip != "FALSE" && show_flexible_trip != "REALTIME") | ||
| 1167 | record_errors.push_back("PUJOPASS.ShowFlexibleTrip should be in BISON E21 values [TRUE, FALSE, REALTIME]"); | ||
| 1168 | std::optional<std::chrono::hh_mm_ss<std::chrono::seconds>> target_arrival_time; | ||
| 1169 | if (!target_arrival_time_raw.empty()) { | ||
| 1170 | target_arrival_time = parseHhmmss(target_arrival_time_raw); | ||
| 1171 | if (!target_arrival_time) { | ||
| 1172 | record_errors.push_back("PUJOPASS.TargetArrivalTime has invalid format, should be HH:MM:SS"); | ||
| 1173 | } | ||
| 1174 | } | ||
| 1175 | std::optional<std::chrono::hh_mm_ss<std::chrono::seconds>> target_departure_time; | ||
| 1176 | if (!target_departure_time_raw.empty()) { | ||
| 1177 | target_departure_time = parseHhmmss(target_departure_time_raw); | ||
| 1178 | if (!target_departure_time) { | ||
| 1179 | record_errors.push_back("PUJOPASS.TargetDepartureTime has invalid format, should be HH:MM:SS"); | ||
| 1180 | } | ||
| 1181 | } | ||
| 1182 | if (!record_errors.empty()) return; | ||
| 1183 | |||
| 1184 | records.public_journey_passing_times.emplace_back( | ||
| 1185 | Kv1PublicJourneyPassingTimes::Key( | ||
| 1186 | data_owner_code, | ||
| 1187 | organizational_unit_code, | ||
| 1188 | schedule_code, | ||
| 1189 | schedule_type_code, | ||
| 1190 | line_planning_number, | ||
| 1191 | static_cast<int>(*journey_number), | ||
| 1192 | static_cast<short>(*stop_order)), | ||
| 1193 | journey_pattern_code, | ||
| 1194 | user_stop_code, | ||
| 1195 | target_arrival_time, | ||
| 1196 | target_departure_time, | ||
| 1197 | wheelchair_accessible, | ||
| 1198 | *data_owner_is_operator, | ||
| 1199 | *planned_monitored, | ||
| 1200 | product_formula_type, | ||
| 1201 | show_flexible_trip); | ||
| 1202 | } | ||
| 1203 | |||
| 1204 | void Kv1Parser::parseOperatingDay() { | ||
| 1205 | auto data_owner_code = eatString("OPERDAY.DataOwnerCode", true, 10); | ||
| 1206 | auto organizational_unit_code = eatString("OPERDAY.OrganizationalUnitCode", true, 10); | ||
| 1207 | auto schedule_code = eatString("OPERDAY.ScheduleCode", true, 10); | ||
| 1208 | auto schedule_type_code = eatString("OPERDAY.ScheduleTypeCode", true, 10); | ||
| 1209 | auto valid_date_raw = eatString("OPERDAY.ValidDate", true, 10); | ||
| 1210 | auto description = eatString("OPERDAY.Description", false, 255); | ||
| 1211 | if (!record_errors.empty()) return; | ||
| 1212 | |||
| 1213 | auto valid_date = parseYyyymmdd(valid_date_raw); | ||
| 1214 | if (!valid_date) | ||
| 1215 | record_errors.push_back("OPERDAY.ValidDate has invalid format, should be YYYY-MM-DD"); | ||
| 1216 | if (!record_errors.empty()) return; | ||
| 1217 | |||
| 1218 | records.operating_days.emplace_back( | ||
| 1219 | Kv1OperatingDay::Key( | ||
| 1220 | data_owner_code, | ||
| 1221 | organizational_unit_code, | ||
| 1222 | schedule_code, | ||
| 1223 | schedule_type_code, | ||
| 1224 | *valid_date), | ||
| 1225 | description); | ||
| 1226 | } | ||
| 1227 | |||
| 1228 | const std::unordered_map<std::string_view, Kv1Parser::ParseFunc> Kv1Parser::type_parsers{ | ||
| 1229 | { "ORUN", &Kv1Parser::parseOrganizationalUnit }, | ||
| 1230 | { "ORUNORUN", &Kv1Parser::parseHigherOrganizationalUnit }, | ||
| 1231 | { "USRSTOP", &Kv1Parser::parseUserStopPoint }, | ||
| 1232 | { "USRSTAR", &Kv1Parser::parseUserStopArea }, | ||
| 1233 | { "TILI", &Kv1Parser::parseTimingLink }, | ||
| 1234 | { "LINK", &Kv1Parser::parseLink }, | ||
| 1235 | { "LINE", &Kv1Parser::parseLine }, | ||
| 1236 | { "DEST", &Kv1Parser::parseDestination }, | ||
| 1237 | { "JOPA", &Kv1Parser::parseJourneyPattern }, | ||
| 1238 | { "CONFINREL", &Kv1Parser::parseConcessionFinancerRelation }, | ||
| 1239 | { "CONAREA", &Kv1Parser::parseConcessionArea }, | ||
| 1240 | { "FINANCER", &Kv1Parser::parseFinancer }, | ||
| 1241 | { "JOPATILI", &Kv1Parser::parseJourneyPatternTimingLink }, | ||
| 1242 | { "POINT", &Kv1Parser::parsePoint }, | ||
| 1243 | { "POOL", &Kv1Parser::parsePointOnLink }, | ||
| 1244 | { "ICON", &Kv1Parser::parseIcon }, | ||
| 1245 | { "NOTICE", &Kv1Parser::parseNotice }, | ||
| 1246 | { "NTCASSGNM", &Kv1Parser::parseNoticeAssignment }, | ||
| 1247 | { "TIMDEMGRP", &Kv1Parser::parseTimeDemandGroup }, | ||
| 1248 | { "TIMDEMRNT", &Kv1Parser::parseTimeDemandGroupRunTime }, | ||
| 1249 | { "PEGR", &Kv1Parser::parsePeriodGroup }, | ||
| 1250 | { "SPECDAY", &Kv1Parser::parseSpecificDay }, | ||
| 1251 | { "TIVE", &Kv1Parser::parseTimetableVersion }, | ||
| 1252 | { "PUJO", &Kv1Parser::parsePublicJourney }, | ||
| 1253 | { "PEGRVAL", &Kv1Parser::parsePeriodGroupValidity }, | ||
| 1254 | { "EXCOPDAY", &Kv1Parser::parseExceptionalOperatingDay }, | ||
| 1255 | { "SCHEDVERS", &Kv1Parser::parseScheduleVersion }, | ||
| 1256 | { "PUJOPASS", &Kv1Parser::parsePublicJourneyPassingTimes }, | ||
| 1257 | { "OPERDAY", &Kv1Parser::parseOperatingDay }, | ||
| 1258 | }; | ||
diff --git a/lib/libtmi8/src/kv1_types.cpp b/lib/libtmi8/src/kv1_types.cpp new file mode 100644 index 0000000..49e306e --- /dev/null +++ b/lib/libtmi8/src/kv1_types.cpp | |||
| @@ -0,0 +1,773 @@ | |||
| 1 | // vim:set sw=2 ts=2 sts et: | ||
| 2 | |||
| 3 | #include <boost/container_hash/hash.hpp> | ||
| 4 | |||
| 5 | #include <tmi8/kv1_types.hpp> | ||
| 6 | |||
| 7 | size_t Kv1Records::size() const { | ||
| 8 | return organizational_units.size() | ||
| 9 | + higher_organizational_units.size() | ||
| 10 | + user_stop_points.size() | ||
| 11 | + user_stop_areas.size() | ||
| 12 | + timing_links.size() | ||
| 13 | + links.size() | ||
| 14 | + lines.size() | ||
| 15 | + destinations.size() | ||
| 16 | + journey_patterns.size() | ||
| 17 | + concession_financer_relations.size() | ||
| 18 | + concession_areas.size() | ||
| 19 | + financers.size() | ||
| 20 | + journey_pattern_timing_links.size() | ||
| 21 | + points.size() | ||
| 22 | + point_on_links.size() | ||
| 23 | + icons.size() | ||
| 24 | + notices.size() | ||
| 25 | + notice_assignments.size() | ||
| 26 | + time_demand_groups.size() | ||
| 27 | + time_demand_group_run_times.size() | ||
| 28 | + period_groups.size() | ||
| 29 | + specific_days.size() | ||
| 30 | + timetable_versions.size() | ||
| 31 | + public_journeys.size() | ||
| 32 | + period_group_validities.size() | ||
| 33 | + exceptional_operating_days.size() | ||
| 34 | + schedule_versions.size() | ||
| 35 | + public_journey_passing_times.size() | ||
| 36 | + operating_days.size(); | ||
| 37 | } | ||
| 38 | |||
| 39 | Kv1OrganizationalUnit::Key::Key( | ||
| 40 | std::string data_owner_code, | ||
| 41 | std::string organizational_unit_code) | ||
| 42 | : data_owner_code(std::move(data_owner_code)), | ||
| 43 | organizational_unit_code(std::move(organizational_unit_code)) | ||
| 44 | {} | ||
| 45 | |||
| 46 | Kv1HigherOrganizationalUnit::Key::Key( | ||
| 47 | std::string data_owner_code, | ||
| 48 | std::string organizational_unit_code_parent, | ||
| 49 | std::string organizational_unit_code_child, | ||
| 50 | std::chrono::year_month_day valid_from) | ||
| 51 | : data_owner_code(std::move(data_owner_code)), | ||
| 52 | organizational_unit_code_parent(std::move(organizational_unit_code_parent)), | ||
| 53 | organizational_unit_code_child(std::move(organizational_unit_code_child)), | ||
| 54 | valid_from(valid_from) | ||
| 55 | {} | ||
| 56 | |||
| 57 | Kv1UserStopPoint::Key::Key( | ||
| 58 | std::string data_owner_code, | ||
| 59 | std::string user_stop_code) | ||
| 60 | : data_owner_code(std::move(data_owner_code)), | ||
| 61 | user_stop_code(std::move(user_stop_code)) | ||
| 62 | {} | ||
| 63 | |||
| 64 | Kv1UserStopArea::Key::Key( | ||
| 65 | std::string data_owner_code, | ||
| 66 | std::string user_stop_area_code) | ||
| 67 | : data_owner_code(std::move(data_owner_code)), | ||
| 68 | user_stop_area_code(std::move(user_stop_area_code)) | ||
| 69 | {} | ||
| 70 | |||
| 71 | Kv1TimingLink::Key::Key( | ||
| 72 | std::string data_owner_code, | ||
| 73 | std::string user_stop_code_begin, | ||
| 74 | std::string user_stop_code_end) | ||
| 75 | : data_owner_code(std::move(data_owner_code)), | ||
| 76 | user_stop_code_begin(std::move(user_stop_code_begin)), | ||
| 77 | user_stop_code_end(std::move(user_stop_code_end)) | ||
| 78 | {} | ||
| 79 | |||
| 80 | Kv1Link::Key::Key(std::string data_owner_code, | ||
| 81 | std::string user_stop_code_begin, | ||
| 82 | std::string user_stop_code_end, | ||
| 83 | std::string transport_type) | ||
| 84 | : data_owner_code(std::move(data_owner_code)), | ||
| 85 | user_stop_code_begin(std::move(user_stop_code_begin)), | ||
| 86 | user_stop_code_end(std::move(user_stop_code_end)), | ||
| 87 | transport_type(std::move(transport_type)) | ||
| 88 | {} | ||
| 89 | |||
| 90 | Kv1Line::Key::Key(std::string data_owner_code, | ||
| 91 | std::string line_planning_number) | ||
| 92 | : data_owner_code(std::move(data_owner_code)), | ||
| 93 | line_planning_number(std::move(line_planning_number)) | ||
| 94 | {} | ||
| 95 | |||
| 96 | Kv1Destination::Key::Key(std::string data_owner_code, | ||
| 97 | std::string dest_code) | ||
| 98 | : data_owner_code(std::move(data_owner_code)), | ||
| 99 | dest_code(std::move(dest_code)) | ||
| 100 | {} | ||
| 101 | |||
| 102 | Kv1JourneyPattern::Key::Key(std::string data_owner_code, | ||
| 103 | std::string line_planning_number, | ||
| 104 | std::string journey_pattern_code) | ||
| 105 | : data_owner_code(std::move(data_owner_code)), | ||
| 106 | line_planning_number(std::move(line_planning_number)), | ||
| 107 | journey_pattern_code(std::move(journey_pattern_code)) | ||
| 108 | {} | ||
| 109 | |||
| 110 | Kv1ConcessionFinancerRelation::Key::Key(std::string data_owner_code, | ||
| 111 | std::string con_fin_rel_code) | ||
| 112 | : data_owner_code(std::move(data_owner_code)), | ||
| 113 | con_fin_rel_code(std::move(con_fin_rel_code)) | ||
| 114 | {} | ||
| 115 | |||
| 116 | Kv1ConcessionArea::Key::Key(std::string data_owner_code, | ||
| 117 | std::string concession_area_code) | ||
| 118 | : data_owner_code(std::move(data_owner_code)), | ||
| 119 | concession_area_code(std::move(concession_area_code)) | ||
| 120 | {} | ||
| 121 | |||
| 122 | Kv1Financer::Key::Key(std::string data_owner_code, | ||
| 123 | std::string financer_code) | ||
| 124 | : data_owner_code(std::move(data_owner_code)), | ||
| 125 | financer_code(std::move(financer_code)) | ||
| 126 | {} | ||
| 127 | |||
| 128 | Kv1JourneyPatternTimingLink::Key::Key(std::string data_owner_code, | ||
| 129 | std::string line_planning_number, | ||
| 130 | std::string journey_pattern_code, | ||
| 131 | short timing_link_order) | ||
| 132 | : data_owner_code(std::move(data_owner_code)), | ||
| 133 | line_planning_number(std::move(line_planning_number)), | ||
| 134 | journey_pattern_code(journey_pattern_code), | ||
| 135 | timing_link_order(timing_link_order) | ||
| 136 | {} | ||
| 137 | |||
| 138 | Kv1Point::Key::Key(std::string data_owner_code, | ||
| 139 | std::string point_code) | ||
| 140 | : data_owner_code(std::move(data_owner_code)), | ||
| 141 | point_code(std::move(point_code)) | ||
| 142 | {} | ||
| 143 | |||
| 144 | Kv1PointOnLink::Key::Key(std::string data_owner_code, | ||
| 145 | std::string user_stop_code_begin, | ||
| 146 | std::string user_stop_code_end, | ||
| 147 | std::string point_data_owner_code, | ||
| 148 | std::string point_code, | ||
| 149 | std::string transport_type) | ||
| 150 | : data_owner_code(std::move(data_owner_code)), | ||
| 151 | user_stop_code_begin(std::move(user_stop_code_begin)), | ||
| 152 | user_stop_code_end(std::move(user_stop_code_end)), | ||
| 153 | point_data_owner_code(std::move(point_data_owner_code)), | ||
| 154 | point_code(std::move(point_code)), | ||
| 155 | transport_type(std::move(transport_type)) | ||
| 156 | {} | ||
| 157 | |||
| 158 | Kv1Icon::Key::Key(std::string data_owner_code, | ||
| 159 | short icon_number) | ||
| 160 | : data_owner_code(std::move(data_owner_code)), | ||
| 161 | icon_number(icon_number) | ||
| 162 | {} | ||
| 163 | |||
| 164 | Kv1Notice::Key::Key(std::string data_owner_code, | ||
| 165 | std::string notice_code) | ||
| 166 | : data_owner_code(std::move(data_owner_code)), | ||
| 167 | notice_code(std::move(notice_code)) | ||
| 168 | {} | ||
| 169 | |||
| 170 | Kv1TimeDemandGroup::Key::Key(std::string data_owner_code, | ||
| 171 | std::string line_planning_number, | ||
| 172 | std::string journey_pattern_code, | ||
| 173 | std::string time_demand_group_code) | ||
| 174 | : data_owner_code(std::move(data_owner_code)), | ||
| 175 | line_planning_number(std::move(line_planning_number)), | ||
| 176 | journey_pattern_code(std::move(journey_pattern_code)), | ||
| 177 | time_demand_group_code(std::move(time_demand_group_code)) | ||
| 178 | {} | ||
| 179 | |||
| 180 | Kv1TimeDemandGroupRunTime::Key::Key(std::string data_owner_code, | ||
| 181 | std::string line_planning_number, | ||
| 182 | std::string journey_pattern_code, | ||
| 183 | std::string time_demand_group_code, | ||
| 184 | short timing_link_order) | ||
| 185 | : data_owner_code(std::move(data_owner_code)), | ||
| 186 | line_planning_number(std::move(line_planning_number)), | ||
| 187 | journey_pattern_code(std::move(journey_pattern_code)), | ||
| 188 | time_demand_group_code(std::move(time_demand_group_code)), | ||
| 189 | timing_link_order(std::move(timing_link_order)) | ||
| 190 | {} | ||
| 191 | |||
| 192 | Kv1PeriodGroup::Key::Key(std::string data_owner_code, | ||
| 193 | std::string period_group_code) | ||
| 194 | : data_owner_code(std::move(data_owner_code)), | ||
| 195 | period_group_code(std::move(period_group_code)) | ||
| 196 | {} | ||
| 197 | |||
| 198 | Kv1SpecificDay::Key::Key(std::string data_owner_code, | ||
| 199 | std::string specific_day_code) | ||
| 200 | : data_owner_code(std::move(data_owner_code)), | ||
| 201 | specific_day_code(std::move(specific_day_code)) | ||
| 202 | {} | ||
| 203 | |||
| 204 | Kv1TimetableVersion::Key::Key(std::string data_owner_code, | ||
| 205 | std::string organizational_unit_code, | ||
| 206 | std::string timetable_version_code, | ||
| 207 | std::string period_group_code, | ||
| 208 | std::string specific_day_code) | ||
| 209 | : data_owner_code(std::move(data_owner_code)), | ||
| 210 | organizational_unit_code(std::move(organizational_unit_code)), | ||
| 211 | timetable_version_code(std::move(timetable_version_code)), | ||
| 212 | period_group_code(std::move(period_group_code)), | ||
| 213 | specific_day_code(std::move(specific_day_code)) | ||
| 214 | {} | ||
| 215 | |||
| 216 | Kv1PublicJourney::Key::Key(std::string data_owner_code, | ||
| 217 | std::string timetable_version_code, | ||
| 218 | std::string organizational_unit_code, | ||
| 219 | std::string period_group_code, | ||
| 220 | std::string specific_day_code, | ||
| 221 | std::string day_type, | ||
| 222 | std::string line_planning_number, | ||
| 223 | int journey_number) | ||
| 224 | : data_owner_code(std::move(data_owner_code)), | ||
| 225 | timetable_version_code(std::move(timetable_version_code)), | ||
| 226 | organizational_unit_code(std::move(organizational_unit_code)), | ||
| 227 | period_group_code(std::move(period_group_code)), | ||
| 228 | specific_day_code(std::move(specific_day_code)), | ||
| 229 | day_type(std::move(day_type)), | ||
| 230 | line_planning_number(std::move(line_planning_number)), | ||
| 231 | journey_number(journey_number) | ||
| 232 | {} | ||
| 233 | |||
| 234 | Kv1PeriodGroupValidity::Key::Key(std::string data_owner_code, | ||
| 235 | std::string organizational_unit_code, | ||
| 236 | std::string period_group_code, | ||
| 237 | std::chrono::year_month_day valid_from) | ||
| 238 | : data_owner_code(std::move(data_owner_code)), | ||
| 239 | organizational_unit_code(std::move(organizational_unit_code)), | ||
| 240 | period_group_code(std::move(period_group_code)), | ||
| 241 | valid_from(valid_from) | ||
| 242 | {} | ||
| 243 | |||
| 244 | Kv1ExceptionalOperatingDay::Key::Key(std::string data_owner_code, | ||
| 245 | std::string organizational_unit_code, | ||
| 246 | std::chrono::sys_seconds valid_date) | ||
| 247 | : data_owner_code(std::move(data_owner_code)), | ||
| 248 | organizational_unit_code(std::move(organizational_unit_code)), | ||
| 249 | valid_date(valid_date) | ||
| 250 | {} | ||
| 251 | |||
| 252 | Kv1ScheduleVersion::Key::Key(std::string data_owner_code, | ||
| 253 | std::string organizational_unit_code, | ||
| 254 | std::string schedule_code, | ||
| 255 | std::string schedule_type_code) | ||
| 256 | : data_owner_code(std::move(data_owner_code)), | ||
| 257 | organizational_unit_code(std::move(organizational_unit_code)), | ||
| 258 | schedule_code(std::move(schedule_code)), | ||
| 259 | schedule_type_code(std::move(schedule_type_code)) | ||
| 260 | {} | ||
| 261 | |||
| 262 | Kv1PublicJourneyPassingTimes::Key::Key(std::string data_owner_code, | ||
| 263 | std::string organizational_unit_code, | ||
| 264 | std::string schedule_code, | ||
| 265 | std::string schedule_type_code, | ||
| 266 | std::string line_planning_number, | ||
| 267 | int journey_number, | ||
| 268 | short stop_order) | ||
| 269 | : data_owner_code(std::move(data_owner_code)), | ||
| 270 | organizational_unit_code(std::move(organizational_unit_code)), | ||
| 271 | schedule_code(std::move(schedule_code)), | ||
| 272 | schedule_type_code(std::move(schedule_type_code)), | ||
| 273 | line_planning_number(std::move(line_planning_number)), | ||
| 274 | journey_number(journey_number), | ||
| 275 | stop_order(stop_order) | ||
| 276 | {} | ||
| 277 | |||
| 278 | Kv1OperatingDay::Key::Key(std::string data_owner_code, | ||
| 279 | std::string organizational_unit_code, | ||
| 280 | std::string schedule_code, | ||
| 281 | std::string schedule_type_code, | ||
| 282 | std::chrono::year_month_day valid_date) | ||
| 283 | : data_owner_code(std::move(data_owner_code)), | ||
| 284 | organizational_unit_code(std::move(organizational_unit_code)), | ||
| 285 | schedule_code(std::move(schedule_code)), | ||
| 286 | schedule_type_code(std::move(schedule_type_code)), | ||
| 287 | valid_date(valid_date) | ||
| 288 | {} | ||
| 289 | |||
| 290 | bool operator==(const Kv1OrganizationalUnit::Key &a, const Kv1OrganizationalUnit::Key &b) { | ||
| 291 | return a.data_owner_code == b.data_owner_code | ||
| 292 | && a.organizational_unit_code == b.organizational_unit_code; | ||
| 293 | } | ||
| 294 | |||
| 295 | bool operator==(const Kv1HigherOrganizationalUnit::Key &a, const Kv1HigherOrganizationalUnit::Key &b) { | ||
| 296 | return a.data_owner_code == b.data_owner_code | ||
| 297 | && a.organizational_unit_code_parent == b.organizational_unit_code_parent | ||
| 298 | && a.organizational_unit_code_child == b.organizational_unit_code_child; | ||
| 299 | } | ||
| 300 | |||
| 301 | bool operator==(const Kv1UserStopPoint::Key &a, const Kv1UserStopPoint::Key &b) { | ||
| 302 | return a.data_owner_code == b.data_owner_code | ||
| 303 | && a.user_stop_code == b.user_stop_code; | ||
| 304 | } | ||
| 305 | |||
| 306 | bool operator==(const Kv1UserStopArea::Key &a, const Kv1UserStopArea::Key &b) { | ||
| 307 | return a.data_owner_code == b.data_owner_code | ||
| 308 | && a.user_stop_area_code == b.user_stop_area_code; | ||
| 309 | } | ||
| 310 | |||
| 311 | bool operator==(const Kv1TimingLink::Key &a, const Kv1TimingLink::Key &b) { | ||
| 312 | return a.data_owner_code == b.data_owner_code | ||
| 313 | && a.user_stop_code_begin == b.user_stop_code_begin | ||
| 314 | && a.user_stop_code_end == b.user_stop_code_end; | ||
| 315 | } | ||
| 316 | |||
| 317 | bool operator==(const Kv1Link::Key &a, const Kv1Link::Key &b) { | ||
| 318 | return a.data_owner_code == b.data_owner_code | ||
| 319 | && a.user_stop_code_begin == b.user_stop_code_begin | ||
| 320 | && a.user_stop_code_end == b.user_stop_code_end | ||
| 321 | && a.transport_type == b.transport_type; | ||
| 322 | } | ||
| 323 | |||
| 324 | bool operator==(const Kv1Line::Key &a, const Kv1Line::Key &b) { | ||
| 325 | return a.data_owner_code == b.data_owner_code | ||
| 326 | && a.line_planning_number == b.line_planning_number; | ||
| 327 | } | ||
| 328 | |||
| 329 | bool operator==(const Kv1Destination::Key &a, const Kv1Destination::Key &b) { | ||
| 330 | return a.data_owner_code == b.data_owner_code | ||
| 331 | && a.dest_code == b.dest_code; | ||
| 332 | } | ||
| 333 | |||
| 334 | bool operator==(const Kv1JourneyPattern::Key &a, const Kv1JourneyPattern::Key &b) { | ||
| 335 | return a.data_owner_code == b.data_owner_code | ||
| 336 | && a.line_planning_number == b.line_planning_number | ||
| 337 | && a.journey_pattern_code == b.journey_pattern_code; | ||
| 338 | } | ||
| 339 | |||
| 340 | bool operator==(const Kv1ConcessionFinancerRelation::Key &a, const Kv1ConcessionFinancerRelation::Key &b) { | ||
| 341 | return a.data_owner_code == b.data_owner_code | ||
| 342 | && a.con_fin_rel_code == b.con_fin_rel_code; | ||
| 343 | } | ||
| 344 | |||
| 345 | bool operator==(const Kv1ConcessionArea::Key &a, const Kv1ConcessionArea::Key &b) { | ||
| 346 | return a.data_owner_code == b.data_owner_code | ||
| 347 | && a.concession_area_code == b.concession_area_code; | ||
| 348 | } | ||
| 349 | |||
| 350 | bool operator==(const Kv1Financer::Key &a, const Kv1Financer::Key &b) { | ||
| 351 | return a.data_owner_code == b.data_owner_code | ||
| 352 | && a.financer_code == b.financer_code; | ||
| 353 | } | ||
| 354 | |||
| 355 | bool operator==(const Kv1JourneyPatternTimingLink::Key &a, const Kv1JourneyPatternTimingLink::Key &b) { | ||
| 356 | return a.data_owner_code == b.data_owner_code | ||
| 357 | && a.line_planning_number == b.line_planning_number | ||
| 358 | && a.journey_pattern_code == b.journey_pattern_code | ||
| 359 | && a.timing_link_order == b.timing_link_order; | ||
| 360 | } | ||
| 361 | |||
| 362 | bool operator==(const Kv1Point::Key &a, const Kv1Point::Key &b) { | ||
| 363 | return a.data_owner_code == b.data_owner_code | ||
| 364 | && a.point_code == b.point_code; | ||
| 365 | } | ||
| 366 | |||
| 367 | bool operator==(const Kv1PointOnLink::Key &a, const Kv1PointOnLink::Key &b) { | ||
| 368 | return a.data_owner_code == b.data_owner_code | ||
| 369 | && a.user_stop_code_begin == b.user_stop_code_begin | ||
| 370 | && a.user_stop_code_end == b.user_stop_code_end | ||
| 371 | && a.point_data_owner_code == b.point_data_owner_code | ||
| 372 | && a.point_code == b.point_code | ||
| 373 | && a.transport_type == b.transport_type; | ||
| 374 | } | ||
| 375 | |||
| 376 | bool operator==(const Kv1Icon::Key &a, const Kv1Icon::Key &b) { | ||
| 377 | return a.data_owner_code == b.data_owner_code | ||
| 378 | && a.icon_number == b.icon_number; | ||
| 379 | } | ||
| 380 | |||
| 381 | bool operator==(const Kv1Notice::Key &a, const Kv1Notice::Key &b) { | ||
| 382 | return a.data_owner_code == b.data_owner_code | ||
| 383 | && a.notice_code == b.notice_code; | ||
| 384 | } | ||
| 385 | |||
| 386 | bool operator==(const Kv1TimeDemandGroup::Key &a, const Kv1TimeDemandGroup::Key &b) { | ||
| 387 | return a.data_owner_code == b.data_owner_code | ||
| 388 | && a.line_planning_number == b.line_planning_number | ||
| 389 | && a.journey_pattern_code == b.journey_pattern_code | ||
| 390 | && a.time_demand_group_code == b.time_demand_group_code; | ||
| 391 | } | ||
| 392 | |||
| 393 | bool operator==(const Kv1TimeDemandGroupRunTime::Key &a, const Kv1TimeDemandGroupRunTime::Key &b) { | ||
| 394 | return a.data_owner_code == b.data_owner_code | ||
| 395 | && a.line_planning_number == b.line_planning_number | ||
| 396 | && a.journey_pattern_code == b.journey_pattern_code | ||
| 397 | && a.time_demand_group_code == b.time_demand_group_code | ||
| 398 | && a.timing_link_order == b.timing_link_order; | ||
| 399 | } | ||
| 400 | |||
| 401 | bool operator==(const Kv1PeriodGroup::Key &a, const Kv1PeriodGroup::Key &b) { | ||
| 402 | return a.data_owner_code == b.data_owner_code | ||
| 403 | && a.period_group_code == b.period_group_code; | ||
| 404 | } | ||
| 405 | |||
| 406 | bool operator==(const Kv1SpecificDay::Key &a, const Kv1SpecificDay::Key &b) { | ||
| 407 | return a.data_owner_code == b.data_owner_code | ||
| 408 | && a.specific_day_code == b.specific_day_code; | ||
| 409 | } | ||
| 410 | |||
| 411 | bool operator==(const Kv1TimetableVersion::Key &a, const Kv1TimetableVersion::Key &b) { | ||
| 412 | return a.data_owner_code == b.data_owner_code | ||
| 413 | && a.organizational_unit_code == b.organizational_unit_code | ||
| 414 | && a.timetable_version_code == b.timetable_version_code | ||
| 415 | && a.period_group_code == b.period_group_code | ||
| 416 | && a.specific_day_code == b.specific_day_code; | ||
| 417 | } | ||
| 418 | |||
| 419 | bool operator==(const Kv1PublicJourney::Key &a, const Kv1PublicJourney::Key &b) { | ||
| 420 | return a.data_owner_code == b.data_owner_code | ||
| 421 | && a.timetable_version_code == b.timetable_version_code | ||
| 422 | && a.organizational_unit_code == b.organizational_unit_code | ||
| 423 | && a.period_group_code == b.period_group_code | ||
| 424 | && a.specific_day_code == b.specific_day_code | ||
| 425 | && a.day_type == b.day_type | ||
| 426 | && a.line_planning_number == b.line_planning_number | ||
| 427 | && a.journey_number == b.journey_number; | ||
| 428 | } | ||
| 429 | |||
| 430 | bool operator==(const Kv1PeriodGroupValidity::Key &a, const Kv1PeriodGroupValidity::Key &b) { | ||
| 431 | return a.data_owner_code == b.data_owner_code | ||
| 432 | && a.organizational_unit_code == b.organizational_unit_code | ||
| 433 | && a.period_group_code == b.period_group_code | ||
| 434 | && a.valid_from == b.valid_from; | ||
| 435 | } | ||
| 436 | |||
| 437 | bool operator==(const Kv1ExceptionalOperatingDay::Key &a, const Kv1ExceptionalOperatingDay::Key &b) { | ||
| 438 | return a.data_owner_code == b.data_owner_code | ||
| 439 | && a.organizational_unit_code == b.organizational_unit_code | ||
| 440 | && a.valid_date == b.valid_date; | ||
| 441 | } | ||
| 442 | |||
| 443 | bool operator==(const Kv1ScheduleVersion::Key &a, const Kv1ScheduleVersion::Key &b) { | ||
| 444 | return a.data_owner_code == b.data_owner_code | ||
| 445 | && a.organizational_unit_code == b.organizational_unit_code | ||
| 446 | && a.schedule_code == b.schedule_code | ||
| 447 | && a.schedule_type_code == b.schedule_type_code; | ||
| 448 | } | ||
| 449 | |||
| 450 | bool operator==(const Kv1PublicJourneyPassingTimes::Key &a, const Kv1PublicJourneyPassingTimes::Key &b) { | ||
| 451 | return a.data_owner_code == b.data_owner_code | ||
| 452 | && a.organizational_unit_code == b.organizational_unit_code | ||
| 453 | && a.schedule_code == b.schedule_code | ||
| 454 | && a.schedule_type_code == b.schedule_type_code | ||
| 455 | && a.line_planning_number == b.line_planning_number | ||
| 456 | && a.journey_number == b.journey_number | ||
| 457 | && a.stop_order == b.stop_order; | ||
| 458 | } | ||
| 459 | |||
| 460 | bool operator==(const Kv1OperatingDay::Key &a, const Kv1OperatingDay::Key &b) { | ||
| 461 | return a.data_owner_code == b.data_owner_code | ||
| 462 | && a.organizational_unit_code == b.organizational_unit_code | ||
| 463 | && a.schedule_code == b.schedule_code | ||
| 464 | && a.schedule_type_code == b.schedule_type_code | ||
| 465 | && a.valid_date == b.valid_date; | ||
| 466 | } | ||
| 467 | |||
| 468 | namespace std::chrono { | ||
| 469 | static size_t hash_value(const year_month_day &ymd) { | ||
| 470 | size_t seed = 0; | ||
| 471 | |||
| 472 | boost::hash_combine(seed, int(ymd.year())); | ||
| 473 | boost::hash_combine(seed, unsigned(ymd.month())); | ||
| 474 | boost::hash_combine(seed, unsigned(ymd.day())); | ||
| 475 | |||
| 476 | return seed; | ||
| 477 | } | ||
| 478 | |||
| 479 | static size_t hash_value(const sys_seconds &s) { | ||
| 480 | return boost::hash<seconds::rep>()(s.time_since_epoch().count()); | ||
| 481 | } | ||
| 482 | } | ||
| 483 | |||
| 484 | size_t hash_value(const Kv1OrganizationalUnit::Key &k) { | ||
| 485 | size_t seed = 0; | ||
| 486 | |||
| 487 | boost::hash_combine(seed, k.data_owner_code); | ||
| 488 | boost::hash_combine(seed, k.organizational_unit_code); | ||
| 489 | |||
| 490 | return seed; | ||
| 491 | } | ||
| 492 | |||
| 493 | size_t hash_value(const Kv1HigherOrganizationalUnit::Key &k) { | ||
| 494 | size_t seed = 0; | ||
| 495 | |||
| 496 | boost::hash_combine(seed, k.data_owner_code); | ||
| 497 | boost::hash_combine(seed, k.organizational_unit_code_parent); | ||
| 498 | boost::hash_combine(seed, k.organizational_unit_code_child); | ||
| 499 | boost::hash_combine(seed, k.valid_from); | ||
| 500 | |||
| 501 | return seed; | ||
| 502 | } | ||
| 503 | |||
| 504 | size_t hash_value(const Kv1UserStopPoint::Key &k) { | ||
| 505 | size_t seed = 0; | ||
| 506 | |||
| 507 | boost::hash_combine(seed, k.data_owner_code); | ||
| 508 | boost::hash_combine(seed, k.user_stop_code); | ||
| 509 | |||
| 510 | return seed; | ||
| 511 | } | ||
| 512 | |||
| 513 | size_t hash_value(const Kv1UserStopArea::Key &k) { | ||
| 514 | size_t seed = 0; | ||
| 515 | |||
| 516 | boost::hash_combine(seed, k.data_owner_code); | ||
| 517 | boost::hash_combine(seed, k.user_stop_area_code); | ||
| 518 | |||
| 519 | return seed; | ||
| 520 | } | ||
| 521 | |||
| 522 | size_t hash_value(const Kv1TimingLink::Key &k) { | ||
| 523 | size_t seed = 0; | ||
| 524 | |||
| 525 | boost::hash_combine(seed, k.data_owner_code); | ||
| 526 | boost::hash_combine(seed, k.user_stop_code_begin); | ||
| 527 | boost::hash_combine(seed, k.user_stop_code_end); | ||
| 528 | |||
| 529 | return seed; | ||
| 530 | } | ||
| 531 | |||
| 532 | size_t hash_value(const Kv1Link::Key &k) { | ||
| 533 | size_t seed = 0; | ||
| 534 | |||
| 535 | boost::hash_combine(seed, k.data_owner_code); | ||
| 536 | boost::hash_combine(seed, k.user_stop_code_begin); | ||
| 537 | boost::hash_combine(seed, k.user_stop_code_end); | ||
| 538 | boost::hash_combine(seed, k.transport_type); | ||
| 539 | |||
| 540 | return seed; | ||
| 541 | } | ||
| 542 | |||
| 543 | size_t hash_value(const Kv1Line::Key &k) { | ||
| 544 | size_t seed = 0; | ||
| 545 | |||
| 546 | boost::hash_combine(seed, k.data_owner_code); | ||
| 547 | boost::hash_combine(seed, k.line_planning_number); | ||
| 548 | |||
| 549 | return seed; | ||
| 550 | } | ||
| 551 | |||
| 552 | size_t hash_value(const Kv1Destination::Key &k) { | ||
| 553 | size_t seed = 0; | ||
| 554 | |||
| 555 | boost::hash_combine(seed, k.data_owner_code); | ||
| 556 | boost::hash_combine(seed, k.dest_code); | ||
| 557 | |||
| 558 | return seed; | ||
| 559 | } | ||
| 560 | |||
| 561 | size_t hash_value(const Kv1JourneyPattern::Key &k) { | ||
| 562 | size_t seed = 0; | ||
| 563 | |||
| 564 | boost::hash_combine(seed, k.data_owner_code); | ||
| 565 | boost::hash_combine(seed, k.line_planning_number); | ||
| 566 | boost::hash_combine(seed, k.journey_pattern_code); | ||
| 567 | |||
| 568 | return seed; | ||
| 569 | } | ||
| 570 | |||
| 571 | size_t hash_value(const Kv1ConcessionFinancerRelation::Key &k) { | ||
| 572 | size_t seed = 0; | ||
| 573 | |||
| 574 | boost::hash_combine(seed, k.data_owner_code); | ||
| 575 | boost::hash_combine(seed, k.con_fin_rel_code); | ||
| 576 | |||
| 577 | return seed; | ||
| 578 | } | ||
| 579 | |||
| 580 | size_t hash_value(const Kv1ConcessionArea::Key &k) { | ||
| 581 | size_t seed = 0; | ||
| 582 | |||
| 583 | boost::hash_combine(seed, k.data_owner_code); | ||
| 584 | boost::hash_combine(seed, k.concession_area_code); | ||
| 585 | |||
| 586 | return seed; | ||
| 587 | } | ||
| 588 | |||
| 589 | size_t hash_value(const Kv1Financer::Key &k) { | ||
| 590 | size_t seed = 0; | ||
| 591 | |||
| 592 | boost::hash_combine(seed, k.data_owner_code); | ||
| 593 | boost::hash_combine(seed, k.financer_code); | ||
| 594 | |||
| 595 | return seed; | ||
| 596 | } | ||
| 597 | |||
| 598 | size_t hash_value(const Kv1JourneyPatternTimingLink::Key &k) { | ||
| 599 | size_t seed = 0; | ||
| 600 | |||
| 601 | boost::hash_combine(seed, k.data_owner_code); | ||
| 602 | boost::hash_combine(seed, k.line_planning_number); | ||
| 603 | boost::hash_combine(seed, k.journey_pattern_code); | ||
| 604 | boost::hash_combine(seed, k.timing_link_order); | ||
| 605 | |||
| 606 | return seed; | ||
| 607 | } | ||
| 608 | |||
| 609 | size_t hash_value(const Kv1Point::Key &k) { | ||
| 610 | size_t seed = 0; | ||
| 611 | |||
| 612 | boost::hash_combine(seed, k.data_owner_code); | ||
| 613 | boost::hash_combine(seed, k.point_code); | ||
| 614 | |||
| 615 | return seed; | ||
| 616 | } | ||
| 617 | |||
| 618 | size_t hash_value(const Kv1PointOnLink::Key &k) { | ||
| 619 | size_t seed = 0; | ||
| 620 | |||
| 621 | boost::hash_combine(seed, k.data_owner_code); | ||
| 622 | boost::hash_combine(seed, k.user_stop_code_begin); | ||
| 623 | boost::hash_combine(seed, k.user_stop_code_end); | ||
| 624 | boost::hash_combine(seed, k.point_data_owner_code); | ||
| 625 | boost::hash_combine(seed, k.point_code); | ||
| 626 | boost::hash_combine(seed, k.transport_type); | ||
| 627 | |||
| 628 | return seed; | ||
| 629 | } | ||
| 630 | |||
| 631 | size_t hash_value(const Kv1Icon::Key &k) { | ||
| 632 | size_t seed = 0; | ||
| 633 | |||
| 634 | boost::hash_combine(seed, k.data_owner_code); | ||
| 635 | boost::hash_combine(seed, k.icon_number); | ||
| 636 | |||
| 637 | return seed; | ||
| 638 | } | ||
| 639 | |||
| 640 | size_t hash_value(const Kv1Notice::Key &k) { | ||
| 641 | size_t seed = 0; | ||
| 642 | |||
| 643 | boost::hash_combine(seed, k.data_owner_code); | ||
| 644 | boost::hash_combine(seed, k.notice_code); | ||
| 645 | |||
| 646 | return seed; | ||
| 647 | } | ||
| 648 | |||
| 649 | size_t hash_value(const Kv1TimeDemandGroup::Key &k) { | ||
| 650 | size_t seed = 0; | ||
| 651 | |||
| 652 | boost::hash_combine(seed, k.data_owner_code); | ||
| 653 | boost::hash_combine(seed, k.line_planning_number); | ||
| 654 | boost::hash_combine(seed, k.journey_pattern_code); | ||
| 655 | boost::hash_combine(seed, k.time_demand_group_code); | ||
| 656 | |||
| 657 | return seed; | ||
| 658 | } | ||
| 659 | |||
| 660 | size_t hash_value(const Kv1TimeDemandGroupRunTime::Key &k) { | ||
| 661 | size_t seed = 0; | ||
| 662 | |||
| 663 | boost::hash_combine(seed, k.data_owner_code); | ||
| 664 | boost::hash_combine(seed, k.line_planning_number); | ||
| 665 | boost::hash_combine(seed, k.journey_pattern_code); | ||
| 666 | boost::hash_combine(seed, k.time_demand_group_code); | ||
| 667 | boost::hash_combine(seed, k.timing_link_order); | ||
| 668 | |||
| 669 | return seed; | ||
| 670 | } | ||
| 671 | |||
| 672 | size_t hash_value(const Kv1PeriodGroup::Key &k) { | ||
| 673 | size_t seed = 0; | ||
| 674 | |||
| 675 | boost::hash_combine(seed, k.data_owner_code); | ||
| 676 | boost::hash_combine(seed, k.period_group_code); | ||
| 677 | |||
| 678 | return seed; | ||
| 679 | } | ||
| 680 | |||
| 681 | size_t hash_value(const Kv1SpecificDay::Key &k) { | ||
| 682 | size_t seed = 0; | ||
| 683 | |||
| 684 | boost::hash_combine(seed, k.data_owner_code); | ||
| 685 | boost::hash_combine(seed, k.specific_day_code); | ||
| 686 | |||
| 687 | return seed; | ||
| 688 | } | ||
| 689 | |||
| 690 | size_t hash_value(const Kv1TimetableVersion::Key &k) { | ||
| 691 | size_t seed = 0; | ||
| 692 | |||
| 693 | boost::hash_combine(seed, k.data_owner_code); | ||
| 694 | boost::hash_combine(seed, k.organizational_unit_code); | ||
| 695 | boost::hash_combine(seed, k.timetable_version_code); | ||
| 696 | boost::hash_combine(seed, k.period_group_code); | ||
| 697 | boost::hash_combine(seed, k.specific_day_code); | ||
| 698 | |||
| 699 | return seed; | ||
| 700 | } | ||
| 701 | |||
| 702 | size_t hash_value(const Kv1PublicJourney::Key &k) { | ||
| 703 | size_t seed = 0; | ||
| 704 | |||
| 705 | boost::hash_combine(seed, k.data_owner_code); | ||
| 706 | boost::hash_combine(seed, k.timetable_version_code); | ||
| 707 | boost::hash_combine(seed, k.organizational_unit_code); | ||
| 708 | boost::hash_combine(seed, k.period_group_code); | ||
| 709 | boost::hash_combine(seed, k.specific_day_code); | ||
| 710 | boost::hash_combine(seed, k.day_type); | ||
| 711 | boost::hash_combine(seed, k.line_planning_number); | ||
| 712 | boost::hash_combine(seed, k.journey_number); | ||
| 713 | |||
| 714 | return seed; | ||
| 715 | } | ||
| 716 | |||
| 717 | size_t hash_value(const Kv1PeriodGroupValidity::Key &k) { | ||
| 718 | size_t seed = 0; | ||
| 719 | |||
| 720 | boost::hash_combine(seed, k.data_owner_code); | ||
| 721 | boost::hash_combine(seed, k.organizational_unit_code); | ||
| 722 | boost::hash_combine(seed, k.period_group_code); | ||
| 723 | boost::hash_combine(seed, k.valid_from); | ||
| 724 | |||
| 725 | return seed; | ||
| 726 | } | ||
| 727 | |||
| 728 | size_t hash_value(const Kv1ExceptionalOperatingDay::Key &k) { | ||
| 729 | size_t seed = 0; | ||
| 730 | |||
| 731 | boost::hash_combine(seed, k.data_owner_code); | ||
| 732 | boost::hash_combine(seed, k.organizational_unit_code); | ||
| 733 | boost::hash_combine(seed, k.valid_date); | ||
| 734 | |||
| 735 | return seed; | ||
| 736 | } | ||
| 737 | |||
| 738 | size_t hash_value(const Kv1ScheduleVersion::Key &k) { | ||
| 739 | size_t seed = 0; | ||
| 740 | |||
| 741 | boost::hash_combine(seed, k.data_owner_code); | ||
| 742 | boost::hash_combine(seed, k.organizational_unit_code); | ||
| 743 | boost::hash_combine(seed, k.schedule_code); | ||
| 744 | boost::hash_combine(seed, k.schedule_type_code); | ||
| 745 | |||
| 746 | return seed; | ||
| 747 | } | ||
| 748 | |||
| 749 | size_t hash_value(const Kv1PublicJourneyPassingTimes::Key &k) { | ||
| 750 | size_t seed = 0; | ||
| 751 | |||
| 752 | boost::hash_combine(seed, k.data_owner_code); | ||
| 753 | boost::hash_combine(seed, k.organizational_unit_code); | ||
| 754 | boost::hash_combine(seed, k.schedule_code); | ||
| 755 | boost::hash_combine(seed, k.schedule_type_code); | ||
| 756 | boost::hash_combine(seed, k.line_planning_number); | ||
| 757 | boost::hash_combine(seed, k.journey_number); | ||
| 758 | boost::hash_combine(seed, k.stop_order); | ||
| 759 | |||
| 760 | return seed; | ||
| 761 | } | ||
| 762 | |||
| 763 | size_t hash_value(const Kv1OperatingDay::Key &k) { | ||
| 764 | size_t seed = 0; | ||
| 765 | |||
| 766 | boost::hash_combine(seed, k.data_owner_code); | ||
| 767 | boost::hash_combine(seed, k.organizational_unit_code); | ||
| 768 | boost::hash_combine(seed, k.schedule_code); | ||
| 769 | boost::hash_combine(seed, k.schedule_type_code); | ||
| 770 | boost::hash_combine(seed, k.valid_date); | ||
| 771 | |||
| 772 | return seed; | ||
| 773 | } | ||
diff --git a/lib/libtmi8/src/kv6_parquet.cpp b/lib/libtmi8/src/kv6_parquet.cpp new file mode 100644 index 0000000..ca70b7f --- /dev/null +++ b/lib/libtmi8/src/kv6_parquet.cpp | |||
| @@ -0,0 +1,102 @@ | |||
| 1 | // vim:set sw=2 ts=2 sts et: | ||
| 2 | |||
| 3 | #include <tmi8/kv6_parquet.hpp> | ||
| 4 | |||
| 5 | ParquetBuilder::ParquetBuilder() { | ||
| 6 | std::shared_ptr<arrow::Field> field_type, field_data_owner_code, field_line_planning_number, field_operating_day, | ||
| 7 | field_journey_number, field_reinforcement_number, field_timestamp, field_source, | ||
| 8 | field_punctuality, field_user_stop_code, field_passage_sequence_number, | ||
| 9 | field_vehicle_number, field_block_code, field_wheelchair_accessible, | ||
| 10 | field_number_of_coaches, field_rd_y, field_rd_x, field_distance_since_last_user_stop; | ||
| 11 | field_type = arrow::field("type", arrow::utf8()); | ||
| 12 | field_data_owner_code = arrow::field("data_owner_code", arrow::utf8()); | ||
| 13 | field_line_planning_number = arrow::field("line_planning_number", arrow::utf8()); | ||
| 14 | field_operating_day = arrow::field("operating_day", arrow::date32()); | ||
| 15 | field_journey_number = arrow::field("journey_number", arrow::uint32()); | ||
| 16 | field_reinforcement_number = arrow::field("reinforcement_number", arrow::uint8()); | ||
| 17 | field_timestamp = arrow::field("timestamp", arrow::timestamp(arrow::TimeUnit::SECOND)); | ||
| 18 | field_source = arrow::field("source", arrow::utf8()); | ||
| 19 | field_punctuality = arrow::field("punctuality", arrow::int16()); | ||
| 20 | field_user_stop_code = arrow::field("user_stop_code", arrow::utf8()); | ||
| 21 | field_passage_sequence_number = arrow::field("passage_sequence_number", arrow::uint16()); | ||
| 22 | field_vehicle_number = arrow::field("vehicle_number", arrow::uint32()); | ||
| 23 | field_block_code = arrow::field("block_code", arrow::uint32()); | ||
| 24 | field_wheelchair_accessible = arrow::field("wheelchair_accessible", arrow::utf8()); | ||
| 25 | field_number_of_coaches = arrow::field("number_of_coaches", arrow::uint8()); | ||
| 26 | field_rd_y = arrow::field("rd_y", arrow::int32()); | ||
| 27 | field_rd_x = arrow::field("rd_x", arrow::int32()); | ||
| 28 | field_distance_since_last_user_stop = arrow::field("distance_since_last_user_stop", arrow::uint32()); | ||
| 29 | |||
| 30 | schema = arrow::schema({ field_type, field_data_owner_code, field_line_planning_number, | ||
| 31 | field_operating_day, field_journey_number, | ||
| 32 | field_reinforcement_number, field_timestamp, field_source, | ||
| 33 | field_punctuality, field_user_stop_code, | ||
| 34 | field_passage_sequence_number, field_vehicle_number, | ||
| 35 | field_block_code, field_wheelchair_accessible, | ||
| 36 | field_number_of_coaches, field_rd_y, field_rd_x, | ||
| 37 | field_distance_since_last_user_stop }); | ||
| 38 | } | ||
| 39 | |||
| 40 | arrow::Result<std::shared_ptr<arrow::Table>> ParquetBuilder::getTable() { | ||
| 41 | ARROW_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Array> types, types.Finish()); | ||
| 42 | ARROW_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Array> data_owner_codes, data_owner_codes.Finish()); | ||
| 43 | ARROW_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Array> line_planning_numbers, line_planning_numbers.Finish()); | ||
| 44 | ARROW_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Array> operating_days, operating_days.Finish()); | ||
| 45 | ARROW_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Array> journey_numbers, journey_numbers.Finish()); | ||
| 46 | ARROW_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Array> reinforcement_numbers, reinforcement_numbers.Finish()); | ||
| 47 | ARROW_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Array> timestamps, timestamps.Finish()); | ||
| 48 | ARROW_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Array> sources, sources.Finish()); | ||
| 49 | ARROW_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Array> punctualities, punctualities.Finish()); | ||
| 50 | ARROW_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Array> user_stop_codes, user_stop_codes.Finish()); | ||
| 51 | ARROW_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Array> passage_sequence_numbers, passage_sequence_numbers.Finish()); | ||
| 52 | ARROW_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Array> vehicle_numbers, vehicle_numbers.Finish()); | ||
| 53 | ARROW_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Array> block_codes, block_codes.Finish()); | ||
| 54 | ARROW_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Array> wheelchair_accessibles, wheelchair_accessibles.Finish()); | ||
| 55 | ARROW_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Array> number_of_coaches, number_of_coaches.Finish()); | ||
| 56 | ARROW_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Array> rd_ys, rd_ys.Finish()); | ||
| 57 | ARROW_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Array> rd_xs, rd_xs.Finish()); | ||
| 58 | ARROW_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Array> distance_since_last_user_stops, distance_since_last_user_stops.Finish()); | ||
| 59 | |||
| 60 | std::vector<std::shared_ptr<arrow::Array>> columns = { types, data_owner_codes, line_planning_numbers, operating_days, | ||
| 61 | journey_numbers, reinforcement_numbers, timestamps, sources, | ||
| 62 | punctualities, user_stop_codes, passage_sequence_numbers, | ||
| 63 | vehicle_numbers, block_codes, wheelchair_accessibles, | ||
| 64 | number_of_coaches, rd_ys, rd_xs, | ||
| 65 | distance_since_last_user_stops }; | ||
| 66 | return arrow::Result(arrow::Table::Make(schema, columns)); | ||
| 67 | } | ||
| 68 | |||
| 69 | arrow::Status writeArrowRecordsAsParquetFile(arrow::RecordBatchReader &rbr, std::filesystem::path filename) { | ||
| 70 | std::shared_ptr<parquet::WriterProperties> props = parquet::WriterProperties::Builder() | ||
| 71 | .compression(arrow::Compression::ZSTD) | ||
| 72 | ->created_by("oeuf-libtmi8") | ||
| 73 | ->version(parquet::ParquetVersion::PARQUET_2_6) | ||
| 74 | ->data_page_version(parquet::ParquetDataPageVersion::V2) | ||
| 75 | ->max_row_group_length(MAX_PARQUET_CHUNK) | ||
| 76 | ->build(); | ||
| 77 | |||
| 78 | std::shared_ptr<parquet::ArrowWriterProperties> arrow_props = parquet::ArrowWriterProperties::Builder() | ||
| 79 | .store_schema()->build(); | ||
| 80 | |||
| 81 | std::shared_ptr<arrow::io::FileOutputStream> out_file; | ||
| 82 | std::string filename_str = filename; | ||
| 83 | ARROW_ASSIGN_OR_RAISE(out_file, arrow::io::FileOutputStream::Open(filename_str + ".part")); | ||
| 84 | |||
| 85 | ARROW_ASSIGN_OR_RAISE(auto writer, | ||
| 86 | parquet::arrow::FileWriter::Open(*rbr.schema(), arrow::default_memory_pool(), out_file, props, arrow_props)); | ||
| 87 | for (const auto &batchr : rbr) { | ||
| 88 | ARROW_ASSIGN_OR_RAISE(auto batch, batchr); | ||
| 89 | ARROW_RETURN_NOT_OK(writer->WriteRecordBatch(*batch)); | ||
| 90 | } | ||
| 91 | ARROW_RETURN_NOT_OK(writer->Close()); | ||
| 92 | ARROW_RETURN_NOT_OK(out_file->Close()); | ||
| 93 | |||
| 94 | std::filesystem::rename(filename_str + ".part", filename); | ||
| 95 | |||
| 96 | return arrow::Status::OK(); | ||
| 97 | } | ||
| 98 | |||
| 99 | arrow::Status writeArrowTableAsParquetFile(const arrow::Table &table, std::filesystem::path filename) { | ||
| 100 | auto tbr = arrow::TableBatchReader(table); | ||
| 101 | return writeArrowRecordsAsParquetFile(tbr, filename); | ||
| 102 | } | ||