diff --git a/.travis.yml b/.travis.yml index 6758edc..a506548 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,32 @@ language: c +sudo: required +dist: trusty + compiler: - gcc - clang +cache: + directories: + - $HOME/.cache/pip + +addons: + apt: + packages: + - libprotobuf-c0-dev + - protobuf-c-compiler + - check + - pkg-config + before_install: - - sudo apt-get update >/dev/null - - sudo apt-get -q install check libprotobuf-c0-dev protobuf-c-compiler + - export TZ=Europe/Amsterdam + - sudo apt-get update -qq + - sudo apt-get install -qq libprotobuf-c0-dev protobuf-c-compiler check pkg-config before_script: + - pip install pytz python-dateutil --user `whoami` + - export PATH=$HOME/.local/bin:$PATH - mkdir build - cd build - cmake .. @@ -16,6 +34,7 @@ before_script: script: - make - ctest + - python -m unittest discover ../rrtimetable/rrtimetable "*_tests.py" env: global: diff --git a/CMakeLists.txt b/CMakeLists.txt index fd8a4fa..8fc5a17 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,16 +1,17 @@ cmake_minimum_required(VERSION 2.8.4) -project(ansi) +project(ansi C) set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake_modules/") -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wextra -Wall -ansi -pedantic -ggdb3 -DRRRR_VALGRIND -DRRRR_STRICT") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wextra -Wall -std=c89 -pedantic -g -ggdb3 -DRRRR_VALGRIND -DRRRR_STRICT -march=native") if("${CMAKE_C_COMPILER_ID}" MATCHES "Clang") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Weverything") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Weverything -Wno-reserved-id-macro -Wno-documentation-unknown-command -Wno-c99-extensions -Wno-padded -Wno-disabled-macro-expansion -Wno-variadic-macros -Wno-gnu-zero-variadic-macro-arguments") endif("${CMAKE_C_COMPILER_ID}" MATCHES "Clang") if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Weverything -Werror") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Weverything") endif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") +set_source_files_properties(gtfs-realtime.pb-c.c PROPERTIES COMPILE_FLAGS -Wno-missing-variable-declarations) SET(PROTO_FILES ${CMAKE_CURRENT_SOURCE_DIR}/gtfs-realtime.proto @@ -33,9 +34,10 @@ add_custom_command(OUTPUT ${PROTO_HDRS} ${PROTO_SRCS} ENABLE_TESTING() set(SOURCE_FILES + api.c + api.h bitset.c bitset.h - cli.c config.h geometry.c geometry.h @@ -43,8 +45,24 @@ set(SOURCE_FILES gtfs-realtime.pb-c.h hashgrid.c hashgrid.h + index_journey_pattern_points.c + index_journey_pattern_points.h + index_journey_patterns.c + index_journey_patterns.h + index_vehicle_journeys.c + index_vehicle_journeys.h + json.c + json.h linkedlist.c linkedlist.h + plan.c + plan.h + plan_render_otp.c + plan_render_otp.h + plan_render_text.c + plan_render_text.h + polyline.c + polyline.h radixtree.c radixtree.h router.c @@ -56,23 +74,36 @@ set(SOURCE_FILES router_result.c router_result.h rrrr_types.h + set.c + set.h + string_pool.c + string_pool.h tdata.c tdata.h - tdata_io_v3.h - tdata_io_v3_dynamic.c - tdata_io_v3_mmap.c + tdata_jit.h + tdata_jit.c + tdata_io_v4.h + tdata_io_v4_dynamic.c + tdata_io_v4_mmap.c tdata_realtime_alerts.c tdata_realtime_alerts.h tdata_realtime_expanded.c tdata_realtime_expanded.h tdata_validation.c tdata_validation.h + street_network.h + street_network.c + hashgrid_street_network.c util.c util.h) +include_directories(/usr/local/include) + +link_directories(/usr/local/lib) link_libraries(m) link_libraries(protobuf-c) -add_executable(cli ${SOURCE_FILES}) +add_executable(cli ${SOURCE_FILES} cli.c) +add_library(rrrr ${SOURCE_FILES}) add_subdirectory(tests) diff --git a/LICENSE b/LICENSE index 7382ba9..879f696 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2014, Bliksem Labs. +Copyright (c) 2014–2015, Bliksem Labs. All rights reserved. Redistribution and use in source and binary forms, with or without modification, diff --git a/Makefile b/Makefile index 3165d1d..5b8adb0 100644 --- a/Makefile +++ b/Makefile @@ -1,25 +1,28 @@ CC=clang debug: - $(CC) -DRRRR_STRICT -DRRRR_TDATA_IO_DYNAMIC -DRRRR_FAKE_REALTIME -DRRRR_VALGRIND -DRRRR_BITSET_64 -Wextra -Wall -ansi -pedantic -DRRRR_DEBUG -DRRRR_INFO -DRRRR_TDATA -ggdb -O0 -lm -lprotobuf-c -o cli cli.c router.c tdata.c tdata_validation.c bitset.c router_request.c router_result.c util.c tdata_realtime_expanded.c tdata_realtime_alerts.c tdata_io_v3_dynamic.c radixtree.c gtfs-realtime.pb-c.c geometry.c router_dump.c hashgrid.c - $(CC) -DRRRR_STRICT -DRRRR_TDATA_IO_MMAP -DRRRR_FAKE_REALTIME -DRRRR_VALGRIND -DRRRR_BITSET_64 -Wextra -Wall -ansi -pedantic -DRRRR_DEBUG -DRRRR_INFO -DRRRR_TDATA -ggdb -O0 -lm -lprotobuf-c -o cli cli.c router.c tdata.c tdata_validation.c bitset.c router_request.c router_result.c util.c tdata_realtime_expanded.c tdata_realtime_alerts.c tdata_io_v3_mmap.c radixtree.c gtfs-realtime.pb-c.c geometry.c router_dump.c hashgrid.c + $(CC) -DRRRR_STRICT -DRRRR_TDATA_IO_DYNAMIC -DRRRR_FAKE_REALTIME -DRRRR_VALGRIND -DRRRR_BITSET_64 -Wextra -Wall -ansi -pedantic -DRRRR_DEBUG -DRRRR_INFO -DRRRR_TDATA -ggdb -O0 -lm -lprotobuf-c -o cli cli.c router.c tdata.c tdata_validation.c bitset.c router_request.c router_result.c util.c tdata_realtime_expanded.c tdata_realtime_alerts.c tdata_io_v4_dynamic.c radixtree.c gtfs-realtime.pb-c.c geometry.c router_dump.c hashgrid.c plan_render_text.c polyline.c json.c plan_render_otp.c api.c set.c string_pool.c hashgrid_street_network.c street_network.c + $(CC) -DRRRR_STRICT -DRRRR_TDATA_IO_MMAP -DRRRR_FAKE_REALTIME -DRRRR_VALGRIND -DRRRR_BITSET_64 -Wextra -Wall -ansi -pedantic -DRRRR_DEBUG -DRRRR_INFO -DRRRR_TDATA -ggdb -O0 -lm -lprotobuf-c -o cli cli.c router.c tdata.c tdata_validation.c bitset.c router_request.c router_result.c util.c tdata_realtime_expanded.c tdata_realtime_alerts.c tdata_io_v4_mmap.c radixtree.c gtfs-realtime.pb-c.c geometry.c router_dump.c hashgrid.c plan_render_text.c polyline.c json.c plan_render_otp.c api.c set.c string_pool.c hashgrid_street_network.c street_network.c valgrind: - $(CC) -DRRRR_STRICT -DRRRR_FAKE_REALTIME -DRRRR_VALGRIND -DRRRR_BITSET_128 -DNDEBUG -O0 -ggdb3 -Wextra -Wall -std=c99 -lm -lprotobuf-c -o cli cli.c router.c tdata.c tdata_validation.c bitset.c router_request.c router_result.c util.c tdata_realtime_expanded.c tdata_realtime_alerts.c tdata_io_v3_dynamic.c tdata_io_v3_mmap.c radixtree.c gtfs-realtime.pb-c.c geometry.c hashgrid.c + $(CC) -I/usr/local/include -L/usr/local/lib -DRRRR_STRICT -DRRRR_FAKE_REALTIME -DRRRR_VALGRIND -DRRRR_BITSET_128 -DNDEBUG -O0 -ggdb3 -Wextra -Wall -std=c99 -lm -lprotobuf-c -o cli cli.c router.c tdata.c tdata_validation.c bitset.c router_request.c router_result.c util.c tdata_realtime_expanded.c tdata_realtime_alerts.c tdata_io_v4_dynamic.c tdata_io_v4_mmap.c radixtree.c gtfs-realtime.pb-c.c geometry.c hashgrid.c plan_render_text.c polyline.c json.c plan_render_otp.c api.c set.c string_pool.c hashgrid_street_network.c street_network.c prod: - $(CC) -DRRRR_BITSET_128 -DNDEBUG -O3 -Wextra -Wall -std=c99 -lm -lprotobuf-c -o cli cli.c router.c tdata.c tdata_validation.c bitset.c router_request.c router_result.c util.c tdata_realtime_expanded.c tdata_realtime_alerts.c tdata_io_v3_dynamic.c radixtree.c gtfs-realtime.pb-c.c geometry.c hashgrid.c + $(CC) -DRRRR_BITSET_128 -DNDEBUG -O3 -Wextra -Wall -std=c99 -lm -lprotobuf-c -o cli cli.c router.c tdata.c tdata_validation.c bitset.c router_request.c router_result.c util.c tdata_realtime_expanded.c tdata_realtime_alerts.c tdata_io_v4_dynamic.c radixtree.c gtfs-realtime.pb-c.c geometry.c hashgrid.c plan_render_text.c polyline.c json.c plan_render_otp.c api.c set.c string_pool.c hashgrid_street_network.c street_network.c api.c plan.c ioscli: - $(CC) -isysroot /var/sdks/Latest.sdk -DRRRR_TDATA_IO_MMAP -DRRRR_BITSET_64 -DNDEBUG -O2 -Wextra -Wall -std=c99 -lm -o cli router.c tdata.c tdata_validation.c bitset.c router_request.c router_result.c util.c tdata_io_v3_mmap.c radixtree.c geometry.c hashgrid.c cli.c + $(CC) -isysroot /var/sdks/Latest.sdk -DRRRR_TDATA_IO_MMAP -DRRRR_BITSET_64 -DNDEBUG -O2 -Wextra -Wall -std=c99 -lm -o cli router.c tdata.c tdata_validation.c bitset.c router_request.c router_result.c util.c tdata_io_v4_mmap.c radixtree.c geometry.c hashgrid.c cli.c plan_render_text.c api.c set.c string_pool.c hashgrid_street_network.c street_network.c ios: - $(CC) -isysroot /var/sdks/Latest.sdk -DRRRR_TDATA_IO_MMAP -DRRRR_BITSET_64 -DNDEBUG -O2 -Wextra -Wall -std=c99 -c router.c tdata.c tdata_validation.c bitset.c router_request.c router_result.c util.c tdata_io_v3_mmap.c radixtree.c geometry.c hashgrid.c - libtool -static -o ../librrrr.a router.o tdata.o tdata_validation.o bitset.o router_request.o router_result.o util.o tdata_io_v3_mmap.o radixtree.o geometry.o hashgrid.o - + $(CC) -isysroot /var/sdks/Latest.sdk -DRRRR_TDATA_IO_MMAP -DRRRR_BITSET_64 -DNDEBUG -O2 -Wextra -Wall -std=c99 -c router.c tdata.c tdata_validation.c bitset.c router_request.c router_result.c util.c tdata_io_v4_mmap.c radixtree.c geometry.c hashgrid.c api.c set.c string_pool.c hashgrid_street_network.c + libtool -static -o ../librrrr.a router.o tdata.o tdata_validation.o bitset.o router_request.o router_result.o util.o tdata_io_v4_mmap.o radixtree.o geometry.o hashgrid.o api.o set.o string_pool.o hashgrid_street_network.o street_network.o all: protoc-c --c_out=. gtfs-realtime.proto + $(CC) -c -Wextra -Wall -ansi -pedantic set.c + $(CC) -c -Wextra -Wall -ansi -pedantic json.c + $(CC) -c -Wextra -Wall -ansi -pedantic string_pool.c + $(CC) -c -Wextra -Wall -ansi -pedantic polyline.c $(CC) -c -Wextra -Wall -ansi -pedantic util.c $(CC) -c -Wextra -Wall -ansi -pedantic linkedlist.c $(CC) -c -Wextra -Wall -ansi -pedantic bitset.c @@ -28,14 +31,19 @@ all: $(CC) -c -Wextra -Wall -ansi -pedantic geometry.c $(CC) -c -Wextra -Wall -ansi -pedantic radixtree.c $(CC) -c -Wextra -Wall -ansi -pedantic tdata_validation.c - $(CC) -DRRRR_TDATA_IO_DYNAMIC -c -Wextra -Wall -ansi -pedantic tdata_io_v3_dynamic.c - $(CC) -DRRRR_TDATA_IO_MMAP -c -Wextra -Wall -ansi -pedantic tdata_io_v3_mmap.c + $(CC) -DRRRR_TDATA_IO_DYNAMIC -c -Wextra -Wall -ansi -pedantic tdata_io_v4_dynamic.c + $(CC) -DRRRR_TDATA_IO_MMAP -c -Wextra -Wall -ansi -pedantic tdata_io_v4_mmap.c $(CC) -DRRRR_STRICT -c -Wextra -Wall -ansi -pedantic tdata.c + $(CC) -c -Wextra -Wall -ansi -pedantic street_network.c + $(CC) -c -Wextra -Wall -ansi -pedantic hashgrid_street_network.c $(CC) -c -Wextra -Wall -ansi -pedantic tdata_realtime_alerts.c $(CC) -c -Wextra -Wall -ansi -pedantic tdata_realtime_expanded.c $(CC) -c -Wextra -Wall -ansi -pedantic router_request.c $(CC) -c -Wextra -Wall -ansi -pedantic router_dump.c $(CC) -c -Wextra -Wall -ansi -pedantic router.c $(CC) -c -Wextra -Wall -ansi -pedantic router_result.c + $(CC) -c -Wextra -Wall -ansi -pedantic plan_render_text.c + $(CC) -c -Wextra -Wall -ansi -pedantic plan_render_otp.c + $(CC) -c -Wextra -Wall -ansi -pedantic api.c # $(CC) -o cli -Wextra -Wall -ansi -pedantic cli.c stubs.c - $(CC) -lm -lprotobuf-c -o cli -Wextra -Wall -ansi -pedantic cli.c router.c tdata.c tdata_validation.c bitset.c router_request.c router_result.c util.c tdata_realtime_alerts.c tdata_realtime_expanded.c tdata_io_v3_dynamic.c radixtree.c gtfs-realtime.pb-c.c geometry.c hashgrid.c + $(CC) -lm -lprotobuf-c -o cli -Wextra -Wall -ansi -pedantic cli.c router.c tdata.c tdata_validation.c bitset.c router_request.c street_network.c hashgrid_street_network.c router_result.c util.c tdata_realtime_alerts.c tdata_realtime_expanded.c tdata_io_v4_dynamic.c radixtree.c gtfs-realtime.pb-c.c geometry.c hashgrid.c plan_render_text.c polyline.c api.c set.c string_pool.c diff --git a/README.md b/README.md index 0856b50..35067e2 100644 --- a/README.md +++ b/README.md @@ -23,15 +23,20 @@ clang provides very good error messages and warnings. At present time gcc provid 1. **check**: a unit testing framework for c. http://check.sourceforge.net/. +Dependencies for OSX +-------------------- + +1. **brew** +Install homebrew. http://brew.sh/ + +1. brew install protobuf-c check pkg-config cmake +Install the requirements to Cmake would work. Building transit data --------------------- -The ansi branch currently does not include any build scripts. Your best bet for generating timetable v3 outside the realm of Bliksem Integration will be timetable.py from the realtime_expanded2 branch. -First, run `python gtfsdb.py input.gtfs.zip output.gtfsdb` to load your GTFS feed into an SQLite database. -Next, run `python transfers.py output.gtfsdb` to add distance-based transfers to the transfers table in the database. -Finally, run `python timetable.py output.gtfsdb` to create the timetable file `timetable.dat` based on that GTFS database. -If you just want to experiment with the code download: http://1313.nl/timetable.dat +Browse into rrtimetable/rrtimetable. Run `python gtfs2rrrr.py gtfs.zip` to create the timetable file `timetable4.dat` based on that GTFS database. +If you just want to experiment with the code download: http://1313.nl/timetable4.dat Coding conventions ------------------ @@ -67,3 +72,9 @@ Been there done that * We have attempted to split the journey_pattern_t in a core routing and a meta data struct. The resulting code was 4% slower. * The original code for the router state was packed in one struct. It gave 10% improvement to split it in separate lists. * In an attempt to prevent overflow checking (migrating rtime to 32bit) resulted in a serious performance degradation. + +Conciderations +-------------- + + * In some places of the code the function argument has been made uint32_t to prevent overflowing on a multiplication later. Such example is initialize_transfers_full (router_t *router, uint32_t round) where the original uint8_t round is later multiplied with spidx_t. + diff --git a/api.c b/api.c new file mode 100644 index 0000000..5ed6e1e --- /dev/null +++ b/api.c @@ -0,0 +1,377 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and + * at https://github.com/bliksemlabs/rrrr/ + */ + +/* api.c : snippets for using the routerengine */ + +#include "config.h" +#include "api.h" +#include "bitset.h" +#include "plan.h" +#include "util.h" +#include "set.h" +#include "street_network.h" +#include "plan_render_text.h" +#include "router_result.h" + +#include + +/* Use first departure if someone wants to leave right now. + * This means that longer wait times might occur later. + * This is also the preference for ONBOARD searches. + */ +bool router_route_first_departure (router_t *router, router_request_t *req, plan_t *plan) { + router_reset (router); + router_request_search_street_network (req, router->tdata); + + if ( ! router_route (router, req) ) { + return false; + } + + if ( ! router_result_to_plan (plan, router, req) ) { + return false; + } + + return true; +} + +static void iterate_origins (router_t *router, router_request_t *req, + plan_t *plan, rtime_t *result, uint32_t n) { + plan_t work_plan; + uint32_t n_results = n; + plan_init (&work_plan); + + while (n_results) { + --n_results; + req->time = result[n_results] + RTIME_ONE_DAY; + + req->max_transfers = RRRR_DEFAULT_MAX_ROUNDS - 1; + /* time_cutoff may remain the highest value observed, if the search runs from last to first, but it seems we are missing some results if we don't apply it */ + router_reset (router); + + if ( router_route (router, req) ) { + int16_t i_itin = 0; + router_result_to_plan (&work_plan, router, req); + for (;i_itin < work_plan.n_itineraries && plan->n_itineraries < (RRRR_DEFAULT_MAX_ROUNDS * RRRR_DEFAULT_MAX_ROUNDS); ++i_itin) { + uint32_t i_plan = plan->n_itineraries; + itinerary_t *b = &work_plan.itineraries[i_itin]; + bool duplicate = false; + while (i_plan) { + itinerary_t *a; + i_plan--; + a = &plan->itineraries[i_plan]; + if (b->n_legs == 0) { + /* feels like a hack, how can n_itineraries > 0, but n_legs = 0? */ + duplicate = true; + break; + } + if (a->n_rides == b->n_rides && a->n_legs == b->n_legs && a->legs[1].sp_from == b->legs[1].sp_from && a->legs[a->n_legs - 1].sp_to == b->legs[b->n_legs - 1].sp_to && + a->legs[1].t0 == b->legs[1].t0 && a->legs[a->n_legs - 1].t1 == b->legs[b->n_legs - 1].t1) { + duplicate = true; + break; + } + } + if (!duplicate) { + plan->itineraries[plan->n_itineraries] = work_plan.itineraries[i_itin]; + plan->n_itineraries++; + } + } + } + } +} + + +bool router_route_all_departures (router_t *router, router_request_t *req, plan_t *plan) { + rtime_t *result = (rtime_t *) malloc(sizeof(rtime_t) * 64); + uint32_t n_results; + + router_request_search_street_network (req, router->tdata); + street_network_null_duration (&req->entry); + /* router_reset (router); */ + + n_results = tdata_n_departures_since (router->tdata, + req->entry.stop_points, req->entry.n_points, + result, 64, + req->time - RTIME_ONE_DAY, + req->time - RTIME_ONE_DAY + 1200); + + iterate_origins (router, req, plan, result, n_results); + + #if RRRR_MAX_FILTERED_OPERATORS > 0 || RRRR_MAX_BANNED_OPERATORS > 0 + if (false) { + bitset_t *bitset_op; + bitset_op = bitset_new (router->tdata->n_operator_ids); + bitset_clear (bitset_op); + + if (plan_get_operators (plan, router->tdata, bitset_op)) { + opidx_t n = (opidx_t) bitset_count (bitset_op); + if (n == 1) { + /* the journey advise is completely uniform, we should attempt to + * get some diversity by banning the specific operator. + */ + opidx_t op = (opidx_t) bitset_next_set_bit (bitset_op, 0); + set_add_uint8 (req->banned_operators, + &req->n_banned_operators, + RRRR_MAX_BANNED_OPERATORS, + op); + req->time_cutoff = UNREACHED; + iterate_origins (router, req, plan, result, n_results); + + /* update our operator list with the output of our last query + */ + plan_get_operators (plan, router->tdata, bitset_op); + + /* if we have a result, we don't have to count, we already + * know that we have an extra operator here. n++ would suffice. + */ + n = (opidx_t) bitset_count (bitset_op); + } + + if (n > 1) { + /* the journey advise is pluriform, we observe multiple operators + * lets see if it is possible to get uniform results for each of them. + */ + uint32_t op = bitset_next_set_bit (bitset_op, 0); + + while (op != BITSET_NONE) { + req->n_operators = 0; + set_add_uint8 (req->operators, &req->n_operators, RRRR_MAX_FILTERED_OPERATORS, (opidx_t) op); + req->time_cutoff = UNREACHED; + iterate_origins (router, req, plan, result, n_results); + op = bitset_next_set_bit (bitset_op, op + 1); + } + } + } + bitset_destroy (bitset_op); + } + #endif + + free (result); + + plan_sort (plan); + return true; +} + + + +/* If we would like to estimate all possible departures given + * a bag of stops in close proximity, we can do so by iterating + * over this stop lists find the journey patterns at these stops + * iterate over the active journey patterns for that operating + * date and return a list of rtime_t candidates. + */ + +static int compareRtime(const void *elem1, const void *elem2) { + return (int) (*(const rtime_t *) elem1) - (*(const rtime_t *) elem2); +} + +uint32_t tdata_n_departures_since (tdata_t *td, spidx_t *sps, spidx_t n_stops, rtime_t *result, uint32_t n_results, rtime_t since, rtime_t until) { + uint32_t n = 0; + while (n_stops) { + jpidx_t n_jps; + jpidx_t *jp_ret; + n_stops--; + n_jps = tdata_journey_patterns_for_stop_point (td, sps[n_stops], &jp_ret); + + while (n_jps) { + jppidx_t n_jpp; + spidx_t *jpp; + uint8_t *jpp_a; + n_jps--; + /* Implement calendar validation for the journey pattern */ + /* if (router->day_mask & td->journey_pattern_active[n_jps]) */ + + jpp = tdata_points_for_journey_pattern(td, jp_ret[n_jps]); + jpp_a = tdata_stop_point_attributes_for_journey_pattern(td, jp_ret[n_jps]); + + n_jpp = td->journey_patterns[jp_ret[n_jps]].n_stops; + while (n_jpp) { + n_jpp--; + if (jpp_a[n_jpp] & rsa_boarding && jpp[n_jpp] == sps[n_stops]) { + jp_vjoffset_t n_vjs = td->journey_patterns[n_jps].n_vjs; + while (n_vjs) { + vehicle_journey_t *vj; + rtime_t arrival; + n_vjs--; + vj = &td->vjs[n_vjs]; + arrival = vj->begin_time + td->stop_times[vj->stop_times_offset + n_jpp].arrival; + if (arrival >= since && arrival <= until && (n == 0 || result[n - 1] != arrival)) result[n++] = arrival; + if (n == n_results) goto full; + } + /* we can't break here because the same spidx may happen later */ + } + } + } + } + +full: + qsort(result, n, sizeof(rtime_t), compareRtime); + n = dedupRtime(result, n); + return n; +} + +/* Use naive reversal only if you want to show the very best arrival time + * considering the presented location unconstrainted by other parameters + * such as walking time or number of transfers. + * + * When searching clockwise we will board any vehicle_journey that will bring us at + * the earliest time at any destination location. If we have to wait at + * some stage for a connection, and this wait time exceeds the frequency + * of the ingress network, we may suggest a later departure decreases + * overal waitingtime. + * + * To compress waitingtime we employ a reversal. A clockwise search + * departing at 9:00am and arriving at 10:00am is observed as was + * requested: what vehicle_journey allows to arrive at 10:00am? The counter clockwise + * search starts at 10:00am and offers the last possible arrival at 9:15am. + * This bounds our searchspace between 9:15am and 10:00am. + * + * Because of the memory structure. We are not able to render an arrive-by + * search, therefore the second arrival will start at 9:15am and should + * render exactly the same vehicle_journey. This is not always true, especially not + * when there are multiple paths with exactly the same transittime. + * + * For an arrive_by counter clockwise search, we must make the result + * clockwise. Only one reversal is required. For the more regular clockwise + * search, the compression is handled in the first reversal (ccw) and made + * clockwise in the second reversal. + * + * Note that for request that start onboard of a vehicle_journey we do not perform any + * reversals since there is no wait-time to be compressed + */ +bool router_route_naive_reversal (router_t *router, router_request_t *req, plan_t *plan) { + uint8_t i; + uint8_t n_reversals = (uint8_t) (req->arrive_by ? 1 : 2); + + router_reset (router); + router_request_search_street_network (req, router->tdata); + if ( ! router_route (router, req) ) { + return false; + } + + /*reversal is meaningless/useless in on-board */ + if (req->onboard_journey_pattern == JP_NONE) { + for (i = 0; i < n_reversals; ++i) { + if (!router_request_reverse(router, req)) { + return false; + } + + router_reset(router); + if (!router_route(router, req)) { + return false; + } + + } + } + + if ( ! router_result_to_plan (plan, router, req) ) { + return false; + } + + return true; +} + +/* Use the advanced reversal if you want to show the end user all + * the alternatives possible given the choosen departure. + */ +bool router_route_full_reversal (router_t *router, router_request_t *req, plan_t *plan) { + router_request_t req_storage[RRRR_DEFAULT_MAX_ROUNDS * RRRR_DEFAULT_MAX_ROUNDS]; + plan_t work_plan; + uint8_t i_rev; + uint8_t n_req = 0; + uint8_t n2_req; + + router_reset (router); + plan_init (&work_plan); + router_request_search_street_network (req, router->tdata); + + if ( ! router_route (router, req) ) { + return false; + } + + if (req->from_stop_point == ONBOARD){ + /*reversal is meaningless/useless in on-board */ + return router_result_to_plan (plan, router, req); + }else if ( ! router_result_to_plan (&work_plan, router, req) ) { + return false; + } + /* Copy direct (without street_network itineraries to the result */ + { + int16_t i_itin = 0; + for (;i_itin < work_plan.n_itineraries;++i_itin) { + if (work_plan.itineraries[i_itin].n_legs == 1) { + plan->itineraries[plan->n_itineraries] = work_plan.itineraries[i_itin]; + ++plan->n_itineraries; + break; + } + } + } + /* Fetch the first possible time to get out of here by transit */ + req_storage[n_req] = *req; + if ( ! req_storage[n_req].arrive_by) { + rtime_t earliest_departure = UNREACHED; + spidx_t i_stop; + + for (i_stop = 0; i_stop < router->tdata->n_stop_points; ++i_stop) { + if (router->states_board_time[i_stop] != UNREACHED) { + earliest_departure = MIN(earliest_departure, router->states_board_time[i_stop]); + } + } + req_storage[n_req].time = earliest_departure; + } + i_rev = n_req; + n_req++; + + /* first reversal, always required */ + if (!router_request_reverse_plan (router, &req_storage[i_rev], req_storage, &n_req, &work_plan, i_rev)) { + return false; + } + + i_rev++; + n2_req = n_req; + + for (; i_rev < n_req; ++i_rev) { + bool reroute = true; + /* Check if we can skip the third reversal if we rendered a fitting itinerary in the first forward search */ + if (!req_storage[i_rev].arrive_by && + req_storage[i_rev].entry.n_points == 1 && + req_storage[i_rev].exit.n_points == 1){ + int16_t i_itin; + for (i_itin = 0; i_itin < work_plan.n_itineraries;++i_itin){ + leg_t first_leg = work_plan.itineraries[i_itin].legs[0]; + leg_t last_leg = work_plan.itineraries[i_itin].legs[work_plan.itineraries[i_itin].n_legs-1]; + if (work_plan.itineraries[i_itin].n_rides == req_storage[i_rev].max_transfers+1 && + first_leg.sp_to == req_storage[i_rev].entry.stop_points[0] && + last_leg.sp_from == req_storage[i_rev].exit.stop_points[0] && + first_leg.t0 == req_storage[i_rev].time && + last_leg.t1 == req_storage[i_rev].time_cutoff){ + plan->itineraries[plan->n_itineraries] = work_plan.itineraries[i_itin]; + ++plan->n_itineraries; + reroute = false; + break; + } + } + } + + if (!reroute) continue; + + router_reset (router); + + if ( ! router_route (router, &req_storage[i_rev]) ) { + return false; + } + + if ( ! req_storage[i_rev].arrive_by && + ! router_result_to_plan (plan, router, &req_storage[i_rev])) { + return false; + } + + if ( ! req->arrive_by && i_rev < n2_req && + ! router_request_reverse_all (router, &req_storage[i_rev], req_storage, &n_req, i_rev)) { + return false; + } + } + + return true; +} diff --git a/api.h b/api.h new file mode 100644 index 0000000..3ec845f --- /dev/null +++ b/api.h @@ -0,0 +1,15 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and + * at https://github.com/bliksemlabs/rrrr/ + */ + +#include "config.h" +#include "router_result.h" +#include "router_request.h" +#include "street_network.h" + +bool router_route_first_departure (router_t *router, router_request_t *req, plan_t *plan); +bool router_route_all_departures (router_t *router, router_request_t *req, plan_t *plan); +bool router_route_naive_reversal (router_t *router, router_request_t *req, plan_t *plan); +bool router_route_full_reversal (router_t *router, router_request_t *req, plan_t *plan); +uint32_t tdata_n_departures_since (tdata_t *td, spidx_t *sps, spidx_t n_stops, rtime_t *result, uint32_t n_results, rtime_t since, rtime_t until); diff --git a/bitset.c b/bitset.c index 3b3d8d2..eb9e33e 100644 --- a/bitset.c +++ b/bitset.c @@ -1,17 +1,14 @@ -/* Copyright 2013 Bliksem Labs. +/* Copyright 2013-2015 Bliksem Labs B.V. * See the LICENSE file at the top-level directory of this distribution and at * https://github.com/bliksemlabs/rrrr/ */ /* bitset.c : compact enumerable bit array */ #include "bitset.h" -#include #include #include -#include -#include #include - +#include /* Initialize a pre-allocated bitset struct, * allocating memory for bits_t holding the bits. @@ -51,19 +48,23 @@ void bitset_destroy(bitset_t *self) { } void bitset_clear(bitset_t *self) { - uint32_t i_chunk = self->n_chunks; - do { - i_chunk--; - self->chunks[i_chunk] = (bits_t) 0; - } while (i_chunk); + /* memset uses an SSE implementation when available that is much faster than writing 0s after each other and + * hoping the auto-vectorization kicks in. memset is by a factor of at least to 2 times faster */ + memset (self->chunks, 0, sizeof (bits_t) * self->n_chunks); } void bitset_black(bitset_t *self) { uint32_t i_chunk = self->n_chunks; - do { + + while (i_chunk) { i_chunk--; self->chunks[i_chunk] = ~((bits_t) 0); - } while (i_chunk); + } + + /* This sets the actual number of bits of capacity opposed to all + * bits, the only reason to do this is to allow counting. + */ + self->chunks[self->n_chunks - 1] = (((bits_t) 1) << (BS_BITS - (self->capacity & (BS_BITS - 1)))) - 1; } void bitset_mask_and(bitset_t *self, bitset_t *mask) { @@ -71,10 +72,10 @@ void bitset_mask_and(bitset_t *self, bitset_t *mask) { assert (self->capacity == mask->capacity); - do { + while (i_chunk) { i_chunk--; self->chunks[i_chunk] &= mask->chunks[i_chunk]; - } while (i_chunk); + } } /* Our bitset code is storing a long number of bits by packing an array of @@ -154,6 +155,36 @@ uint32_t bitset_next_set_bit(bitset_t *bs, uint32_t index) { return BITSET_NONE; } +/* TODO: optimise; http://stackoverflow.com/questions/109023/how-to-count-the-number-of-set-bits-in-a-32-bit-integer */ +#if 0 +uint32_t bitset_count(bitset_t *self) { + uint32_t total = 0; + uint32_t elem; + for (elem = bitset_next_set_bit(self, 0); + elem != BITSET_NONE; + elem = bitset_next_set_bit(self, elem + 1)) { + total++; + } + return total; +} +#endif + +uint32_t bitset_count(bitset_t *self) { + uint32_t c = 0; + uint32_t i_chunk = self->n_chunks; + + while (i_chunk) { + bits_t v; + i_chunk--; + v = self->chunks[i_chunk]; + v = v - ((v >> 1) & (bits_t)~(bits_t)0/3); + v = (v & (bits_t)~(bits_t)0/15*3) + ((v >> 2) & (bits_t)~(bits_t)0/15*3); + v = (v + (v >> 4)) & (bits_t)~(bits_t)0/255*15; + c += (bits_t)(v * ((bits_t)~(bits_t)0/255)) >> (sizeof(bits_t) - 1) * 8; + } + return c; +} + #ifdef RRRR_DEBUG void bitset_dump(bitset_t *self) { diff --git a/bitset.h b/bitset.h index f229fe6..d9a0162 100644 --- a/bitset.h +++ b/bitset.h @@ -1,4 +1,4 @@ -/* Copyright 2013 Bliksem Labs. +/* Copyright 2013-2015 Bliksem Labs B.V. * See the LICENSE file at the top-level directory of this distribution and at * https://github.com/bliksemlabs/rrrr/ */ @@ -70,6 +70,9 @@ bool bitset_get(bitset_t *self, uint32_t index); */ uint32_t bitset_next_set_bit(bitset_t*, uint32_t index); +/* Return the number of bits set in the bitset_t */ +uint32_t bitset_count(bitset_t *self); + #ifdef RRRR_DEBUG /* Print a string-representation of this bitset to STDERR */ void bitset_dump(bitset_t *self); diff --git a/cli.c b/cli.c index 0b5bf13..4581ca5 100644 --- a/cli.c +++ b/cli.c @@ -1,4 +1,4 @@ -/* Copyright 2013 Bliksem Labs. +/* Copyright 2013-2015 Bliksem Labs B.V. * See the LICENSE file at the top-level directory of this distribution and * at https://github.com/bliksemlabs/rrrr/ */ @@ -10,9 +10,21 @@ #include #include +#include "config.h" +#include "api.h" +#include "plan.h" +#include "set.h" #include "router_request.h" #include "router_result.h" +#ifdef RRRR_FEATURE_RENDER_TEXT +#include "plan_render_text.h" +#endif + +#ifdef RRRR_FEATURE_RENDER_OTP +#include "plan_render_otp.h" +#endif + #ifdef RRRR_FEATURE_REALTIME #ifdef RRRR_FEATURE_REALTIME_ALERTS @@ -25,70 +37,18 @@ #endif -#define OUTPUT_LEN 8000 +#define OUTPUT_LEN 32000 typedef struct cli_arguments cli_arguments_t; struct cli_arguments { char *gtfsrt_alerts_filename; char *gtfsrt_tripupdates_filename; - uint32_t repeat; + long repeat; + uint32_t(*plan_render)(plan_t *plan, tdata_t *tdata, char *buf, uint32_t buflen); bool verbose; + bool has_latlon; }; -#if RRRR_MAX_BANNED_JOURNEY_PATTERNS > 0 -static void set_add_jp (uint32_t *set, - uint8_t *length, uint8_t max_length, - uint32_t value) { - uint8_t i; - - if (*length >= max_length) return; - - for (i = 0; i < *length; ++i) { - if (set[i] == value) return; - } - - set[*length] = value; - (*length)++; -} -#endif - -#if RRRR_MAX_BANNED_STOPS > 0 || RRRR_MAX_BANNED_STOPS_HARD > 0 -static void set_add_sp (spidx_t *set, - uint8_t *length, uint8_t max_length, - spidx_t value) { - uint8_t i; - - if (*length >= max_length) return; - - for (i = 0; i < *length; ++i) { - if (set[i] == value) return; - } - - set[*length] = value; - (*length)++; -} -#endif - - -#if RRRR_MAX_BANNED_VEHICLE_JOURNEYS > 0 -static void set_add_trip (uint32_t *set1, uint16_t *set2, - uint8_t *length, uint8_t max_length, - uint32_t value1, uint16_t value2) { - uint8_t i; - - if (*length >= max_length) return; - - for (i = 0; i < *length; ++i) { - if (set1[i] == value1 && - set2[i] == value2) return; - } - - set1[*length] = value1; - set2[*length] = value2; - (*length)++; -} -#endif - int main (int argc, char *argv[]) { /* our return value */ int status = EXIT_SUCCESS; @@ -105,11 +65,28 @@ int main (int argc, char *argv[]) { /* the router structure, should not be manually changed */ router_t router; + /* the plan structure, created for all the results */ + plan_t plan; + /* initialise the structs so we can always trust NULL values */ memset (&tdata, 0, sizeof(tdata_t)); memset (&router, 0, sizeof(router_t)); memset (&cli_args, 0, sizeof(cli_args)); + plan_init (&plan); + cli_args.repeat = 1; + + /* try to set a default plan_render function. */ + #ifdef RRRR_FEATURE_RENDER_TEXT + cli_args.plan_render = plan_render_text; + #else + #ifdef RRRR_FEATURE_RENDER_OTP + cli_args.plan_render = plan_render_otp; + #else + cli_args.plan_render = NULL; + #endif + #endif + /* * * * * * * * * * * * * * * * * * * * * * * PHASE ZERO: HANDLE COMMANDLINE ARGUMENTS * @@ -120,29 +97,46 @@ int main (int argc, char *argv[]) { "[ --verbose ] [ --randomize ]\n" "[ --arrive=YYYY-MM-DDTHH:MM:SS | " "--depart=YYYY-MM-DDTHH:MM:SS ]\n" - "[ --from-idx=idx | --from-latlon=Y,X ]\n" - "[ --via-idx=idx | --via-latlon=Y,X ]\n" - "[ --to-idx=idx | --to-latlon=Y,X ]\n" + "[ --from-idx=idx | --from-id=id | --from-jp-vj-offset=jpidx,vj_offset | --from-sp-idx=idx | --from-sp-id=id | --from-latlon=Y,X ]\n" + "[ --via-idx=idx | --via-id=id | --via-latlon=Y,X ]\n" + "[ --to-idx=idx | --to-id=id | --to-sp-idx=idx | --to-sp-id=id | --to-latlon=Y,X ]\n", argv[0]); + + fprintf(stderr, +#if RRRR_MAX_FILTERED_OPERATORS > 0 + "[ --operator=name ]\n" +#endif #if RRRR_MAX_BANNED_JOURNEY_PATTERNS > 0 "[ --banned-jp-idx=idx ]\n" #endif -#if RRRR_MAX_BANNED_STOPS > 0 +#if RRRR_MAX_BANNED_OPERATORS > 0 + "[ --banned-op-idx=idx | --banned-operator=name ]\n" +#endif +#if RRRR_MAX_BANNED_STOP_POINTS > 0 "[ --banned-stop-idx=idx ]\n" #endif -#if RRRR_MAX_BANNED_STOP_HARD > 0 +#if RRRR_MAX_BANNED_STOP_POINTS_HARD > 0 "[ --banned-stop-hard-idx=idx ]\n" #endif #if RRRR_MAX_BANNED_VEHICLE_JOURNEYS > 0 - "[ --banned-vj-offset=jp_idx,trip_offset ]\n" + "[ --banned-vj-offset=jp_idx,vj_offset ]\n" #endif -#if RRRR_FEATURE_REALTIME_ALERTS == 1 +#ifdef RRRR_FEATURE_REALTIME +#ifdef RRRR_FEATURE_REALTIME_ALERTS "[ --gtfsrt-alerts=filename.pb ]\n" #endif -#if RRRR_FEATURE_REALTIME_EXPANDED == 1 +#ifdef RRRR_FEATURE_REALTIME_EXPANDED "[ --gtfsrt-tripupdates=filename.pb ]\n" #endif - "[ --repeat=n ]\n" - , argv[0]); +#endif + "[ --render=none" +#ifdef RRRR_FEATURE_RENDER_OTP + "|otp" +#endif +#ifdef RRRR_FEATURE_RENDER_TEXT + "|text" +#endif + " ]\n" + "[ --repeat=n ]\n"); } /* The first mandartory argument is the timetable file. We must initialise @@ -152,12 +146,12 @@ int main (int argc, char *argv[]) { * The tdata_file stores timetable data in binary format. * We have several ways to load this data from disk; * - * 1) memory mapped (tdata_io_v3_mmap.c) + * 1) memory mapped (tdata_io_v4_mmap.c) * Pro: The operation is resource efficient * Con: The operating system has to support it, * the data can't be arbitrary extended * - * 2) using read into dynamically allocated memory (tdata_io_v3_dynamic.c) + * 2) using read into dynamically allocated memory (tdata_io_v4_dynamic.c) * Pro: This will work in all C implementations * Con: Requires heap memory which stores the timetable in full */ @@ -168,11 +162,23 @@ int main (int argc, char *argv[]) { goto clean_exit; } + /* Report the validity of the timetable. + * Requests outside this period will result in interpolation. + */ + { + uint64_t starttime, endtime; + char start[20], end[20]; + tdata_validity (&tdata, &starttime, &endtime); + strftime(start, 20, "%Y-%m-%d %H:%M:%S", localtime((time_t *) &starttime)); + strftime(end, 20, "%Y-%m-%d %H:%M:%S", localtime((time_t *) &endtime)); + printf("Tdata validity: %s %s\n", start, end); + } + /* initialise the request */ router_request_initialize (&req); /* initialise the random function */ - srand(time(NULL)); + srand((unsigned int) time(NULL)); { @@ -200,14 +206,30 @@ int main (int argc, char *argv[]) { case 'f': if (strncmp(argv[i], "--from-idx=", 11) == 0) { - req.from = (uint32_t) strtol(&argv[i][11], NULL, 10); + strtospidx (&argv[i][11], &tdata, &req.from_stop_area, NULL); + } + else if (strncmp(argv[i], "--from-sp-idx=", 14) == 0) { + strtospidx (&argv[i][14], &tdata, &req.from_stop_point, NULL); + } + else if (strncmp(argv[i], "--from-id=", 10) == 0) { + req.from_stop_area = tdata_stop_areaidx_by_stop_area_id (&tdata, &argv[i][10], 0); + } + else if (strncmp(argv[i], "--from-sp-id=", 13) == 0) { + req.from_stop_point = tdata_stop_pointidx_by_stop_point_id (&tdata, &argv[i][13], 0); } - #ifdef RRRR_FEATURE_LATLON else if (strncmp(argv[i], "--from-latlon=", 14) == 0) { - /* TODO: check return value */ - strtolatlon(&argv[i][14], &req.from_latlon); + cli_args.has_latlon = strtolatlon(&argv[i][14], &req.from_latlon); + } + else if (strncmp(argv[i], "--from-jp-vj-offset=", 20) == 0) { + char *endptr; + jpidx_t jp; + if (strtojpidx (&argv[i][20], &tdata, &jp, &endptr)) { + jp_vjoffset_t vj_o; + strtovjoffset (++endptr, &tdata, jp, &vj_o, NULL); + req.onboard_journey_pattern = jp; + req.onboard_journey_pattern_vjoffset = vj_o; + } } - #endif break; #ifdef RRRR_FEATURE_REALTIME @@ -225,25 +247,60 @@ int main (int argc, char *argv[]) { break; #endif + #if RRRR_MAX_FILTERED_OPERATORS > 0 + case 'o': + if (strncmp(argv[i], "--operator=", 11) == 0) { + opidx_t operator = tdata_operator_idx_by_operator_name(&tdata, &argv[i][11], 0); + while (operator != OP_NONE) + { + set_add_uint8 (req.operators, &req.n_operators, RRRR_MAX_FILTERED_OPERATORS, operator); + operator = tdata_operator_idx_by_operator_name(&tdata, &argv[i][11], operator + 1); + } + } + break; + #endif + case 'r': if (strcmp(argv[i], "--randomize") == 0) { router_request_randomize (&req, &tdata); } else if (strncmp(argv[i], "--repeat=", 9) == 0) { - cli_args.repeat = (uint32_t) strtol(&argv[i][9], NULL, 10); + cli_args.repeat = strtol(&argv[i][9], NULL, 10); + } + else if (strncmp(argv[i], "--render=", 9) == 0) { + if (strcmp(&argv[i][9], "none") == 0) { + cli_args.plan_render = NULL; + } + #ifdef RRRR_FEATURE_RENDER_TEXT + if (strcmp(&argv[i][9], "text") == 0) { + cli_args.plan_render = plan_render_text; + } + #endif + #ifdef RRRR_FEATURE_RENDER_OTP + else if (strcmp(&argv[i][9], "otp") == 0) { + cli_args.plan_render = plan_render_otp; + } + #endif } break; case 't': if (strncmp(argv[i], "--to-idx=", 9) == 0) { - req.to = (uint32_t) strtol(&argv[i][9], NULL, 10); + strtospidx (&argv[i][9], &tdata, &req.to_stop_area, NULL); + } + else if (strncmp(argv[i], "--to-sp-idx=", 12) == 0) { + strtospidx (&argv[i][12], &tdata, &req.to_stop_point, NULL); + } + else if (strncmp(argv[i], "--to-id=", 8) == 0) { + req.to_stop_area = tdata_stop_areaidx_by_stop_area_id (&tdata, &argv[i][8], 0); + } + else if (strncmp(argv[i], "--to-sp-id=", 11) == 0) { + req.to_stop_point = tdata_stop_pointidx_by_stop_point_id (&tdata, &argv[i][11], 0); } - #ifdef RRRR_FEATURE_LATLON else if (strncmp(argv[i], "--to-latlon=", 12) == 0) { - /* TODO: check return value */ - strtolatlon(&argv[i][12], &req.to_latlon); + cli_args.has_latlon = strtolatlon(&argv[i][12], &req.to_latlon); } - #endif + break; case 'b': @@ -251,36 +308,61 @@ int main (int argc, char *argv[]) { #if RRRR_MAX_BANNED_JOURNEY_PATTERNS > 0 else if (strncmp(argv[i], "--banned-jp-idx=", 16) == 0) { - uint32_t jp_index = (uint32_t) strtol(&argv[i][16], NULL, 10); - if (jp_index < tdata.n_journey_patterns) { + jpidx_t jp; + if (strtojpidx (&argv[i][16], &tdata, &jp, NULL)) { set_add_jp(req.banned_journey_patterns, &req.n_banned_journey_patterns, RRRR_MAX_BANNED_JOURNEY_PATTERNS, - jp_index); + jp); } } #endif - #if RRRR_MAX_BANNED_STOPS > 0 + #if RRRR_MAX_BANNED_OPERATORS > 0 + else + if (strncmp(argv[i], "--banned-op-idx=", 16) == 0) { + opidx_t op; + if (strtoopidx (&argv[i][16], &tdata, &op, NULL)) { + set_add_uint8 (req.banned_operators, + &req.n_banned_operators, + RRRR_MAX_BANNED_OPERATORS, + op); + } + } + else + if (strncmp(argv[i], "--banned-operator=", 18) == 0) { + opidx_t operator = tdata_operator_idx_by_operator_name(&tdata, &argv[i][18], 0); + while (operator != OP_NONE) + { + set_add_uint8 (req.banned_operators, + &req.n_banned_operators, + RRRR_MAX_BANNED_OPERATORS, + operator); + + operator = tdata_operator_idx_by_operator_name(&tdata, &argv[i][18], operator + 1); + } + } + #endif + #if RRRR_MAX_BANNED_STOP_POINTS > 0 else if (strncmp(argv[i], "--banned-stop-idx=", 19) == 0) { - uint32_t stop_idx = (uint32_t) strtol(&argv[i][19], NULL, 10); - if (stop_idx < tdata.n_stops) { + spidx_t sp; + if (strtospidx (&argv[i][19], &tdata, &sp, NULL)) { set_add_sp(req.banned_stops, &req.n_banned_stops, - RRRR_MAX_BANNED_STOPS, - stop_idx); + RRRR_MAX_BANNED_STOP_POINTS, + sp); } } #endif - #if RRRR_MAX_BANNED_STOPS_HARD > 0 + #if RRRR_MAX_BANNED_STOP_POINTS_HARD > 0 else if (strncmp(argv[i], "--banned-stop-hard-idx=", 23) == 0) { - uint32_t stop_idx = (uint32_t) strtol(&argv[i][23], NULL, 10); - if (stop_idx < tdata.n_stops) { - set_add_sp(req.banned_stops_hard, - &req.n_banned_stops_hard, - RRRR_MAX_BANNED_STOPS_HARD, - stop_idx); + spidx_t sp; + if (strtospidx (&argv[i][23], &tdata, &sp, NULL)) { + set_add_sp(req.banned_stop_points_hard, + &req.n_banned_stop_points_hard, + RRRR_MAX_BANNED_STOP_POINTS_HARD, + sp); } } #endif @@ -288,17 +370,16 @@ int main (int argc, char *argv[]) { else if (strncmp(argv[i], "--banned-vj-offset=", 19) == 0) { char *endptr; - uint32_t jp_index; - - jp_index = (uint32_t) strtol(&argv[i][21], &endptr, 10); - if (jp_index < tdata.n_journey_patterns && endptr[0] == ',') { - uint16_t vj_offset = strtol(++endptr, NULL, 10); - if (vj_offset < tdata.journey_patterns[jp_index].n_vjs) { - set_add_trip(req.banned_vjs_journey_pattern, - req.banned_vjs_offset, - &req.n_banned_vjs, - RRRR_MAX_BANNED_VEHICLE_JOURNEYS, - jp_index, vj_offset); + jpidx_t jp; + if (strtojpidx (&argv[i][21], &tdata, &jp, &endptr)) { + jp_vjoffset_t vj_o; + if (endptr[0] == ',' && + strtovjoffset (++endptr, &tdata, jp, &vj_o, NULL)) { + set_add_vj(req.banned_vjs_journey_pattern, + req.banned_vjs_offset, + &req.n_banned_vjs, + RRRR_MAX_BANNED_VEHICLE_JOURNEYS, + jp, vj_o); } } } @@ -309,15 +390,15 @@ int main (int argc, char *argv[]) { if (strcmp(argv[i], "--verbose") == 0) { cli_args.verbose = true; } - else if (strncmp(argv[i], "--via=", 6) == 0) { - req.via = (uint32_t) strtol(&argv[i][6], NULL, 10); + else if (strncmp(argv[i], "--via-idx=", 10) == 0) { + strtospidx (&argv[i][10], &tdata, &req.via_stop_point, NULL); + } + else if (strncmp(argv[i], "--via-id=", 9) == 0) { + req.via_stop_point = tdata_stop_pointidx_by_stop_point_id (&tdata, &argv[i][9], 0); } - #ifdef RRRR_FEATURE_LATLON else if (strncmp(argv[i], "--via-latlon=", 13) == 0) { - /* TODO: check return value */ - strtolatlon(&argv[i][13], &req.via_latlon); + cli_args.has_latlon = strtolatlon(&argv[i][13], &req.via_latlon); } - #endif break; case 'w': @@ -325,7 +406,10 @@ int main (int argc, char *argv[]) { req.walk_speed = (float) strtod(&argv[i][13], NULL); } else if (strncmp(argv[i], "--walk-slack=", 13) == 0) { - req.walk_slack = (uint8_t) strtod(&argv[i][13], NULL); + long walk_slack = strtol(&argv[i][13], NULL, 10); + if (walk_slack >= 0 && walk_slack <= 255) { + req.walk_slack = (uint8_t) walk_slack; + } } break; @@ -343,14 +427,7 @@ int main (int argc, char *argv[]) { if (cli_args.gtfsrt_alerts_filename != NULL || cli_args.gtfsrt_tripupdates_filename != NULL) { - tdata.stopid_index = radixtree_load_strings_from_tdata (tdata.stop_ids, tdata.stop_ids_width, tdata.n_stops); - tdata.vjid_index = radixtree_load_strings_from_tdata (tdata.vj_ids, tdata.vj_ids_width, tdata.n_vjs); - tdata.lineid_index = radixtree_load_strings_from_tdata (tdata.line_ids, tdata.line_ids_width, tdata.n_journey_patterns); - - /* Validate the radixtrees are actually created. */ - if (!(tdata.stopid_index && - tdata.vjid_index && - tdata.lineid_index)) { + if (!tdata_realtime_setup (&tdata)) { status = EXIT_FAILURE; goto clean_exit; } @@ -379,6 +456,11 @@ int main (int argc, char *argv[]) { } req.time_rounded = false; + if (! tdata_hashgrid_setup (&tdata)) { + status = EXIT_FAILURE; + goto clean_exit; + } + /* * * * * * * * * * * * * * * * * * * PHASE ONE: INITIALISE THE ROUTER * @@ -401,88 +483,27 @@ int main (int argc, char *argv[]) { * * * * * * * * * * * * * * * * * * * */ -plan: - /* While the scratch space remains allocated, each new search may require - * reinitialisation of this memory. - */ - router_reset (&router); - - /* Reset the cutoff time to UNREACHED or 0 to simulate a complete new request, - * this erases the set cutoff time from reversals in previous requests in the repeat function - */ - if (req.arrive_by) { - req.time_cutoff = 0; - } else { - req.time_cutoff = UNREACHED; - } - - /* The router is now able to take a request, and to search - * the first arrival time at the target, given the requests - * origin. - */ - - if ( ! router_route (&router, &req)) { - /* if the search failed we must exit */ - status = EXIT_FAILURE; - goto clean_exit; - } - - /* To debug the router, we render an intermediate result */ - if (cli_args.verbose) { - char result_buf[OUTPUT_LEN]; - router_request_dump (&req, &tdata); - router_result_dump(&router, &req, result_buf, OUTPUT_LEN); - puts(result_buf); - } - - /* When searching clockwise we will board any vehicle_journey that will bring us at - * the earliest time at any destination location. If we have to wait at - * some stage for a connection, and this wait time exceeds the frequency - * of the ingress network, we may suggest a later departure decreases - * overal waitingtime. - * - * To compress waitingtime we employ a reversal. A clockwise search - * departing at 9:00am and arriving at 10:00am is observed as was - * requested: what vehicle_journey allows to arrive at 10:00am? The counter clockwise - * search starts at 10:00am and offers the last possible arrival at 9:15am. - * This bounds our searchspace between 9:15am and 10:00am. - * - * Because of the memory structure. We are not able to render an arrive-by - * search, therefore the second arrival will start at 9:15am and should - * render exactly the same vehicle_journey. This is not always true, especially not - * when there are multiple paths with exactly the same transittime. - * - * - * For an arrive_by counter clockwise search, we must make the result - * clockwise. Only one reversal is required. For the more regular clockwise - * search, the compression is handled in the first reversal (ccw) and made - * clockwise in the second reversal. - */ - - { - uint8_t i; - uint8_t n_reversals = req.arrive_by ? 1 : 2; - for (i = 0; i < n_reversals; ++i) { - if ( ! router_request_reverse (&router, &req)) { - /* if the reversal fails we must exit */ - status = EXIT_FAILURE; - goto clean_exit; - } - - router_reset (&router); + while (cli_args.repeat){ + --cli_args.repeat; + plan_init (&plan); + + /* Reset the cutoff time to UNREACHED or 0 to simulate a complete new request, + * this erases the set cutoff time from reversals in previous requests in the repeat function + */ + if (req.arrive_by) { + req.time_cutoff = 0; + } else { + req.time_cutoff = UNREACHED; + } - if ( ! router_route (&router, &req)) { - status = EXIT_FAILURE; - goto clean_exit; - } + /* The router is now able to take a request, and to search + * the first arrival time at the target, given the requests + * origin. + */ - if (cli_args.verbose) { - char result_buf[OUTPUT_LEN]; - puts ("Repeated search with reversed request: \n"); - router_request_dump (&req, &tdata); - router_result_dump (&router, &req, result_buf, OUTPUT_LEN); - puts (result_buf); - } + if (!router_route_full_reversal(&router, &req, &plan)) { + status = EXIT_FAILURE; + goto clean_exit; } } @@ -490,19 +511,11 @@ int main (int argc, char *argv[]) { * PHASE THREE: RENDER THE RESULTS * * * * * * * * * * * * * * * * * * * */ - - /* Output only final result in non-verbose mode */ - if ( ! cli_args.verbose) { - char result_buf[OUTPUT_LEN] = ""; - router_result_dump(&router, &req, result_buf, OUTPUT_LEN); - - /* For benchmarking: repeat the search up to n time */ - if (cli_args.repeat > 0) { - cli_args.repeat--; - goto plan; - } - - puts (result_buf); + if (cli_args.plan_render) { + char result_buf[OUTPUT_LEN]; + plan.req = req; + cli_args.plan_render (&plan, &tdata, result_buf, OUTPUT_LEN); + puts(result_buf); } /* * * * * * * * * * * * * * * * * * * @@ -523,11 +536,8 @@ int main (int argc, char *argv[]) { /* Deallocate the scratchspace of the router */ router_teardown (&router); - #ifdef RRRR_FEATURE_REALTIME - if (tdata.stopid_index) radixtree_destroy (tdata.stopid_index); - if (tdata.vjid_index) radixtree_destroy (tdata.vjid_index); - if (tdata.lineid_index) radixtree_destroy (tdata.lineid_index); - #endif + /* Deallocate the hashgrid coordinates */ + tdata_hashgrid_teardown (&tdata); /* Unmap the memory and/or deallocate the memory on the heap */ tdata_close (&tdata); diff --git a/config.h b/config.h index 176e183..44890a1 100644 --- a/config.h +++ b/config.h @@ -1,3 +1,8 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and at + * https://github.com/bliksemlabs/rrrr/ + */ + #ifndef _CONFIG_H #define _CONFIG_H @@ -6,43 +11,66 @@ /* Maximum iterations the algorithm will run */ #define RRRR_DEFAULT_MAX_ROUNDS 6 +/* Maximum number of itineraries a plan can store < uint_t */ +#define RRRR_DEFAULT_PLAN_ITIN RRRR_DEFAULT_MAX_ROUNDS * RRRR_DEFAULT_MAX_ROUNDS + /* Walk slack in seconds */ #define RRRR_DEFAULT_WALK_SLACK 0 /* Speed by foot, in meter per second */ #define RRRR_DEFAULT_WALK_SPEED 1.5 +/*Maximum stops to enter or exit a journey */ +#define RRRR_MAX_ENTRY_EXIT_POINTS 2500 + /* Maximum distance in meters to travel by feet from the - * origin to the first stop, and from the last stop to + * origin to the first stop_point, and from the last stop_point to * the destination. */ #define RRRR_DEFAULT_WALK_MAX_DISTANCE 500 #define RRRR_MAX_BANNED_JOURNEY_PATTERNS 1 -#define RRRR_MAX_BANNED_STOPS 1 -#define RRRR_MAX_BANNED_STOPS_HARD 1 +#define RRRR_MAX_BANNED_OPERATORS 1 +#define RRRR_MAX_BANNED_STOP_POINTS 1 +#define RRRR_MAX_BANNED_STOP_POINTS_HARD 1 #define RRRR_MAX_BANNED_VEHICLE_JOURNEYS 1 -#define RRRR_FEATURE_LATLON 1 +#define RRRR_MAX_FILTERED_OPERATORS 1 #define RRRR_WALK_COMP 1.2 -#define RRRR_BANNED_JOURNEY_PATTERNS_BITMASK 0 +#define RRRR_BANNED_JOURNEY_PATTERNS_BITMASK 1 #if RRRR_MAX_BANNED_JOURNEY_PATTERNS == 0 #undef RRRR_BANNED_JOURNEY_PATTERNS_BITMASK #endif +/* Explictly enable MMAP +#define RRRR_TDATA_IO_MMAP 1 +#undef RRRR_TDATA_IO_DYNAMIC +*/ + #if (defined(RRRR_TDATA_IO_MMAP) && defined(RRRR_TDATA_IO_DYNAMIC)) || (!defined(RRRR_TDATA_IO_MMAP) && !defined(RRRR_TDATA_IO_DYNAMIC)) +/* We default to using dynamic allocation */ #define RRRR_TDATA_IO_DYNAMIC 1 -#endif - -#ifndef RRRR_TDATA_IO_MMAP #define RRRR_FEATURE_REALTIME_EXPANDED 1 #define RRRR_FEATURE_REALTIME_ALERTS 1 #define RRRR_FEATURE_REALTIME 1 #define RRRR_DYNAMIC_SLACK 2 + +#else +/* Condering we are using MMAP, realtime is not available */ +#undef RRRR_FEATURE_REALTIME +#undef RRRR_FEATURE_REALTIME_ALERTS +#undef RRRR_FEATURE_REALTIME_EXPANDED +#endif + +#define RRRR_FEATURE_RENDER_TEXT 1 +#define RRRR_FEATURE_RENDER_OTP 1 + +#ifdef RRRR_DEBUG +#define RRRR_DEV #endif /* roughly the length of common prefixes in IDs */ @@ -54,4 +82,4 @@ * could use int indexes into a fixed-size, pre-allocated edge pool. */ -#endif +#endif /* _CONFIG_H */ diff --git a/geometry.c b/geometry.c index 5379cb6..4890fa7 100644 --- a/geometry.c +++ b/geometry.c @@ -1,32 +1,18 @@ -/* Copyright 2013 Bliksem Labs. +/* Copyright 2013-2015 Bliksem Labs B.V. * See the LICENSE file at the top-level directory of this distribution and at * https://github.com/bliksemlabs/rrrr/ */ -#include "config.h" #include "geometry.h" #include "rrrr_types.h" - -#include -#include #include +#ifdef RRRR_DEBUG +#include +#endif -/* Mean of Earth's equatorial and meridional circumferences. */ -#define EARTH_CIRCUMFERENCE 40041438.5 - -/* UINT32_MAX is also the full range of INT32. */ -#define INT32_RANGE UINT32_MAX - -/* We could have more resolution in the latitude direction by mapping - * 90 degrees to the int32 range instead of 180, but keeping both axes at the - * same scale enables efficent distance calculations. In any case the extra - * Y resolution is unnecessary, since 1 brad is already just under 1cm. - */ -#define METERS_PER_BRAD (EARTH_CIRCUMFERENCE / INT32_RANGE) - -/* Must be scaled according to latitude for use in the longitude direction. - */ -#define METERS_PER_DEGREE_LAT (EARTH_CIRCUMFERENCE / 360.0) +double radians (double degrees); +double degrees (double radians); +double latlon_distance_meters (latlon_t *ll1, latlon_t *ll2); double radians (double degrees) { return degrees * M_PI / 180; @@ -79,13 +65,13 @@ static double coord_diff_meters (int32_t o1, int32_t o2) { * 2. Either we start x=0 at lon=-180 or lon=0. */ void coord_from_lat_lon (coord_t *coord, double lat, double lon) { - coord->y = lat * UINT32_MAX / 360.0; - coord->x = lon * UINT32_MAX / 360.0 * xscale_at_lat (lat); + coord->y = (int32_t)(lat * UINT32_MAX / 360.0); + coord->x = (int32_t)(lon * UINT32_MAX / 360.0 * xscale_at_lat (lat)); } void coord_from_meters (coord_t *coord, double meters_x, double meters_y) { - coord->x = meters_x / METERS_PER_BRAD; - coord->y = meters_y / METERS_PER_BRAD; + coord->x = (int32_t)(meters_x / METERS_PER_BRAD); + coord->y = (int32_t)(meters_y / METERS_PER_BRAD); } void coord_from_latlon (coord_t *coord, latlon_t *latlon) { @@ -99,7 +85,7 @@ void coord_from_latlon (coord_t *coord, latlon_t *latlon) { * * TODO: add meters_from_ersatz */ -double coord_distance_ersatz (coord_t *c1, coord_t *c2) { +double coord_distance_ersatz (const coord_t *c1, const coord_t *c2) { double dx = c2->x - c1->x; double dy = c2->y - c1->y; return (dx * dx) + (dy * dy); @@ -113,7 +99,7 @@ double ersatz_from_distance (double meters) { return d_brads * d_brads; } -double coord_distance_meters (coord_t *c1, coord_t *c2) { +double coord_distance_meters (const coord_t *c1, const coord_t *c2) { double dxm = coord_diff_meters(c1->x, c2->x); double dym = coord_diff_meters(c1->y, c2->y); return sqrt((dxm * dxm) + (dym * dym)); @@ -130,9 +116,9 @@ double latlon_distance_meters (latlon_t *ll1, latlon_t *ll2) { return coord_distance_meters (&c1, &c2); } -void latlon_from_coord (latlon_t *latlon, coord_t *coord) { - latlon->lat = coord->y * 180.0f / INT32_MAX ; - latlon->lon = coord->x * 180.0f / INT32_MAX / xscale_at_y (coord->y); +void latlon_from_coord (latlon_t *latlon, const coord_t *coord) { + latlon->lat = (float) (coord->y * 180.0f / INT32_MAX); + latlon->lon = (float) (coord->x * 180.0f / INT32_MAX / xscale_at_y ((uint32_t)coord->y)); } bool strtolatlon (char *latlon, latlon_t *result) { @@ -150,7 +136,7 @@ void latlon_dump (latlon_t *latlon) { fprintf(stderr, "latlon lat=%f lon=%f \n", latlon->lat, latlon->lon); } -void coord_dump (coord_t *coord) { +void coord_dump (const coord_t *coord) { fprintf(stderr, "coordinate x=%d y=%d \n", coord->x, coord->y); } #endif diff --git a/geometry.h b/geometry.h index 24c6858..e711325 100644 --- a/geometry.h +++ b/geometry.h @@ -1,4 +1,4 @@ -/* Copyright 2013 Bliksem Labs. +/* Copyright 2013-2015 Bliksem Labs B.V. * See the LICENSE file at the top-level directory of this distribution and at * https://github.com/bliksemlabs/rrrr/ */ @@ -7,8 +7,22 @@ #define _GEOMETRY_H #include +#include #include +/* Mean of Earth's equatorial and meridional circumferences. */ +#define EARTH_CIRCUMFERENCE 40041438.5 + +/* UINT32_MAX is also the full range of INT32. */ +#define INT32_RANGE UINT32_MAX + +/* We could have more resolution in the latitude direction by mapping + * 90 degrees to the int32 range instead of 180, but keeping both axes at the + * same scale enables efficent distance calculations. In any case the extra + * Y resolution is unnecessary, since 1 brad is already just under 1cm. + */ +#define METERS_PER_BRAD (EARTH_CIRCUMFERENCE / INT32_RANGE) + typedef struct coord coord_t; struct coord { int32_t x; @@ -27,17 +41,19 @@ void coord_from_lat_lon (coord_t*, double lat, double lon); void coord_from_meters (coord_t*, double meters_x, double meters_y); -double coord_distance_meters (coord_t*, coord_t*); +double coord_distance_meters (const coord_t*, const coord_t*); + +double latlon_distance_meters (latlon_t *ll1, latlon_t *ll2); -double coord_distance_ersatz (coord_t *c1, coord_t *c2); +double coord_distance_ersatz (const coord_t *c1, const coord_t *c2); double ersatz_from_distance (double meters); void latlon_dump (latlon_t*); -void latlon_from_coord (latlon_t*, coord_t*); +void latlon_from_coord (latlon_t*, const coord_t*); -void coord_dump (coord_t*); +void coord_dump (const coord_t*); bool strtolatlon (char *latlon, latlon_t *result); diff --git a/gtfs-realtime.proto b/gtfs-realtime.proto index 51e2183..cd3920f 100644 --- a/gtfs-realtime.proto +++ b/gtfs-realtime.proto @@ -11,10 +11,9 @@ // arrival times. // // This protocol is published at: -// http://developers.google.com/transit/gtfs-realtime/ +// https://developers.google.com/transit/gtfs-realtime/ syntax = "proto2"; - option java_package = "com.google.transit.realtime"; package transit_realtime; @@ -30,11 +29,17 @@ package transit_realtime; // items of one specified application; all the other entities will be ignored. // - Polling frequency message FeedMessage { + // Metadata about this feed and feed message. required FeedHeader header = 1; // Contents of the feed. repeated FeedEntity entity = 2; + + // The extensions namespace allows 3rd-party developers to extend the + // GTFS-realtime specification in order to add and evaluate new features and + // modifications to the spec. + extensions 1000 to 1999; } // Metadata about a feed, included in feed messages. @@ -86,6 +91,11 @@ message FeedEntity { optional TripUpdate trip_update = 3; optional VehiclePosition vehicle = 4; optional Alert alert = 5; + + // The extensions namespace allows 3rd-party developers to extend the + // GTFS-realtime specification in order to add and evaluate new features and + // modifications to the spec. + extensions 1000 to 1999; } // @@ -188,8 +198,7 @@ message TripUpdate { // stops, although not necessarily according to the times of the schedule. // At least one of arrival and departure must be provided. If the schedule // for this stop contains both arrival and departure times then so must - // this update. An update with only an arrival, say, where the schedule - // has both, indicates that the trip is terminating early at this stop. + // this update. SCHEDULED = 0; // The stop is skipped, i.e., the vehicle will not stop at this stop. @@ -235,10 +244,30 @@ message TripUpdate { // - stop_sequences 10,... have unknown delay. repeated StopTimeUpdate stop_time_update = 2; - // Moment at which the vehicle's real-time progress was measured. In POSIX + // Moment at which the vehicle's real-time progress was measured. In POSIX // time (i.e., the number of seconds since January 1st 1970 00:00:00 UTC). optional uint64 timestamp = 4; + // The current schedule deviation for the trip. Delay should only be + // specified when the prediction is given relative to some existing schedule + // in GTFS. + // + // Delay (in seconds) can be positive (meaning that the vehicle is late) or + // negative (meaning that the vehicle is ahead of schedule). Delay of 0 + // means that the vehicle is exactly on time. + // + // Delay information in StopTimeUpdates take precedent of trip-level delay + // information, such that trip-level delay is only propagated until the next + // stop along the trip with a StopTimeUpdate delay value specified. + // + // Feed providers are strongly encouraged to provide a TripUpdate.timestamp + // value indicating when the delay value was last updated, in order to + // evaluate the freshness of the data. + // + // NOTE: This field is still experimental, and subject to change. It may be + // formally adopted in the future. + optional int32 delay = 5; + // The extensions namespace allows 3rd-party developers to extend the // GTFS-realtime specification in order to add and evaluate new features and // modifications to the spec. @@ -296,6 +325,43 @@ message VehiclePosition { } optional CongestionLevel congestion_level = 6; + // The degree of passenger occupancy of the vehicle. This field is still + // experimental, and subject to change. It may be formally adopted in the + // future. + enum OccupancyStatus { + // The vehicle is considered empty by most measures, and has few or no + // passengers onboard, but is still accepting passengers. + EMPTY = 0; + + // The vehicle has a relatively large percentage of seats available. + // What percentage of free seats out of the total seats available is to be + // considered large enough to fall into this category is determined at the + // discretion of the producer. + MANY_SEATS_AVAILABLE = 1; + + // The vehicle has a relatively small percentage of seats available. + // What percentage of free seats out of the total seats available is to be + // considered small enough to fall into this category is determined at the + // discretion of the feed producer. + FEW_SEATS_AVAILABLE = 2; + + // The vehicle can currently accommodate only standing passengers. + STANDING_ROOM_ONLY = 3; + + // The vehicle can currently accommodate only standing passengers + // and has limited space for them. + CRUSHED_STANDING_ROOM_ONLY = 4; + + // The vehicle is considered full by most measures, but may still be + // allowing passengers to board. + FULL = 5; + + // The vehicle is not accepting additional passengers. + NOT_ACCEPTING_PASSENGERS = 6; + + } + optional OccupancyStatus occupancy_status = 9; + optional OVapiVehiclePosition ovapi_vehicle_position = 1003; // The extensions namespace allows 3rd-party developers to extend the // GTFS-realtime specification in order to add and evaluate new features @@ -310,7 +376,6 @@ message OVapiVehiclePosition { optional int32 delay = 1; } - // An alert, indicating some sort of incident in the public transit network. message Alert { // Time when the alert should be shown to the user. If missing, the @@ -389,6 +454,11 @@ message TimeRange { // 00:00:00 UTC). // If missing, the interval ends at plus infinity. optional uint64 end = 2; + + // The extensions namespace allows 3rd-party developers to extend the + // GTFS-realtime specification in order to add and evaluate new features and + // modifications to the spec. + extensions 1000 to 1999; } // A position. @@ -412,8 +482,8 @@ message Position { optional float speed = 5; // The extensions namespace allows 3rd-party developers to extend the - // GTFS-realtime specification in order to add and evaluate new features - // and modifications to the spec. + // GTFS-realtime specification in order to add and evaluate new features and + // modifications to the spec. extensions 1000 to 1999; } @@ -428,22 +498,30 @@ message Position { // addition, absolute arrival/departure times must be provided. message TripDescriptor { // The trip_id from the GTFS feed that this selector refers to. - // For non frequency expanded trips, this field is enough to uniquely identify - // the trip. For frequency expanded, start_time and start_date might also be + // For non frequency-based trips, this field is enough to uniquely identify + // the trip. For frequency-based trip, start_time and start_date might also be // necessary. optional string trip_id = 1; // The route_id from the GTFS that this selector refers to. optional string route_id = 5; - // The scheduled start time of this trip instance. - // This field should be given only if the trip is frequency-expanded in the - // GTFS feed. The value must precisely correspond to start_time specified for - // the route in the GTFS feed plus some multiple of headway_secs. - // Format of the field is same as that of GTFS/frequencies.txt/start_time, - // e.g., 11:15:35 or 25:15:35. + // The initially scheduled start time of this trip instance. + // When the trip_id corresponds to a non-frequency-based trip, this field + // should either be omitted or be equal to the value in the GTFS feed. When + // the trip_id correponds to a frequency-based trip, the start_time must be + // specified for trip updates and vehicle positions. If the trip corresponds + // to exact_times=1 GTFS record, then start_time must be some multiple + // (including zero) of headway_secs later than frequencies.txt start_time for + // the corresponding time period. If the trip corresponds to exact_times=0, + // then its start_time may be arbitrary, and is initially expected to be the + // first departure of the trip. Once established, the start_time of this + // frequency-based trip should be considered immutable, even if the first + // departure time changes -- that time change may instead be reflected in a + // StopTimeUpdate. + // Format and semantics of the field is same as that of + // GTFS/frequencies.txt/start_time, e.g., 11:15:35 or 25:15:35. optional string start_time = 2; - // The scheduled start date of this trip instance. // Must be provided to disambiguate trips that are so late as to collide with // a scheduled trip on a next day. For example, for a train that departs 8:00 @@ -476,27 +554,12 @@ message TripDescriptor { // A trip that existed in the schedule but was removed. CANCELED = 3; - // A trip that replaces a portion of static schedule. - // If the trip selector identifies a certain trip instance, then only that - // instance is replaced. If the selector identifies a route, then all the - // trips along that route are replaced. - // - // The replacement applies only to the portion of the trip supplied. For - // instance, consider a route that goes through stops A,B,C,D,E,F, and a - // REPLACEMENT trip provides data for stops A,B,C. Then, the times for stops - // D,E,F are still taken from the static schedule. - // - // A feed might supply several REPLACEMENT trips. In this case, the portion - // of static schedule that is replaced is the union of what is defined by - // all the feeds. Normally, all the REPLACEMENT trips should either - // correspond to the same route or to individual trip instances. - REPLACEMENT = 5; } optional ScheduleRelationship schedule_relationship = 4; // The extensions namespace allows 3rd-party developers to extend the - // GTFS-realtime specification in order to add and evaluate new features - // and modifications to the spec. + // GTFS-realtime specification in order to add and evaluate new features and + // modifications to the spec. extensions 1000 to 1999; } @@ -515,8 +578,8 @@ message VehicleDescriptor { optional string license_plate = 3; // The extensions namespace allows 3rd-party developers to extend the - // GTFS-realtime specification in order to add and evaluate new features - // and modifications to the spec. + // GTFS-realtime specification in order to add and evaluate new features and + // modifications to the spec. extensions 1000 to 1999; } @@ -534,8 +597,8 @@ message EntitySelector { optional string stop_id = 5; // The extensions namespace allows 3rd-party developers to extend the - // GTFS-realtime specification in order to add and evaluate new features - // and modifications to the spec. + // GTFS-realtime specification in order to add and evaluate new features and + // modifications to the spec. extensions 1000 to 1999; } @@ -557,8 +620,17 @@ message TranslatedString { // no i18n is done at all for the feed. At most one translation is // allowed to have an unspecified language tag. optional string language = 2; + + // The extensions namespace allows 3rd-party developers to extend the + // GTFS-realtime specification in order to add and evaluate new features and + // modifications to the spec. + extensions 1000 to 1999; } // At least one translation must be provided. repeated Translation translation = 1; -} + // The extensions namespace allows 3rd-party developers to extend the + // GTFS-realtime specification in order to add and evaluate new features and + // modifications to the spec. + extensions 1000 to 1999; +} diff --git a/hashgrid.c b/hashgrid.c index 413d753..ba70ae3 100644 --- a/hashgrid.c +++ b/hashgrid.c @@ -1,17 +1,14 @@ -/* Copyright 2013 Bliksem Labs. See the LICENSE file at the top-level directory of this distribution and at https://github.com/bliksemlabs/rrrr/. */ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and + * at https://github.com/bliksemlabs/rrrr/ + */ /* hashgrid.c */ #include "hashgrid.h" +#include "util.h" -#include "geometry.h" -#include "tdata.h" -#include "config.h" -#include #include -#include #include -#include -#include /* be sure to link with math library (-lm) */ /* TODO: benchmark the conversion from variable length arrays * to [y * grid_dims + x] @@ -32,7 +29,7 @@ * but you have to make sure overflow is happening (-fwrapv?) */ -static uint32_t xbin (hashgrid_t *hg, coord_t *coord) { +static uint32_t xbin (hashgrid_t *hg, const coord_t *coord) { uint32_t x = (uint32_t) abs(coord->x / (hg->bin_size.x)); x %= hg->grid_dim; #ifdef RRRR_DEBUG_HASHGRID @@ -41,7 +38,7 @@ static uint32_t xbin (hashgrid_t *hg, coord_t *coord) { return x; } -static uint32_t ybin (hashgrid_t *hg, coord_t *coord) { +static uint32_t ybin (hashgrid_t *hg, const coord_t *coord) { uint32_t y = (uint32_t) abs(coord->y / (hg->bin_size.y)); y %= hg->grid_dim; #ifdef RRRR_DEBUG_HASHGRID @@ -105,7 +102,7 @@ uint32_t hashgrid_result_next (hashgrid_result_t *r) { ret_item = r->hg->bins[r->y * r->hg->grid_dim + r->x][r->i]; #ifdef RRRR_DEBUG - printf ("x=%d y=%d i=%d item=%d ", r->x, r->y, r->i, ret_item); + printf ("x=%d y=%d i=%d item=%d\n", r->x, r->y, r->i, ret_item); #endif return ret_item; } @@ -123,7 +120,7 @@ uint32_t hashgrid_result_next (hashgrid_result_t *r) { uint32_t hashgrid_result_next_filtered (hashgrid_result_t *r, double *distance) { uint32_t item; while ((item = hashgrid_result_next(r)) != HASHGRID_NONE) { - coord_t *coord = r->hg->coords + item; + const coord_t *coord = r->hg->coords + item; latlon_t latlon; latlon_from_coord (&latlon, coord); #ifdef RRRR_DEBUG_HASHGRID @@ -151,7 +148,7 @@ uint32_t hashgrid_result_closest (hashgrid_result_t *r) { uint32_t best_item = HASHGRID_NONE; double best_distance = INFINITY; while ((item = hashgrid_result_next(r)) != HASHGRID_NONE) { - coord_t *coord = r->hg->coords + item; + const coord_t *coord = r->hg->coords + item; latlon_t latlon; latlon_from_coord (&latlon, coord); #ifdef RRRR_DEBUG_HASHGRID @@ -172,8 +169,8 @@ uint32_t hashgrid_result_closest (hashgrid_result_t *r) { return best_item; } -void hashgrid_init (hashgrid_t *hg, uint32_t grid_dim, double bin_size_meters, - coord_t *coords, uint32_t n_items) { +bool hashgrid_init (hashgrid_t *hg, uint32_t grid_dim, double bin_size_meters, + const coord_t *coords, uint32_t n_items) { /* Initialize all struct members. */ hg->grid_dim = grid_dim; hg->coords = coords; @@ -184,6 +181,8 @@ void hashgrid_init (hashgrid_t *hg, uint32_t grid_dim, double bin_size_meters, hg->bins = (uint32_t **) malloc(sizeof(uint32_t *) * grid_dim * grid_dim); hg->items = (uint32_t *) malloc(sizeof(uint32_t) * n_items); + if (!hg->counts || !hg->bins || !hg->items) return false; + { /* Initalize all dynamically allocated arrays. */ uint32_t y; @@ -220,6 +219,7 @@ void hashgrid_init (hashgrid_t *hg, uint32_t grid_dim, double bin_size_meters, for (x = 0; x < grid_dim; ++x) { hg->bins[y * grid_dim + x] = bin; bin += hg->counts[y * grid_dim + x]; + bin = MIN(bin, hg->items + n_items - 1); /* Reset bin item count for reuse when filling up bins. */ hg->counts[y * grid_dim + x] = 0; } @@ -232,7 +232,7 @@ void hashgrid_init (hashgrid_t *hg, uint32_t grid_dim, double bin_size_meters, */ uint32_t i_coord; for (i_coord = 0; i_coord < n_items; ++i_coord) { - coord_t *coord = coords + i_coord; + const coord_t *coord = coords + i_coord; uint32_t x = xbin (hg, coord); uint32_t y = ybin (hg, coord); uint32_t i = hg->counts[y * grid_dim + x]; @@ -240,6 +240,8 @@ void hashgrid_init (hashgrid_t *hg, uint32_t grid_dim, double bin_size_meters, hg->counts[y * grid_dim + x] += 1; } } + + return true; } @@ -270,7 +272,7 @@ void hashgrid_dump (hashgrid_t *hg) { } fprintf (stderr, "\n"); } - fprintf (stderr, "total of all counts: %d", total); + fprintf (stderr, "total of all counts: %d\n", total); /* Bins */ for (y = 0; y < hg->grid_dim; ++y) { uint32_t x; diff --git a/hashgrid.h b/hashgrid.h index 32d5870..5944cde 100644 --- a/hashgrid.h +++ b/hashgrid.h @@ -1,8 +1,9 @@ -/* Copyright 2013 Bliksem Labs. - * See the LICENSE file at the top-level directory of this distribution and at - * https://github.com/bliksemlabs/rrrr/ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and + * at https://github.com/bliksemlabs/rrrr/ */ + /* hashgrid.h */ #ifndef _HASHGRID_H @@ -24,7 +25,7 @@ struct hashgrid_s { /* the array of coords that were indexed * note: may have been deallocated by caller */ - coord_t *coords; + const coord_t *coords; /* array containing all the binned items, * aliased by the bins array @@ -64,7 +65,7 @@ struct hashgrid_result_s { bool has_next; }; -void hashgrid_init (hashgrid_t *hg, uint32_t grid_dim, double bin_size_meters, coord_t *coords, uint32_t n_items); +bool hashgrid_init (hashgrid_t *hg, uint32_t grid_dim, double bin_size_meters, const coord_t *coords, uint32_t n_items); void hashgrid_query (hashgrid_t *, hashgrid_result_t *, coord_t, double radius_meters); diff --git a/hashgrid_street_network.c b/hashgrid_street_network.c new file mode 100644 index 0000000..5a23905 --- /dev/null +++ b/hashgrid_street_network.c @@ -0,0 +1,27 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and at + * https://github.com/bliksemlabs/rrrr/ + */ + +#include "street_network.h" + +bool street_network_stoppoint_durations(latlon_t *latlon, float walk_speed, uint16_t max_walk_distance, tdata_t *tdata, street_network_t *sn){ + coord_t coord; + double distance; + uint32_t sp_index; + hashgrid_result_t hg_result; + coord_from_latlon(&coord, latlon); + sn->n_points = 0; + hashgrid_query (&tdata->hg, &hg_result, coord, max_walk_distance); + hashgrid_result_reset(&hg_result); + + sp_index = hashgrid_result_next_filtered(&hg_result, &distance); + while (sp_index != HASHGRID_NONE) { + rtime_t duration = SEC_TO_RTIME((uint32_t)((distance * RRRR_WALK_COMP) / + walk_speed)); + street_network_mark_duration_to_stop_point(sn, (spidx_t) sp_index, duration); + /* get the next potential start stop_point */ + sp_index = hashgrid_result_next_filtered(&hg_result, &distance); + } + return sn->n_points > 0; +} diff --git a/index_journey_pattern.h b/index_journey_pattern.h new file mode 100644 index 0000000..e69de29 diff --git a/index_journey_pattern_points.c b/index_journey_pattern_points.c new file mode 100644 index 0000000..b28db80 --- /dev/null +++ b/index_journey_pattern_points.c @@ -0,0 +1,124 @@ +#include "index_journey_pattern_points.h" +#include + +/* To create the journey patterns at stop list we know + * that the list equals the length of all journeypatterns + * by ordering the stopid, journeypatternid, we can dedup + * in a second round. + */ + +typedef struct jp_s_index jp_s_index_t; +struct jp_s_index { + jpidx_t j; + spidx_t s; +}; + +static int +jp_s_cmp(const void *elem1, const void *elem2) { + const jp_s_index_t *i1 = (const jp_s_index_t *) elem1; + const jp_s_index_t *i2 = (const jp_s_index_t *) elem2; + + return ((i1->s - i2->s) << 16) + (i1->j - i2->j); +} + +static jp_s_index_t* +journey_pattern_stop_index_ordered (const tdata_t *td, uint32_t *n) { + jp_s_index_t *idx; + uint32_t n_idx; + jpidx_t jp_index; + + /* allocate the slackspace to compute the index */ + idx = malloc(td->n_journey_pattern_points * sizeof(jp_s_index_t)); + if (!idx) return NULL; + + /* copy the journey pattern points from the timetable */ + n_idx = 0; + for (jp_index = 0; jp_index < td->n_journey_patterns; ++jp_index) { + uint32_t jpp_index; + journey_pattern_t *jp = td->journey_patterns + jp_index; + + for (jpp_index = jp->journey_pattern_point_offset; + jpp_index < (jp->journey_pattern_point_offset + jp->n_stops); + jpp_index++) { + idx[n_idx].j = jp_index; + idx[n_idx].s = td->journey_pattern_points[jpp_index]; + n_idx++; + } + } + + /* sort the journey pattern points in sp_idx, jp_idx order */ + qsort (idx, n_idx, sizeof(jp_s_index_t), jp_s_cmp); + + *n = n_idx; + return idx; +} + +bool index_journey_pattern_points (const tdata_t *td, jpidx_t **jps, uint32_t *n, uint32_t **jpaspo) { + jp_s_index_t *idx = NULL; + jpidx_t *journey_patterns_at_stop = NULL; + uint32_t *journey_patterns_at_stop_point_offset = NULL; + uint32_t i, i2, n_idx, n_journey_patterns_at_stop; + spidx_t sp_index; + + idx = journey_pattern_stop_index_ordered (td, &n_idx); + if (!idx) return false; + + /* compute the length of the final allocation */ + /* TODO: validate the 0 case: no journey patterns */ + n_journey_patterns_at_stop = 1; + for (i = 1; i < n_idx; i++) { + n_journey_patterns_at_stop += (idx[i - 1].s != idx[i].s) || (idx[i - 1].j != idx[i].j); + } + + #if 0 + printf ("%u %u\n", td->n_journey_patterns_at_stop, td->n_stop_points); + #endif + + /* allocate the final index */ + journey_patterns_at_stop = (jpidx_t *) malloc (sizeof (jpidx_t) * n_journey_patterns_at_stop); + if (!journey_patterns_at_stop) goto failure; + + /* allocate the new stop_point_offset */ + journey_patterns_at_stop_point_offset = (uint32_t *) malloc (sizeof (uint32_t) * td->n_stop_points); + if (!journey_patterns_at_stop_point_offset) goto failure; + + i = 0; + i2 = 0; + journey_patterns_at_stop[i2] = idx[i].j; + /* we fill the stop points we have no data for with next available data */ + for (sp_index = 0; sp_index <= idx[0].s; sp_index++) journey_patterns_at_stop_point_offset[sp_index] = i2; + i2++; + i++; + + /* What does this loop do? + * i is our the index for out unduplicated list. + * i2 is our deduplicated index for the final list + */ + for (; i < n_idx; i++) { + if (idx[i - 1].s != idx[i].s) { + journey_patterns_at_stop[i2] = idx[i].j; + for (; sp_index <= idx[i].s; sp_index++) journey_patterns_at_stop_point_offset[sp_index] = i2; + i2++; + } else if (idx[i - 1].j != idx[i].j) { + journey_patterns_at_stop[i2] = idx[i].j; + i2++; + } + } + + /* our list is done, fill up remaining stops in the list with the highest + * jp_index found, this makes the length of the list zero */ + for (; sp_index <= td->n_stop_points; sp_index++) journey_patterns_at_stop_point_offset[sp_index] = i2; + + *jps = journey_patterns_at_stop; + *n = n_journey_patterns_at_stop; + *jpaspo = journey_patterns_at_stop_point_offset; + + free (idx); + return true; + +failure: + free (idx); + free (journey_patterns_at_stop); + free (journey_patterns_at_stop_point_offset); + return false; +} diff --git a/index_journey_pattern_points.h b/index_journey_pattern_points.h new file mode 100644 index 0000000..75d7458 --- /dev/null +++ b/index_journey_pattern_points.h @@ -0,0 +1,4 @@ +#include "rrrr_types.h" +#include "tdata.h" + +bool index_journey_pattern_points (const tdata_t *td, jpidx_t **jps, uint32_t *n, uint32_t **jpaspo); diff --git a/index_journey_patterns.c b/index_journey_patterns.c new file mode 100644 index 0000000..731f5bd --- /dev/null +++ b/index_journey_patterns.c @@ -0,0 +1,58 @@ +#include "index_journey_patterns.h" +#include "index_vehicle_journeys.h" +#include + +bool index_journey_patterns (const tdata_t *td, calendar_t **jp_active, + rtime_t **jp_min, rtime_t **jp_max, + rtime_t *td_max) { + calendar_t *active = NULL; + rtime_t *min = NULL, *max = NULL; + rtime_t max_time = 0; + jpidx_t i; + + active = (calendar_t *) malloc (sizeof(calendar_t) * td->n_journey_patterns); + min = (rtime_t *) malloc (sizeof(rtime_t) * td->n_journey_patterns); + max = (rtime_t *) malloc (sizeof(rtime_t) * td->n_journey_patterns); + if (!active || !min || !max) goto failure; + + i = (jpidx_t) td->n_journey_patterns; + + while (i) { + i--; + index_vehicle_journeys (td, i, active, min, max); + if (max_time < max[i]) { + max_time = max[i]; + } + } + + *jp_active = active; + *jp_min = min; + *jp_max = max; + *td_max = max_time; + + return true; + +failure: + free(active); + free(min); + free(max); + return false; +} + +bool index_max_time (const tdata_t *td, rtime_t *td_max) { + jpidx_t i = (jpidx_t) td->n_journey_patterns; + rtime_t max_time = 0; + + if (td->journey_pattern_max == NULL) return false; + + while (i) { + i--; + if (max_time < td->journey_pattern_max[i]) { + max_time = td->journey_pattern_max[i]; + } + } + + *td_max = max_time; + + return true; +} diff --git a/index_journey_patterns.h b/index_journey_patterns.h new file mode 100644 index 0000000..295d3c4 --- /dev/null +++ b/index_journey_patterns.h @@ -0,0 +1,13 @@ +#include "rrrr_types.h" +#include "tdata.h" + +/* Returns the minimum and maximum rtime_t per journey pattern, + * and the global maximum rtime_t. + */ +bool index_journey_patterns (const tdata_t *td, calendar_t **jp_active, + rtime_t **jp_min, rtime_t **jp_max, + rtime_t *td_max); + +/* Returns the global maximum rtime_t. + */ +bool index_max_time (const tdata_t *td, rtime_t *td_max); diff --git a/index_vehicle_journeys.c b/index_vehicle_journeys.c new file mode 100644 index 0000000..abb4618 --- /dev/null +++ b/index_vehicle_journeys.c @@ -0,0 +1,27 @@ +#include "index_vehicle_journeys.h" + +void index_vehicle_journeys (const tdata_t *td, uint32_t i, calendar_t *active, rtime_t *min, rtime_t *max) { + journey_pattern_t *jp = &td->journey_patterns[i]; + calendar_t mask = 0; + vjidx_t v1, v2; + + v1 = jp->vj_index; + v2 = v1 + jp->n_vjs; + + /* this assumes we have a sorted list of vehicle journeys */ + min[i] = td->vjs[v1].begin_time; + max[i] = td->vjs[v2 - 1].begin_time + td->stop_times[td->vjs[v2 - 1].stop_times_offset + jp->n_stops - 1].departure; + + while (v2 > v1) { + rtime_t new_max; + v2--; + mask |= td->vj_active[v2]; + new_max = td->vjs[v2].begin_time + td->stop_times[td->vjs[v2].stop_times_offset + jp->n_stops - 1].departure; + + /* it seems that the source data isn't sorted, or realtime is applied */ + if (min[i] > td->vjs[v1].begin_time) min[i] = td->vjs[v1].begin_time; + if (max[i] < new_max) max[i] = new_max; + } + + active[i] = mask; +} diff --git a/index_vehicle_journeys.h b/index_vehicle_journeys.h new file mode 100644 index 0000000..2894ee4 --- /dev/null +++ b/index_vehicle_journeys.h @@ -0,0 +1,4 @@ +#include "rrrr_types.h" +#include "tdata.h" + +void index_vehicle_journeys (const tdata_t *td, uint32_t i, calendar_t *active, rtime_t *min, rtime_t *max); diff --git a/json.c b/json.c new file mode 100644 index 0000000..181ef05 --- /dev/null +++ b/json.c @@ -0,0 +1,197 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and + * at https://github.com/bliksemlabs/rrrr/ + */ + +/* json.c : helper functions to build a JSON document */ + +#include "json.h" +#include + +/* private functions */ + +/* Check an operation that will write multiple characters to the buffer, + * when the maximum number of characters is known. + */ +static bool remaining(json_t *j, size_t n) { + if (j->b + n < j->buf_end) return true; + j->overflowed = true; + return false; +} + +/* Overflow-checked copy of a single char to the buffer. + */ +static void check(json_t *j, char c) { + if (j->b >= j->buf_end) j->overflowed = true; + else *(j->b++) = c; +} + +/* Add a comma to the buffer, but only if we are currently in a list. + */ +static void comma(json_t *j) { + if (j->in_list) check(j, ','); +} + +/* Write a string out to the buffer, surrounding it in quotes and + * escaping all quotes or slashes. + */ +static void string (json_t *j, const char *s) { + const char *c; + + if (s == NULL) { + if (remaining(j, 4)) j->b += sprintf(j->b, "null"); + return; + } + check(j, '"'); + for (c = s; *c != '\0'; ++c) { + switch (*c) { + case '\\' : + check(j, '\\'); + check(j, '\\'); + break; + case '\b' : + check(j, '\\'); + check(j, 'b'); + break; + case '\f' : + check(j, '\\'); + check(j, 'f'); + break; + case '\n' : + check(j, '\\'); + check(j, 'n'); + break; + case '\r' : + check(j, '\\'); + check(j, 'r'); + break; + case '\t' : + check(j, '\\'); + check(j, 't'); + break; + case '\v' : + check(j, '\\'); + check(j, 'v'); + break; + case '"' : + check(j, '\\'); + check(j, '"'); + break; + default: + check(j, *c); + } + } + check(j, '"'); +} + +/* Escape a key and copy it to the buffer, preparing for a single value. + * This should only be used internally, since it sets in_list _before_ + * the value is added. + */ +static void ekey (json_t *j, const char *k) { + comma(j); + if (k) { + string(j, k); + check(j, ':'); + } + j->in_list = true; +} + + +/* public functions (eventually) */ + +/* Initialise the struct to store the JSON document. + */ +void json_init (json_t *j, char *buf, size_t buflen) { + j->buf_start = j->b = buf; + j->buf_end = j->b + buflen - 1; + j->in_list = false; + j->overflowed = false; +} + +/* Finish the JSON document + */ +void json_end (json_t *j) { + *j->b = '\0'; +} + +/* Add a string value to the JSON document. + */ +void json_kv(json_t *j, const char *key, const char *value) { + ekey(j, key); + string(j, value); +} + +/* Add a signed integer value to the JSON document. + */ +void json_kd(json_t *j, const char *key, int value) { + ekey(j, key); + if (remaining(j, 11)) j->b += sprintf(j->b, "%d", value); +} + +/* Add a floating point value to the JSON document. + */ +void json_kf(json_t *j, const char *key, double value) { + ekey(j, key); + if (remaining(j, 12)) j->b += sprintf(j->b, "%5.5f", value); +} + +/* Add a long signed integer to the JSON document. + */ +void json_kl(json_t *j, const char *key, int64_t value) { + ekey(j, key); + if (remaining(j, 21)) j->b += sprintf(j->b, "%" PRId64 , value); +} + +/* Add a boolean value to the JSON document. + */ +void json_kb(json_t *j, const char *key, bool value) { + ekey(j, key); + if (remaining(j, 5)) j->b += sprintf(j->b, value ? "true" : "false"); +} + +/* Start a new object inside the JSON document. + */ +void json_key_obj(json_t *j, const char *key) { + ekey(j, key); + check(j, '{'); + j->in_list = false; +} + +/* Start a new array inside the JSON document. + */ +void json_key_arr(json_t *j, const char *key) { + ekey(j, key); + check(j, '['); + j->in_list = false; +} + +/* Close an object in the JSON document. + */ +void json_end_obj(json_t *j) { + check(j, '}'); + j->in_list = true; +} + +/* Close an array in the JSON document. + */ +void json_end_arr(json_t *j) { + check(j, ']'); + j->in_list = true; +} + +/* Return the length in bytes of the JSON document. + */ +size_t json_length(json_t *j) { + return (size_t) (j->b - j->buf_start); +} + +#ifdef RRRR_DEBUG +/* Dump the current JSON document for debugging + */ +void json_dump(json_t *j) { + *j->b = '\0'; + if (j->overflowed) fprintf (stderr, "[JSON OVERFLOW]\n"); + fprintf(stderr, "%s\n", j->buf_start); +} +#endif diff --git a/json.h b/json.h new file mode 100644 index 0000000..959a621 --- /dev/null +++ b/json.h @@ -0,0 +1,42 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and + * at https://github.com/bliksemlabs/rrrr/ + */ + +#include +#include +#include + +typedef struct json json_t; +struct json { + char *buf_start; + char *buf_end; + char *b; + bool overflowed; + bool in_list; +}; + +void json_init (json_t *j, char *buf, size_t buflen); +void json_end (json_t *j); +void json_kv(json_t *j, const char *key, const char *value); +void json_kd(json_t *j, const char *key, int value); +void json_kf(json_t *j, const char *key, double value); +void json_kl(json_t *j, const char *key, int64_t value); +void json_kb(json_t *j, const char *key, bool value); +void json_key_obj(json_t *j, const char *key); +void json_key_arr(json_t *j, const char *key); +void json_end_obj(json_t *j); +void json_end_arr(json_t *j); +size_t json_length(json_t *j); + +#define json_v(j, v); json_kv(j, NULL, v); +#define json_d(j, d); json_kd(j, NULL, d); +#define json_f(j, f); json_kf(j, NULL, f); +#define json_l(j, l); json_kl(j, NULL, l); +#define json_b(j, b); json_kb(j, NULL, b); +#define json_obj(j); json_key_obj(j, NULL); +#define json_arr(j); json_key_arr(j, NULL); + +#ifdef RRRR_DEBUG +void json_dump(json_t *j); +#endif diff --git a/linkedlist.c b/linkedlist.c index ed2d910..fb750d7 100644 --- a/linkedlist.c +++ b/linkedlist.c @@ -1,15 +1,12 @@ -/* Copyright 2013 Bliksem Labs. - * See the LICENSE file at the top-level directory of this distribution and at - * https://github.com/bliksemlabs/rrrr/ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and + * at https://github.com/bliksemlabs/rrrr/ */ /* linkedlist.c */ #include "linkedlist.h" -#include -#include - void linkedlist_init (linkedlist_t *list) { list->head = NULL; list->tail = NULL; diff --git a/linkedlist.h b/linkedlist.h index 22ea929..f5ebd03 100644 --- a/linkedlist.h +++ b/linkedlist.h @@ -1,6 +1,6 @@ -/* Copyright 2013 Bliksem Labs. - * See the LICENSE file at the top-level directory of this distribution and at - * https://github.com/bliksemlabs/rrrr/ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and + * at https://github.com/bliksemlabs/rrrr/ */ /* linkedlist.h */ @@ -18,3 +18,10 @@ typedef struct { listnode_t *tail; uint32_t size; } linkedlist_t; + +void linkedlist_init (linkedlist_t *list); +linkedlist_t *linkedlist_new (void); +void linkedlist_destroy (linkedlist_t **list); +void linkedlist_push (linkedlist_t *list, void *payload); +void linkedlist_enqueue (linkedlist_t *list, void *payload); +void *linkedlist_pop (linkedlist_t *list); diff --git a/plan.c b/plan.c new file mode 100644 index 0000000..3a216b4 --- /dev/null +++ b/plan.c @@ -0,0 +1,55 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and at + * https://github.com/bliksemlabs/rrrr/ + */ + +#include "plan.h" + +bool itinerary_init (itinerary_t *itin) { + itin->n_legs = 0; + itin->n_rides = 0; + + return true; +} + +bool plan_init (plan_t *plan) { + plan->n_itineraries = 0; + + return true; +} + +static int +compareItineraries(const void *elem1, const void *elem2) { + const itinerary_t *i1 = (const itinerary_t *) elem1; + const itinerary_t *i2 = (const itinerary_t *) elem2; + + return ((i1->legs[0].t0 - i2->legs[0].t0) << 16) + + i1->legs[i1->n_legs - 1].t1 - i2->legs[i2->n_legs - 1].t1; +} + +void plan_sort (plan_t *plan) { + qsort(&plan->itineraries, plan->n_itineraries, + sizeof(itinerary_t), compareItineraries); +} + +bool plan_get_operators (plan_t *plan, tdata_t *td, bitset_t *bs) { + uint8_t n_itineraries; + n_itineraries = plan->n_itineraries; + + while (n_itineraries) { + uint8_t n_legs; + --n_itineraries; + n_legs = plan->itineraries[n_itineraries].n_legs; + + while (n_legs) { + jpidx_t jp; + --n_legs; + jp = plan->itineraries[n_itineraries].legs[n_legs].journey_pattern; + if (jp < WALK) { + bitset_set (bs, tdata_operator_idx_for_journey_pattern(td, jp)); + } + } + } + + return true; +} diff --git a/plan.h b/plan.h new file mode 100644 index 0000000..335c947 --- /dev/null +++ b/plan.h @@ -0,0 +1,82 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and at + * https://github.com/bliksemlabs/rrrr/ + */ + +#ifndef _PLAN_H +#define _PLAN_H + +#include "rrrr_types.h" +#include "tdata.h" +#include "bitset.h" + +#include + +#define MAX_LEGS RRRR_DEFAULT_MAX_ROUNDS * 4 + 1 + +/* A leg represents one ride or walking transfer. */ +typedef struct leg leg_t; +struct leg { + /* vj index */ + jp_vjoffset_t vj; + + /* journey_pattern index */ + jpidx_t journey_pattern; + + /* from stop_point index */ + spidx_t sp_from; + + /* to stop_point index */ + spidx_t sp_to; + + /* start time */ + rtime_t t0; + + /* end time */ + rtime_t t1; + + #ifdef RRRR_FEATURE_REALTIME_EXPANDED + /* Serviceday of the vehicle_journey */ + calendar_t cal_day; + #endif + + #ifdef RRRR_FEATURE_REALTIME + /* start journey_pattern_point index */ + uint16_t jpp0; + + /* end journey_pattern_point index */ + uint16_t jpp1; + + /* start delay */ + int16_t d0; + + /* end delay */ + int16_t d1; + #endif +}; + +/* An itinerary is a chain of legs leading from one place to another. */ +typedef struct itinerary itinerary_t; +struct itinerary { + leg_t legs[MAX_LEGS]; + uint8_t n_rides; + uint8_t n_legs; +}; + +/* A plan is several pareto-optimal itineraries connecting the same two stops. */ +typedef struct plan plan_t; +struct plan { + itinerary_t itineraries[RRRR_DEFAULT_PLAN_ITIN]; + router_request_t req; + uint8_t n_itineraries; +}; + +bool itinerary_init (itinerary_t *itin); + +bool plan_init (plan_t *plan); + +void plan_sort (plan_t *plan); + +bool plan_get_operators (plan_t *plan, tdata_t *td, bitset_t *bs); + +#endif /* _PLAN_H */ diff --git a/plan_render_otp.c b/plan_render_otp.c new file mode 100644 index 0000000..11dc012 --- /dev/null +++ b/plan_render_otp.c @@ -0,0 +1,543 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and at + * https://github.com/bliksemlabs/rrrr/ + */ + +#include "config.h" + +#ifdef RRRR_FEATURE_RENDER_OTP + +/* plan_render_otp.c renders plan structs to a json-variant of the API output of the OpenTripPlanner project */ +#include "json.h" +#include "util.h" +#include "polyline.h" +#include "plan_render_otp.h" +#include "router_request.h" +#include + +static char * +modes_string (tmode_t m, char *dst) { + if ((m & m_tram) == m_tram) { dst = strncpy(dst, "TRAM,", 5); dst += 5; } + if ((m & m_subway) == m_subway) { dst = strncpy(dst, "SUBWAY,", 7); dst += 7; } + if ((m & m_rail) == m_rail) { dst = strncpy(dst, "RAIL,", 5); dst += 5; } + if ((m & m_bus) == m_bus) { dst = strncpy(dst, "BUS,", 4); dst += 4; } + if ((m & m_ferry) == m_ferry) { dst = strncpy(dst, "FERRY,", 6); dst += 6; } + if ((m & m_cablecar) == m_cablecar) { dst = strncpy(dst, "CABLE_CAR,", 10); dst += 10; } + if ((m & m_gondola) == m_gondola) { dst = strncpy(dst, "GONDOLA,", 8); dst += 8; } + if ((m & m_funicular) == m_funicular) { dst = strncpy(dst, "FUNICULAR,", 10); dst += 10; } + return dst; +} + +/* Produces a polyline connecting a subset of the stops in a route, + * or connecting two walk path endpoints if route_idx == WALK. + * sidx0 and sidx1 are global stop indexes, not stop indexes within the route. + */ +static void +polyline_for_leg (polyline_t *pl, tdata_t *tdata, leg_t *leg, router_request_t *req, uint8_t leg_idx) { + polyline_begin(pl); + + if (leg->journey_pattern == STREET && leg_idx == 0){ + if (req->from_latlon.lat != 0.0 && req->from_latlon.lon != 0.0){ + polyline_latlon(pl, req->from_latlon); + }else if (req->from_stop_area != STOP_NONE){ + polyline_latlon (pl, tdata->stop_area_coords[req->from_stop_area]); + } + polyline_latlon (pl, tdata->stop_point_coords[leg->sp_to]); + }else if (leg->journey_pattern == STREET) { + polyline_latlon (pl, tdata->stop_point_coords[leg->sp_from]); + if (req->to_latlon.lat != 0.0 && req->to_latlon.lon != 0.0){ + polyline_latlon(pl, req->to_latlon); + }else if (req->to_stop_area != STOP_NONE){ + polyline_latlon (pl, tdata->stop_area_coords[req->to_stop_area]); + } + }else if (leg->journey_pattern == WALK || leg->journey_pattern == STAY_ON) { + polyline_latlon (pl, tdata->stop_point_coords[leg->sp_from]); + polyline_latlon (pl, tdata->stop_point_coords[leg->sp_to]); + } else { + jppidx_t i_jpp; + journey_pattern_t jp = tdata->journey_patterns[leg->journey_pattern]; + spidx_t *stops = tdata_points_for_journey_pattern (tdata, leg->journey_pattern); + bool output = false; + for (i_jpp = 0; i_jpp < jp.n_stops; ++i_jpp) { + spidx_t sidx = stops[i_jpp]; + if (!output && (sidx == leg->sp_from)) output = true; + if (output) polyline_latlon (pl, tdata->stop_point_coords[sidx]); + if (sidx == leg->sp_to) break; + } + } + /* printf ("final polyline: %s\n\n", polyline_result ()); */ +} + +static int64_t +rtime_to_msec(rtime_t rtime, time_t date, int32_t tdata_utc_offset) { + return ((int64_t) 1000) * (RTIME_TO_SEC_SIGNED(rtime - RTIME_ONE_DAY) + date - tdata_utc_offset); +} + +static void +json_place (json_t *j, const char *key, rtime_t arrival, rtime_t departure, + spidx_t stop_index, tdata_t *tdata, time_t date) { + const char *stop_name = tdata_stop_point_name_for_index(tdata, stop_index); + const char *platformcode = tdata_platformcode_for_index(tdata, stop_index); + const char *stop_id = tdata_stop_point_id_for_index(tdata, stop_index); + uint8_t *stop_attr = tdata_stop_point_attributes_for_index(tdata, stop_index); + latlon_t coords = tdata->stop_point_coords[stop_index]; + json_key_obj(j, key); + json_kv(j, "name", stop_name); + json_key_obj(j, "stopId"); + json_kv(j, "agencyId", "NL"); + json_kv(j, "id", stop_id); + json_end_obj(j); + json_kv(j, "stopCode", NULL); /* eventually fill it with UserStopCode */ + json_kv(j, "platformCode", platformcode); + json_kf(j, "lat", coords.lat); + json_kf(j, "lon", coords.lon); + json_kv(j, "wheelchairBoarding", (*stop_attr & sa_wheelchair_boarding) ? "true" : NULL); + json_kv(j, "visualAccessible", (*stop_attr & sa_visual_accessible) ? "true" : NULL); + if (arrival == UNREACHED) { + json_kv(j, "arrival", NULL); + } else { + json_kl(j, "arrival", rtime_to_msec(arrival, date, tdata->utc_offset)); + } + + if (departure == UNREACHED) { + json_kv(j, "departure", NULL); + } else { + json_kl(j, "departure", rtime_to_msec(departure, date, tdata->utc_offset)); + } + json_end_obj(j); +} + +static void +json_place_area (json_t *j, const char *key, rtime_t arrival, rtime_t departure, + spidx_t stop_area_index, tdata_t *tdata, time_t date) { + const char *stop_area_name = tdata_stop_area_name_for_index(tdata, stop_area_index); + const char *stop_area_id = tdata_stop_area_id_for_index(tdata, stop_area_index); + latlon_t coords = tdata->stop_area_coords[stop_area_index]; + json_key_obj(j, key); + json_kv(j, "name", stop_area_name); + json_key_obj(j, "stopId"); + json_kv(j, "agencyId", "NL"); + json_kv(j, "id", stop_area_id); + json_end_obj(j); + json_kv(j, "stopCode", NULL); /* eventually fill it with UserStopCode */ + json_kv(j, "platformCode", NULL); + json_kf(j, "lat", coords.lat); + json_kf(j, "lon", coords.lon); + json_kv(j, "wheelchairBoarding", NULL); + json_kv(j, "visualAccessible", NULL); + if (arrival == UNREACHED) { + json_kv(j, "arrival", NULL); + } else { + json_kl(j, "arrival", rtime_to_msec(arrival, date, tdata->utc_offset)); + } + + if (departure == UNREACHED) { + json_kv(j, "departure", NULL); + } else { + json_kl(j, "departure", rtime_to_msec(departure, date, tdata->utc_offset)); + } + json_end_obj(j); +} + +static void put_servicedate(leg_t *leg, time_t date, tdata_t *tdata, char *servicedate){ + struct tm ltm; + #ifdef RRRR_FEATURE_REALTIME_EXPANDED + time_t servicedate_time = (time_t) tdata->calendar_start_time + (SEC_IN_ONE_DAY * leg->cal_day); + UNUSED(date); + #else + time_t servicedate_time = date + RTIME_TO_SEC(leg->t0 % RTIME_ONE_DAY); + UNUSED(tdata); + #endif + rrrr_gmtime_r(&servicedate_time, <m); + strftime(servicedate, 9, "%Y%m%d", <m); +} + +static void +json_leg (json_t *j, leg_t *leg, tdata_t *tdata, + router_request_t *req, time_t date, uint8_t leg_idx, bool interlineWithPreviousLeg) { + const char *mode = NULL; + const char *headsign = NULL; + const char *linecode = NULL; + const char *line_color = NULL; + const char *line_color_text = NULL; + const char *linename = NULL; + const char *commercialmode = NULL; + const char *line_id = NULL; + const char *vj_id = NULL; + vj_attribute_mask_t vj_attributes = 0; + const char *wheelchair_accessible = NULL; + const char *operator_id = NULL; + const char *operator_name = NULL; + const char *operator_url = NULL; + char agencyTzOffset[16] = "\0"; + + char servicedate[9] = "\0"; + int64_t departuredelay = 0; + + int64_t starttime = rtime_to_msec(leg->t0, date, tdata->utc_offset); + int64_t endtime = rtime_to_msec(leg->t1, date, tdata->utc_offset); + + polyline_t pl; + + if (leg->journey_pattern >= WALK) mode = "WALK"; else { + put_servicedate(leg, date, tdata, servicedate); + + #ifdef RRRR_FEATURE_REALTIME_EXPANDED + headsign = tdata_headsign_for_journey_pattern_point(tdata, leg->journey_pattern,leg->jpp0); + #else + headsign = tdata_headsign_for_journey_pattern(tdata, leg->journey_pattern); + #endif + linecode = tdata_line_code_for_journey_pattern(tdata, leg->journey_pattern); + line_color = tdata_line_color_for_journey_pattern(tdata, leg->journey_pattern); + line_color_text = tdata_line_color_text_for_journey_pattern(tdata, leg->journey_pattern); + linename = tdata_line_name_for_journey_pattern(tdata, leg->journey_pattern); + commercialmode = tdata_commercial_mode_name_for_journey_pattern(tdata, leg->journey_pattern); + line_id = tdata_line_id_for_journey_pattern(tdata, leg->journey_pattern); + operator_id = tdata_operator_id_for_journey_pattern(tdata, leg->journey_pattern); + operator_name = tdata_operator_name_for_journey_pattern(tdata, leg->journey_pattern); + operator_url = tdata_operator_url_for_journey_pattern(tdata, leg->journey_pattern); + vj_id = tdata_vehicle_journey_id_for_jp_vj_offset(tdata, leg->journey_pattern, leg->vj); + vj_attributes = tdata->vjs[leg->vj].vj_attributes; + sprintf(agencyTzOffset,"%d",tdata_utc_offset_for_jp_vj_offset(tdata, leg->journey_pattern, leg->vj)*1000); + + /* departuredelay = tdata_delay_min (tdata, leg->journey_pattern, leg->vj); */ + + wheelchair_accessible = (vj_attributes & vja_wheelchair_accessible) ? "true" : NULL; + if ((tdata->journey_patterns[leg->journey_pattern].attributes & m_tram) == m_tram) mode = "TRAM"; else + if ((tdata->journey_patterns[leg->journey_pattern].attributes & m_subway) == m_subway) mode = "SUBWAY"; else + if ((tdata->journey_patterns[leg->journey_pattern].attributes & m_rail) == m_rail) mode = "RAIL"; else + if ((tdata->journey_patterns[leg->journey_pattern].attributes & m_bus) == m_bus) mode = "BUS"; else + if ((tdata->journey_patterns[leg->journey_pattern].attributes & m_ferry) == m_ferry) mode = "FERRY"; else + if ((tdata->journey_patterns[leg->journey_pattern].attributes & m_cablecar) == m_cablecar) mode = "CABLE_CAR"; else + if ((tdata->journey_patterns[leg->journey_pattern].attributes & m_gondola) == m_gondola) mode = "GONDOLA"; else + if ((tdata->journey_patterns[leg->journey_pattern].attributes & m_funicular) == m_funicular) mode = "FUNICULAR"; else + mode = "INVALID"; + } + + json_obj(j); /* one leg */ + /* TODO We should have stop arrival/departure here */ + json_place(j, "from", UNREACHED, leg->t0, leg->sp_from, tdata, date); + json_place(j, "to", leg->t1, UNREACHED, leg->sp_to, tdata, date); + + json_kv(j, "mode", mode); + json_kl(j, "startTime", starttime); + json_kl(j, "endTime", endtime); + json_kl(j, "departureDelay", departuredelay); + json_kl(j, "arrivalDelay", 0); + json_kv(j, "route", linecode && strcmp(linecode,"") ? linename : linecode); + json_kv(j, "routeShortName", linecode); + json_kv(j, "routeLongName", linename); + json_kv(j, "routeId", line_id); + json_kv(j, "routeColor", line_color); + json_kv(j, "routeTextColor", line_color_text); + json_kv(j, "headsign", headsign); + json_kv(j, "tripId", vj_id); + json_kv(j, "serviceDate", servicedate); + json_kv(j, "agencyId", operator_id); + json_kv(j, "agencyName", operator_name); + json_kv(j, "agencyUrl", operator_url); + if (interlineWithPreviousLeg){ + json_kv(j, "interlineWithPreviousLeg", "true"); + } + if (leg->journey_pattern < WALK){ + json_kv(j, "agencyTimeZoneOffset", agencyTzOffset); + } + json_kv(j, "wheelchairAccessible", wheelchair_accessible); + json_kv(j, "productCategory", commercialmode); +/* + "realTime": false, + "distance": 2656.2383456335, + "mode": "BUS", + "route": "39", + "agencyName": "RET", + "agencyUrl": "http:\/\/www.ret.nl", + "agencyTimeZoneOffset": 7200000, + "routeColor": null, + "routeType": 3, + "routeId": "1836", + "routeTextColor": null, + "interlineWithPreviousLeg": false, + "tripShortName": "48562", + "tripBlockId": null, + "headsign": "Rotterdam Centraal", + "agencyId": "RET", + "tripId": "2597372", + "serviceDate": "20130819", + "from": { + "name": "Rotterdam, Nieuwe Crooswijksewe", + "stopId": { + "agencyId": "ARR", + "id": "52272" + }, + "stopCode": "HA2286", + "platformCode": null, + "lon": 4.49654, + "lat": 51.934423, + "arrival": 1376897759000, + "departure": 1376897760000, + "orig": null, + "zoneId": null, + "stopIndex": 3 + }, + "to": { + "name": "Rotterdam, Rotterdam Centraal", + "stopId": { + "agencyId": "ARR", + "id": "51175" + }, + "stopCode": "HA3940", + "platformCode": null, + "lon": 4.467403, + "lat": 51.923529, + "arrival": 1376898480000, + "departure": 1376898770000, + "orig": null, + "zoneId": null, + "stopIndex": 10 + }, + "legGeometry": { + "points": "cm~{HkfmZfI|JDfj@zMfHpUlHpI`\\nDzZdChr@", + "levels": null, + "length": 8 + }, + "notes": null, + "alerts": null, + "routeShortName": "39", + "routeLongName": null, + "boardRule": null, + "alightRule": null, + "rentedBike": false, + "transitLeg": true, + "duration": 720000, + "intermediateStops": null, + "steps": [ + + ] +*/ + json_key_obj(j, "legGeometry"); + polyline_for_leg (&pl, tdata, leg, req, leg_idx); + json_kv(j, "points", polyline_result(&pl)); + json_kv(j, "levels", NULL); + json_kd(j, "length", (int) polyline_length(&pl)); + json_end_obj(j); + json_key_arr(j, "intermediateStops"); + if (req->intermediatestops && leg->journey_pattern != WALK) { + jppidx_t i_jpp; + bool visible = false; + for (i_jpp = 0; i_jpp < tdata->journey_patterns[leg->journey_pattern].n_stops; i_jpp++) { + spidx_t stop_idx = tdata->journey_pattern_points[tdata->journey_patterns[leg->journey_pattern].journey_pattern_point_offset + i_jpp]; + if (stop_idx == leg->sp_from) { + visible = true; + continue; + } else if (stop_idx == leg->sp_to) { + visible = false; + break; + } + + if (visible) { + vehicle_journey_t vj = tdata->vjs[tdata->journey_patterns[leg->journey_pattern].vj_index + leg->vj]; + rtime_t arrival = vj.begin_time + tdata->stop_times[vj.stop_times_offset + i_jpp].arrival; + rtime_t departure = vj.begin_time + tdata->stop_times[vj.stop_times_offset + i_jpp].departure; + + json_place(j, NULL, arrival, departure, stop_idx, tdata, date); + } + } + } + json_end_arr(j); + json_kl(j, "duration", endtime - starttime); + json_end_obj(j); +} + +static void +json_itinerary (json_t *j, itinerary_t *itin, tdata_t *tdata, router_request_t *req, time_t date) { + int64_t starttime = rtime_to_msec(itin->legs[0].t0, date, tdata->utc_offset); + int64_t endtime = rtime_to_msec(itin->legs[(itin->n_legs - 1)].t1, date, tdata->utc_offset); + int32_t walktime = 0; + int32_t walkdistance = 0; + int32_t waitingtime = 0; + int32_t transittime = 0; + bool interlineWithPreviousLeg = false; + uint8_t leg_idx; + + json_obj(j); /* one itinerary */ + json_kl(j, "duration", endtime - starttime); + json_kl(j, "startTime", starttime); + json_kl(j, "endTime", endtime); + json_kd(j, "transfers", MAX(0,itin->n_rides - 1)); + json_key_arr(j, "legs"); + for (leg_idx = 0; leg_idx < itin->n_legs; ++leg_idx) { + leg_t *leg = &itin->legs[leg_idx]; + uint32_t leg_duration = RTIME_TO_SEC(leg->t1 - leg->t0); + if (leg->journey_pattern == STAY_ON){ + interlineWithPreviousLeg = true; + continue; + } + json_leg (j, leg, tdata, req, date, leg_idx, interlineWithPreviousLeg); + interlineWithPreviousLeg = false; + if (leg->journey_pattern >= WALK) { + if (leg->sp_from == leg->sp_to) { + waitingtime += leg_duration; + } else { + walktime += leg_duration; + + /* TODO: make this a real distance */ + walkdistance += leg_duration; + } + } else { + transittime += leg_duration; + } + } + json_end_arr(j); + json_kd(j, "walkTime", walktime); + json_kd(j, "transitTime", transittime); + json_kd(j, "waitingTime", waitingtime); + json_kd(j, "walkDistance", walkdistance); + json_kb(j, "walkLimitExceeded", false); + json_kd(j, "elevationLost",0); + json_kd(j, "elevationGained",0); + json_kv(j, "occupancyStatus", NULL); + + json_end_obj(j); +} +static uint32_t +otp_json(json_t *j, plan_t *plan, tdata_t *tdata, char *buf, uint32_t buflen) { + struct tm ltm; + time_t date_seconds = router_request_to_date(& plan->req, tdata, <m); + char date[11]; + char requesttime[16]; + uint8_t i; + strftime(date, 11, "%Y-%m-%d", <m); + btimetext(plan->req.time, requesttime); + + json_init(j, buf, buflen); + json_obj(j); + json_kv(j, "error", "null"); + json_key_obj(j, "requestParameters"); + json_kv(j, "time", requesttime); + json_kb(j, "arriveBy", plan->req.arrive_by); + json_kf(j, "maxWalkDistance", 2000.0); + if (plan->req.from_stop_area != STOP_NONE) { + json_kv(j, "fromPlace", tdata_stop_area_name_for_index(tdata, plan->req.from_stop_area)); + } else { + json_kv(j, "fromPlace", tdata_stop_point_name_for_index(tdata, plan->req.from_stop_point)); + } + if (plan->req.to_stop_area != STOP_NONE) { + json_kv(j, "toPlace", tdata_stop_area_name_for_index(tdata, plan->req.to_stop_area)); + } else { + json_kv(j, "toPlace", tdata_stop_point_name_for_index(tdata, plan->req.to_stop_point)); + } + json_kv(j, "date", date); + if (plan->req.mode == m_all) { + json_kv(j, "mode", "TRANSIT,WALK"); + } else { + /* max length is 58 + 4 + 8 = 70, minus shortest (3 + 1) + 1 */ + char modes[67]; + char *dst = modes; + + dst = modes_string (plan->req.mode, dst); + dst = strcpy(dst, "WALK"); + + json_kv(j, "mode", modes); + } + json_end_obj(j); + json_key_obj(j, "plan"); + json_kl(j, "date", ((int64_t) 1000) * date_seconds); + if (plan->req.from_stop_area != STOP_NONE) { + json_place_area(j, "from", UNREACHED, UNREACHED, plan->req.from_stop_area, tdata, date_seconds); + } else { + json_place(j, "from", UNREACHED, UNREACHED, plan->req.from_stop_point, tdata, date_seconds); + } + + if (plan->req.to_stop_area != STOP_NONE) { + json_place_area(j, "to", UNREACHED, UNREACHED, plan->req.to_stop_area, tdata, date_seconds); + } else { + json_place(j, "to", UNREACHED, UNREACHED, plan->req.to_stop_point, tdata, date_seconds); + } + + json_key_arr(j, "itineraries"); + for (i = 0; i < plan->n_itineraries; ++i) { + json_itinerary (j, plan->itineraries + i, tdata, &plan->req, date_seconds); + } + json_end_arr(j); + json_end_obj(j); + #if 0 + json_key_obj(j, "debug"); + json_kd(j, "precalculationTime", 12); + json_kd(j, "pathCalculationTime", 808); + json_kb(j, "timedOut", false); + json_end_obj(j); + #endif + json_end_obj(j); + json_end(j); + /* json_dump(j); */ + return (uint32_t) json_length(j); +} + +uint32_t +plan_render_otp(plan_t *plan, tdata_t *tdata, char *buf, uint32_t buflen) { + json_t j; + + return otp_json(&j, plan, tdata, buf, buflen); +} + +uint32_t metadata_render_otp (tdata_t *tdata, char *buf, uint32_t buflen) { + json_t j; + float *lon, *lat; + uint64_t starttime, endtime; + spidx_t i_stop = (spidx_t) tdata->n_stop_points; + tmode_t m; + + char modes[67]; + char *dst = modes; + + tdata_validity (tdata, &starttime, &endtime); + tdata_modes (tdata, &m); + + json_init(&j, buf, buflen); + json_obj(&j); + json_kl(&j, "startTime", (int64_t) (starttime * 1000)); + json_kl(&j, "endTime", (int64_t) (endtime * 1000)); + dst = modes_string (m, dst); + dst[-1] = '\0'; + + json_kv(&j, "transitModes", modes); + + lon = (float *) malloc(sizeof(float) * tdata->n_stop_points); + lat = (float *) malloc(sizeof(float) * tdata->n_stop_points); + + if (lon && lat) { + latlon_t ll, ur, c; + + do { + i_stop--; + lon[i_stop] = tdata->stop_point_coords[i_stop].lon; + lat[i_stop] = tdata->stop_point_coords[i_stop].lat; + } while (i_stop > 0); + + c.lon = median (lon, tdata->n_stop_points, &ll.lon, &ur.lon); + c.lat = median (lat, tdata->n_stop_points, &ll.lat, &ur.lat); + + json_kf(&j, "lowerLeftLatitude", ll.lat); + json_kf(&j, "lowerLeftLongitude", ll.lon); + json_kf(&j, "upperRightLatitude", ur.lat); + json_kf(&j, "upperRightLongitude", ur.lon); + json_kf(&j, "minLatitude", ll.lat); + json_kf(&j, "minLongitude", ll.lon); + json_kf(&j, "maxLatitude", ur.lat); + json_kf(&j, "maxLongitude", ur.lon); + + json_kf(&j, "centerLatitude", c.lat); + json_kf(&j, "centerLongitude", c.lon); + } + + free (lon); + free (lat); + + json_end_obj(&j); + json_end(&j); + + return (uint32_t) json_length(&j); +} +#else +void plan_render_otp_not_available(); +#endif /* RRRR_FEATURE_RENDER_OTP */ diff --git a/plan_render_otp.h b/plan_render_otp.h new file mode 100644 index 0000000..41e8c88 --- /dev/null +++ b/plan_render_otp.h @@ -0,0 +1,15 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and at + * https://github.com/bliksemlabs/rrrr/ + */ + +#include "config.h" + +#ifdef RRRR_FEATURE_RENDER_OTP + +#include "rrrr_types.h" +#include "router_result.h" + +uint32_t plan_render_otp(plan_t *plan, tdata_t *tdata, char *buf, uint32_t buflen); +uint32_t metadata_render_otp (tdata_t *tdata, char *buf, uint32_t buflen); +#endif diff --git a/plan_render_text.c b/plan_render_text.c new file mode 100644 index 0000000..dfa2a84 --- /dev/null +++ b/plan_render_text.c @@ -0,0 +1,184 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and at + * https://github.com/bliksemlabs/rrrr/ + */ + +#include "config.h" + +#ifdef RRRR_FEATURE_RENDER_TEXT + +/* plan_render_text.c renders plan structs to a human-readable tabular format */ +#include "plan_render_text.h" +#include "router_request.h" +#include + +#ifdef RRRR_FEATURE_REALTIME_ALERTS +static void +leg_add_alerts (leg_t *leg, tdata_t *tdata, time_t date, char **alert_msg) { + size_t i_entity; + uint64_t t0 = (uint64_t) (date + RTIME_TO_SEC(leg->t0 - RTIME_ONE_DAY)); + uint64_t t1 = (uint64_t) (date + RTIME_TO_SEC(leg->t1 - RTIME_ONE_DAY)); + for (i_entity = 0; i_entity < tdata->alerts->n_entity; ++i_entity) { + if (tdata->alerts->entity[i_entity] && + tdata->alerts->entity[i_entity]->alert) { + TransitRealtime__Alert *alert = tdata->alerts->entity[i_entity]->alert; + + if (alert->n_active_period > 0) { + size_t i_active_period; + for (i_active_period = 0; i_active_period < alert->n_active_period; ++i_active_period) { + TransitRealtime__TimeRange *active_period = alert->active_period[i_active_period]; + size_t i_informed_entity; + + if (active_period->start >= t1 || active_period->end <= t0) continue; + + for (i_informed_entity = 0; i_informed_entity < alert->n_informed_entity; ++i_informed_entity) { + TransitRealtime__EntitySelector *informed_entity = alert->informed_entity[i_informed_entity]; + + if ( ( (!informed_entity->route_id) || ((uint32_t) *(informed_entity->route_id) == leg->journey_pattern) ) && + ( (!informed_entity->stop_id) || ( + ((uint32_t) *(informed_entity->stop_id) == leg->sp_from && active_period->start <= t0 && active_period->end >= t0 ) || + ((uint32_t) *(informed_entity->stop_id) == leg->sp_to && active_period->start <= t1 && active_period->end >= t1 ) + ) ) && + ( (!informed_entity->trip) || (!informed_entity->trip->trip_id) || ((uint32_t) *(informed_entity->trip->trip_id) == leg->vj) ) + /* TODO: need to have the start date for a trip_id for informed_entity->trip->start_date */ + ) { + *alert_msg = alert->header_text->translation[0]->text; + } + + /* TODO: theoretically we could have multiple alert messages */ + if (*alert_msg) return; + } + } + } + } + } +} +#endif + +static char * +plan_render_itinerary (struct itinerary *itin, tdata_t *tdata, time_t date, + char *b, char *b_end) { + leg_t *leg; + int32_t time_offset = itin->n_legs < 2 ? 0 : + SIGNED_SEC_TO_RTIME(tdata_time_offset_for_jp_vj_offset(tdata, itin->legs[1].journey_pattern, itin->legs[1].vj)); + + b += sprintf (b, "\nITIN %d rides \n", itin->n_rides); + + /* Render the legs of this itinerary, which are in chronological order */ + for (leg = itin->legs; leg < itin->legs + itin->n_legs; ++leg) { + char ct0[16]; + char ct1[16]; + char *alert_msg = NULL; + const char *operator_name, *short_name, *headsign, *commercial_mode, *vj_id; + const char *leg_mode = NULL; + const char *s0_id = tdata_stop_point_name_for_index(tdata, leg->sp_from); + const char *s1_id = tdata_stop_point_name_for_index(tdata, leg->sp_to); + float d0 = 0.0, d1 = 0.0; + + btimetext((rtime_t) (leg->t0 - time_offset), ct0); + btimetext((rtime_t) (leg->t1 - time_offset), ct1); + + if (leg->journey_pattern >= WALK) { + operator_name = ""; + short_name = "walk"; + headsign = "walk"; + commercial_mode = ""; + vj_id = ""; + + /* Skip uninformative legs that just tell you to stay in the same + * place. + */ + if (leg->sp_from == ONBOARD) continue; + else if (leg->journey_pattern == STREET) leg_mode = "STREET"; + else if (leg->journey_pattern == STAY_ON){ + leg_mode = "STAY ON"; + short_name = ""; + headsign = ""; + } + else if (leg->sp_from == leg->sp_to) leg_mode = "WAIT"; + else leg_mode = "WALK"; + } else { + operator_name = tdata_operator_name_for_journey_pattern(tdata, leg->journey_pattern); + short_name = tdata_line_code_for_journey_pattern(tdata, leg->journey_pattern); + commercial_mode = tdata_commercial_mode_name_for_journey_pattern(tdata, leg->journey_pattern); + vj_id = tdata_vehicle_journey_id_for_jp_vj_offset(tdata, leg->journey_pattern, leg->vj); + #ifdef RRRR_FEATURE_REALTIME_EXPANDED + headsign = tdata_headsign_for_journey_pattern_point(tdata, leg->journey_pattern,leg->jpp0); + d0 = leg->d0 / 60.0f; + d1 = leg->d1 / 60.0f; + #else + headsign = tdata_headsign_for_journey_pattern(tdata, leg->journey_pattern); + #endif + + leg_mode = tdata_physical_mode_name_for_journey_pattern(tdata, leg->journey_pattern); + + #ifdef RRRR_FEATURE_REALTIME_ALERTS + if (leg->journey_pattern < WALK && tdata->alerts) { + leg_add_alerts (leg, tdata, date, &alert_msg); + } + #else + UNUSED(date); + #endif + time_offset = SIGNED_SEC_TO_RTIME(tdata_time_offset_for_jp_vj_offset(tdata, leg->journey_pattern, leg->vj)); + } + + /* TODO: we are able to calculate the maximum length required for each line + * therefore we could prevent a buffer overflow from happening. */ + b += sprintf (b, "%s %5d %3d %5d %5d %s %+3.1f %s %+3.1f ;%s;%s;%s;%s;%s;%s;%s;%s\n", + leg_mode, leg->journey_pattern, leg->vj, leg->sp_from, leg->sp_to, ct0, d0, ct1, d1, operator_name, short_name, headsign, commercial_mode, s0_id, s1_id, + vj_id,(alert_msg ? alert_msg : "")); + + /* EXAMPLE + polyline_for_leg (tdata, leg); + b += sprintf (b, "%s\n", polyline_result()); + */ + + if (b > b_end) { + fprintf (stderr, "buffer overflow\n"); + return b; + /* exit(2); */ + } + } + + return b; +} + +/* Write a plan structure out to a text buffer in tabular format. */ +uint32_t +plan_render_text(plan_t *plan, tdata_t *tdata, char *buf, uint32_t buflen) { + char *b = buf; + char *b_end = buf + buflen; + struct tm ltm; + time_t date = router_request_to_date (&plan->req, tdata, <m); + + if ((plan->req.optimise & o_all) == o_all) { + /* Iterate over itineraries in this plan, + * which are in increasing order of number of rides + */ + itinerary_t *itin; + for (itin = plan->itineraries; + itin < plan->itineraries + plan->n_itineraries; + ++itin) { + b = plan_render_itinerary (itin, tdata, date, b, b_end); + } + } else if (plan->n_itineraries > 0) { + /* only render the first itinerary, + * which has the least transfers + */ + if ((plan->req.optimise & o_transfers) == o_transfers) { + b = plan_render_itinerary (plan->itineraries, tdata, date, b, b_end); + } + + /* only render the last itinerary, + * which has the most rides and is the shortest in time + */ + if ((plan->req.optimise & o_shortest) == o_shortest) { + b = plan_render_itinerary (&plan->itineraries[plan->n_itineraries - 1], tdata, date, b, b_end); + } + } + *b = '\0'; + return (uint32_t) (b - buf); +} +#else +void plan_render_text_not_available(); +#endif /* RRRR_FEATURE_RENDER_TEXT */ diff --git a/plan_render_text.h b/plan_render_text.h new file mode 100644 index 0000000..7781fad --- /dev/null +++ b/plan_render_text.h @@ -0,0 +1,14 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and at + * https://github.com/bliksemlabs/rrrr/ + */ + +#include "config.h" + +#ifdef RRRR_FEATURE_RENDER_TEXT + +#include "rrrr_types.h" +#include "router_result.h" + +uint32_t plan_render_text(plan_t *plan, tdata_t *tdata, char *buf, uint32_t buflen); +#endif diff --git a/polyline.c b/polyline.c new file mode 100644 index 0000000..8cd9d93 --- /dev/null +++ b/polyline.c @@ -0,0 +1,128 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and at + * https://github.com/bliksemlabs/rrrr/ + */ + +/* polyline.c : encode chains of latitude/longitude coordinates as + * printable ASCII text. + */ + +/* https://developers.google.com/maps/documentation/utilities/polylinealgorithm + * This official polyline description is a very imperative, algorithmic one and + * is missing some details. + * + * Here's my version: + * Encoded polylines are variable-length base-64 encoding of latitude and + * longitude coordinates. Each coordinate is encoded separately using exactly + * the same method, in pairs (lat, lon). The first pair in a polyline is + * absolute, but subsequent ones are relative. Saving deltas between points + * makes the overall polyline much shorter since we use only as many bytes as + * necessary for each point. The latitude and longitude are first converted to + * integers: from floating point to fixed-point (5 decimal places). The + * twos-complement representation of the number is left-shifted by one and + * inverted (bitwise NOT) if it is negative. This has the effect of making the + * lowest-order bit a sign bit and makes the number of significant bits + * proportionate to the absolute value of the number. Starting from the + * low-order bits we mask off chunks of 5 bits, giving a range of 0-31 for + * each chunk. Chunks of zeros in the high order bits are dropped, with the + * sixth bit (0x20) used to indicate whether additional chunks follow. These + * six-bit chunks have a range of 0-63, and are shifted by 63 into the + * printable character block at 63-126, with characters from the second half + * at 95-126 making up the body of an encoded number and characters from the + * first half at 63-94 (for which bit 6 is 0) terminating an encoded number. + * + * Results can be checked with: + * https://developers.google.com/maps/documentation/utilities/polylineutility + * + * For comparison see: + * https://developers.google.com/protocol-buffers/docs/encoding#optional + * Repeated base-128 varints would be more efficient. + */ + +#include "polyline.h" + +static int encode_offset (uint32_t binary, char *buf) { + char *b = buf; + /* 31 == (2^5 - 1) == 00000 00000 00000 00000 00000 11111 */ + uint32_t mask = 31; + char chunks[6]; + /* initialization to 0 is important so we always get at least + * one '?' chunk, allowing zero coordinates and deltas + */ + uint32_t last_chunk = 0; + uint8_t i; + + /* printf ("%+10.5f %+10d ", c, binary); */ + /* use the lowest order bit as a sign bit */ + binary <<= 1; + /* printf ("%+10d ", binary); */ + if ((int32_t)binary < 0) binary = ~binary; + /* printf ("%+10d ", binary); */ + for (i = 0; i < 6; ++i) { + chunks[i] = (char) ((binary & mask) >> (5 * i)); + /* printf ("%3d ", chunks[i]); */ + /* track the last nonzero chunk. there may be zeros between + * positive chunks (rendered as '_' == 95) + */ + if (chunks[i] != 0) last_chunk = i; + mask <<= 5; + } + for (i = 0; i <= last_chunk; ++i) { + char out = chunks[i]; + /* high bit (range 32-63) indicates another chunk follows */ + if (i != last_chunk) out |= 0x20; + /* move into alphabetic range 63-126, + * with 95-126 indicating another chunk follow + */ + out += 63; + *(b++) = out; + } + *b = '\0'; + /* printf ("%s \n", buf); */ + return (int) (b - buf); +} + +void polyline_begin (polyline_t *pl) { + pl->last_lat = 0; + pl->last_lon = 0; + pl->buf_cur = pl->buf; + *pl->buf_cur = '\0'; + pl->n_points = 0; + + /* each latlon could take up to 12 chars */ + pl->buf_max = pl->buf + PL_BUFLEN - 13; +} + +/* Allows preserving precision by not using float-based latlon_t. */ +void polyline_point (polyline_t *pl, double lat, double lon) { + uint32_t dlat, dlon; + + uint32_t ilat = (uint32_t) (round(1e5 * lat)); + uint32_t ilon = (uint32_t) (round(1e5 * lon)); + + /* check for potential buffer overflow */ + if (pl->buf_cur >= pl->buf_max) return; + + /* encoded polylines are variable-width, + * and use coordinate differences to save space. + */ + dlat = ilat - pl->last_lat; + dlon = ilon - pl->last_lon; + pl->buf_cur += encode_offset (dlat, pl->buf_cur); + pl->buf_cur += encode_offset (dlon, pl->buf_cur); + pl->last_lat = ilat; + pl->last_lon = ilon; + pl->n_points += 1; +} + +void polyline_latlon (polyline_t *pl, latlon_t ll) { + polyline_point (pl, ll.lat, ll.lon); +} + +char *polyline_result (polyline_t *pl) { + return pl->buf; +} + +uint32_t polyline_length (polyline_t *pl) { + return pl->n_points; +} diff --git a/polyline.h b/polyline.h new file mode 100644 index 0000000..88bb634 --- /dev/null +++ b/polyline.h @@ -0,0 +1,37 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and at + * https://github.com/bliksemlabs/rrrr/ + */ + +/* polyline.h */ +/* https://developers.google.com/maps/documentation/utilities/polylinealgorithm */ + +#include "geometry.h" +#include "router_result.h" + +/* For The Netherlands our longest journey pattern is 124 stops + * it would be sufficient to allocate only 124 * 13 bytes. + */ + +#define PL_BUFLEN 1996 + +typedef struct polyline polyline_t; +struct polyline { + char *buf_cur; + char *buf_max; + uint32_t last_lat; + uint32_t last_lon; + uint32_t n_points; + char buf[PL_BUFLEN]; +}; + +void polyline_begin (polyline_t *pl); + +void polyline_point (polyline_t *pl, double lat, double lon); + +void polyline_latlon (polyline_t *pl, latlon_t ll); + +char *polyline_result (polyline_t *pl); + +/* number of points in the polyline */ +uint32_t polyline_length (polyline_t *pl); diff --git a/python/README.md b/python/README.md new file mode 100644 index 0000000..93c657a --- /dev/null +++ b/python/README.md @@ -0,0 +1,4 @@ +Python bindings for rrrr +======================== + +This is our first _unstable_ Python 2 API for rrrr. We will follow up with Python 3 support. diff --git a/python/example.py b/python/example.py new file mode 100644 index 0000000..9c49f23 --- /dev/null +++ b/python/example.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python2 +import rrrr +import sys +import os.path + +if len(sys.argv) < 2 or not os.path.isfile(sys.argv[1]): + print ("Create a timetable from GTFS using rrtimetable.") + sys.exit(-1) + +router = rrrr.Raptor(timetable = sys.argv[1]) + +print router.stops()[0:10] +print router.route(from_id = 'vb', to_id = 'amf', depart = 1427883032) +print router.route(from_idx = 200, to_idx = 201, depart = 1427883032) +print router.route(from_latlon = (52.07, 4.35), to_id = 'amf', depart = 1427883032) diff --git a/python/rrrr.c b/python/rrrr.c new file mode 100644 index 0000000..05abd9d --- /dev/null +++ b/python/rrrr.c @@ -0,0 +1,375 @@ +#include +#include "structmember.h" +#include "config.h" +#include "plan_render_otp.h" +#include "set.h" +#include "api.h" +#include "plan.h" +#include "router_request.h" + +typedef struct { + /* The object itself */ + PyObject_HEAD + + /* filename of timetable4.dat */ + PyObject *timetable; + + /* json.loads function */ + PyObject *json; + + /* interface to a loaded timetable */ + tdata_t tdata; + + /* interface to the router instance */ + router_t router; +} Raptor; + +static void +Raptor_dealloc (Raptor* self) +{ + /* free the memory used in the object */ + Py_XDECREF (self->timetable); + router_teardown (&self->router); + tdata_hashgrid_teardown (&self->tdata); + tdata_close (&self->tdata); + self->ob_type->tp_free ((PyObject*)self); +} + +static PyObject * +Raptor_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + Raptor *self; + PyObject *json; + + self = (Raptor *)type->tp_alloc(type, 0); + if (self != NULL) { + self->timetable = PyString_FromString(""); + if (self->timetable == NULL) { + Py_DECREF(self); + return NULL; + } + + /* initialise the memory for the router */ + memset (&self->tdata, 0, sizeof(tdata_t)); + memset (&self->router, 0, sizeof(router_t)); + } + + /* import json */ + json = PyImport_ImportModule("json"); + + /* json.loads() */ + self->json = PyObject_GetAttrString(json, "loads"); + + return (PyObject *)self; +} + +static int +Raptor_init(Raptor *self, PyObject *args, PyObject *kwds) +{ + PyObject *timetable=NULL, *tmp; + + { + /* class Raptor: def __init__(timetable) */ + static char *kwlist[] = {"timetable", NULL}; + if (! PyArg_ParseTupleAndKeywords(args, kwds, + "O", kwlist, + &timetable)) { + return -1; + } + } + + if (timetable) { + tmp = self->timetable; + Py_INCREF(timetable); + self->timetable = timetable; + Py_XDECREF(tmp); + + /* Initialise the journey planner */ + char *filename = PyString_AsString(self->timetable); + if (! tdata_load(&self->tdata, filename) || + ! tdata_hashgrid_setup (&self->tdata) || + ! router_setup (&self->router, &self->tdata)) { + return -1; + } + } + + return 0; +} + + +static PyMemberDef Raptor_members[] = { + {"timetable", T_OBJECT_EX, offsetof(Raptor, timetable), 0, + "Path to the location of timetable4.dat"}, + {NULL} /* Sentinel */ +}; + +static PyObject * +Raptor_stops(Raptor* self, PyObject *args, PyObject *keywords) +{ + /* Number of stops to iterate over */ + spidx_t sp_index = (spidx_t) self->tdata.n_stop_points; + + /* Each list is a column similar to stops.txt */ + PyObject *py_stop_name = PyList_New(sp_index); + PyObject *py_stop_lat = PyList_New(sp_index); + PyObject *py_stop_lon = PyList_New(sp_index); + + /* The return object stores all list by column name */ + PyObject *py_stops = PyDict_New(); + + while (sp_index) { + const char *stop_name; + const latlon_t *latlon; + sp_index--; + + stop_name = tdata_stop_point_name_for_index(&self->tdata, sp_index); + latlon = tdata_stop_point_coord_for_index(&self->tdata, sp_index); + PyList_SetItem (py_stop_name, sp_index, PyString_FromString (stop_name)); + PyList_SetItem (py_stop_lat, sp_index, PyFloat_FromDouble (latlon->lat)); + PyList_SetItem (py_stop_lon, sp_index, PyFloat_FromDouble (latlon->lon)); + } + + /* Create stops = {'stop_name: [], 'stop_lat': [], 'stop_lon', []} */ + PyDict_SetItemString (py_stops, "stop_name", py_stop_name); + PyDict_SetItemString (py_stops, "stop_lat", py_stop_lat); + PyDict_SetItemString (py_stops, "stop_lon", py_stop_lon); + + return py_stops; +} + +static PyObject * +Raptor_validity(Raptor* self, PyObject *args, PyObject *keywords) +{ + uint64_t starttime, endtime; + PyObject *py_validity = PyTuple_New(2); + tdata_validity (&self->tdata, &starttime, &endtime); + + PyTuple_SetItem (py_validity, 0, PyLong_FromUnsignedLongLong(starttime)); + PyTuple_SetItem (py_validity, 1, PyLong_FromUnsignedLongLong(endtime)); + + return py_validity; +} + +static PyObject * +Raptor_route(Raptor* self, PyObject *args, PyObject *keywords) +{ + char *from_id = NULL, *from_sp_id = NULL, + *to_id = NULL, *to_sp_id = NULL, + *operator = NULL, *mode = NULL; + float walk_speed = RRRR_DEFAULT_WALK_SPEED; + unsigned char api = 0, walk_slack = RRRR_DEFAULT_WALK_SLACK; + time_t arrive = 0, depart = 0, epoch = 0; + uint16_t walk_max_distance = RRRR_DEFAULT_WALK_MAX_DISTANCE; + router_request_t req; + plan_t plan; + #define OUTPUT_LEN 100000 + char result_buf[OUTPUT_LEN]; + + router_request_initialize (&req); + + { + static char * list[] = { "from_id", "to_id", + "from_sp_id", "to_sp_id", + "from_latlon", "to_latlon", + "from_idx", "to_idx", + "from_sp_idx", "to_sp_idx", + "arrive", "depart", + "operator", "mode", + "walk_speed", "walk_slack", "walk_max_distance", + "api", + NULL }; + + if ( !PyArg_ParseTupleAndKeywords(args, keywords, "|ssss(ff)(ff)HHHHllssfbhb", + list, &from_id, &to_id, + &from_sp_id, &to_sp_id, + &req.from_latlon.lat, &req.from_latlon.lon, + &req.to_latlon.lat, &req.to_latlon.lon, + &req.from_stop_area, &req.to_stop_area, + &req.from_stop_point, &req.to_stop_point, + &arrive, &depart, + &operator, &mode, + &walk_speed, &walk_slack, &walk_max_distance, + &api)) { + return NULL; + } + } + + /* Validate input */ + if ( (from_id == NULL && from_sp_id == NULL && + req.from_stop_area == STOP_NONE && req.from_stop_point == STOP_NONE && + (req.from_latlon.lon == 0.0 && req.from_latlon.lat == 0.0 )) || + (to_id == NULL && to_sp_id == NULL && + req.to_stop_area == STOP_NONE && req.to_stop_point == STOP_NONE && + ( req.to_latlon.lon == 0.0 && req.to_latlon.lat == 0.0 )) || + ( arrive == 0 && depart == 0)) { + PyErr_SetString(PyExc_AttributeError, "Missing mandatory input"); + return NULL; + } + + /* Temporal related input */ + if (arrive != 0) { + req.arrive_by = true; + epoch = arrive; + } else { + req.arrive_by = false; + epoch = depart; + } + + router_request_from_epoch (&req, &self->tdata, epoch); + if (req.time_rounded && ! (req.arrive_by)) { + req.time++; + } + req.time_rounded = false; + + if (req.arrive_by) { + req.time_cutoff = 0; + } else { + req.time_cutoff = UNREACHED; + } + + + /* Spatial related input */ + if (from_id) { + req.from_stop_area = tdata_stop_areaidx_by_stop_area_id (&self->tdata, from_id, 0); + } + + if (from_sp_id) { + req.from_stop_point = tdata_stop_pointidx_by_stop_point_id (&self->tdata, from_sp_id, 0); + } + + if (to_id) { + req.to_stop_area = tdata_stop_areaidx_by_stop_area_id (&self->tdata, to_id, 0); + } + + if (to_sp_id) { + req.to_stop_point = tdata_stop_pointidx_by_stop_point_id (&self->tdata, to_sp_id, 0); + } + + /* Filtering related input */ + if (operator) { + opidx_t op_idx = tdata_operator_idx_by_operator_name (&self->tdata, operator, 0); + while (op_idx != OP_NONE) + { + set_add_uint8 (req.operators, &req.n_operators, RRRR_MAX_FILTERED_OPERATORS, op_idx); + op_idx = tdata_operator_idx_by_operator_name (&self->tdata, operator, op_idx + 1); + } + } + + if (mode) { + req.mode = strtomode (mode); + } + + req.walk_speed = walk_speed; + req.walk_slack = walk_slack; + req.walk_max_distance = walk_max_distance; + + plan_init (&plan); + + { + bool success = false; + switch (api) { + case 1: + success = router_route_all_departures (&self->router, &req, &plan); + break; + case 2: + success = router_route_naive_reversal (&self->router, &req, &plan); + break; + case 3: + success = router_route_full_reversal (&self->router, &req, &plan); + break; + default: + success = router_route_first_departure (&self->router, &req, &plan); + } + + if (!success) { + PyErr_SetString(PyExc_AttributeError, "No results"); + return NULL; + } + } + + plan.req = req; + plan_render_otp (&plan, &self->tdata, result_buf, OUTPUT_LEN); + + /* return json.loads(result_buf) */ + return PyObject_CallFunctionObjArgs(self->json, PyString_FromString (result_buf), NULL); +} + +static PyMethodDef Raptor_methods[] = { + {"route", (PyCFunction)Raptor_route, METH_VARARGS | METH_KEYWORDS, + "Return a JSON-OTP formatted set of itineraries" + }, + {"stops", (PyCFunction)Raptor_stops, METH_NOARGS, + "Return a List of stops from the timetable" + }, + {"validity", (PyCFunction)Raptor_validity, METH_NOARGS, + "Return a Tuple with the validity of the timetable" + }, + {NULL} /* Sentinel */ +}; + +static PyTypeObject RaptorType = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "rrrr.Raptor", /*tp_name*/ + sizeof(Raptor), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)Raptor_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + "Raptor objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + Raptor_methods, /* tp_methods */ + Raptor_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)Raptor_init, /* tp_init */ + 0, /* tp_alloc */ + Raptor_new, /* tp_new */ +}; + +static PyMethodDef module_methods[] = { + {NULL} /* Sentinel */ +}; + +#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ +#define PyMODINIT_FUNC void +#endif +PyMODINIT_FUNC +initrrrr (void) +{ + PyObject* m; + + if (PyType_Ready (&RaptorType) < 0) + return; + + m = Py_InitModule3 ("rrrr", module_methods, + "Public transport Journey Planner."); + + if (m == NULL) + return; + + Py_INCREF (&RaptorType); + PyModule_AddObject (m, "Raptor", (PyObject *)&RaptorType); +} diff --git a/python/setup.py b/python/setup.py new file mode 100644 index 0000000..f21f89b --- /dev/null +++ b/python/setup.py @@ -0,0 +1,10 @@ +from distutils.core import setup, Extension + +extension = Extension("rrrr", + sources = ["rrrr.c"], + libraries = ['rrrr', 'protobuf-c', 'm'], + library_dirs = ['../build'], + include_dirs = ['..']) + +setup(name="rrrr", version="1.0", ext_modules = [extension]) + diff --git a/radixtree.c b/radixtree.c index e998805..a8084e9 100644 --- a/radixtree.c +++ b/radixtree.c @@ -1,28 +1,26 @@ -/* Copyright 2013 Bliksem Labs. - * See the LICENSE file at the top-level directory of this distribution and at - * https://github.com/bliksemlabs/rrrr/ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and + * at https://github.com/bliksemlabs/rrrr/ */ #include "radixtree.h" -#include "config.h" /* All nodes are identical in size, allowing use with no dynamic allocation * (in a contiguous block of memory). Only supports insertion and retrieval, * not deleting. */ -#include -#include #include #include #include #include -#include -#include #include +#if defined(RRRR_TDATA_IO_MMAP) +#include +#endif -static struct rxt_edge *rxt_edge_new () { - struct rxt_edge *e = (struct rxt_edge *) malloc(sizeof(struct rxt_edge)); +static rxt_edge_t *rxt_edge_new () { + rxt_edge_t *e = (rxt_edge_t *) malloc(sizeof(rxt_edge_t)); if (e == NULL) return NULL; e->next = NULL; @@ -57,7 +55,7 @@ radixtree_t *radixtree_new () { */ bool radixtree_insert (radixtree_t *r, const char *key, uint32_t value) { const char *k = key; - struct rxt_edge *e = r->root; + rxt_edge_t *e = r->root; /* Loop over edges labeled to continuation from within nested loops. */ tail_recurse: while (e != NULL) { @@ -118,7 +116,7 @@ bool radixtree_insert (radixtree_t *r, const char *key, uint32_t value) { * of the for loop, to avoid goto. Then again purpose of * goto is clear. */ - struct rxt_edge *new; + rxt_edge_t *new; uint32_t j; char *n, *o; @@ -197,9 +195,9 @@ bool radixtree_insert (radixtree_t *r, const char *key, uint32_t value) { /* should never happen unless allocation fails, giving a NULL next edge. */ } -uint32_t radixtree_find (radixtree_t *r, const char *key) { +rxt_edge_t* radixtree_find (radixtree_t *r, const char *key) { const char *k = key; - struct rxt_edge *e = r->root; + rxt_edge_t *e = r->root; while (e != NULL) { const char *p = e->prefix; if (*k == *p) { /* we have a match, consume some characters */ @@ -209,13 +207,13 @@ uint32_t radixtree_find (radixtree_t *r, const char *key) { /* prefix char is 0, reached the end of this edge's * prefix string. */ - if (*k != *p) return RADIXTREE_NONE; + if (*k != *p) return NULL; /* key was not in tree */ } /* We have consumed the prefix with or without * hitting a terminator byte. */ - if (*k == '\0') return e->value; + if (*k == '\0') return e; /* This edge consumed the entire key. */ e = e->child; /* Key characters remain, tail-recurse on those @@ -226,10 +224,43 @@ uint32_t radixtree_find (radixtree_t *r, const char *key) { e = e->next; /* Next edge in the list on the same tree level */ } - return RADIXTREE_NONE; + return NULL; /* Ran out of edges to traverse, no match was found. */ } +uint32_t radixtree_find_exact (radixtree_t *r, const char *key) { + rxt_edge_t *e = radixtree_find(r, key); + if (e) { + return e->value; + } + return RADIXTREE_NONE; +} + +uint32_t radixtree_find_prefix (radixtree_t *r, const char *key, + rxt_edge_t *result) { + rxt_edge_t *e = radixtree_find (r, key); + if (result) { + /* return the last edge before a branch + * TODO: validate this is required or + * or e would be already be correct. + */ + while (e && e->child && !e->next) { + e = e->child; + } + + result = e; + } + + while (e && e->child) { + e = e->child; + } + + if (e) { + return e->value; + } + return RADIXTREE_NONE; +} + radixtree_t *radixtree_load_strings_from_file (char *filename) { radixtree_t *r; char *strings_end, *s; @@ -251,16 +282,21 @@ radixtree_t *radixtree_load_strings_from_file (char *filename) { goto fail_close_fd; } - r->size = st.st_size; + if (st.st_size <= 0) { + fprintf(stderr, "The input file %s is too small.\n", filename); + goto fail_close_fd; + } + + r->size = (size_t) st.st_size; #if defined(RRRR_TDATA_IO_MMAP) - r->base = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + r->base = mmap(NULL, (size_t) st.st_size, PROT_READ, MAP_SHARED, fd, 0); if (r->base == MAP_FAILED) { fprintf(stderr, "The input file %s could not be mapped.\n", filename); goto fail_close_fd; } #else - r->base = malloc(st.st_size + 1); + r->base = malloc((unsigned long) (st.st_size + 1)); if (r->base == NULL) { fprintf(stderr, "Could not allocate memomry to store %s.\n", filename); goto fail_close_fd; @@ -298,9 +334,9 @@ radixtree_t *radixtree_load_strings_from_file (char *filename) { #if 0 rxt_compress (root); fprintf (stderr, "total number of edges: %d\n", edge_count(root)); - fprintf (stderr, "size of one edge: %ld\n", sizeof(struct rxt_edge)); + fprintf (stderr, "size of one edge: %ld\n", sizeof(rxt_edge_t)); fprintf (stderr, "total size of all edges: %ld\n", - edge_count(root) * sizeof(struct rxt_edge)); + edge_count(root) * sizeof(rxt_edge_t)); #endif return r; @@ -314,32 +350,7 @@ radixtree_t *radixtree_load_strings_from_file (char *filename) { return NULL; } -radixtree_t *radixtree_load_strings_from_tdata (char *strings, uint32_t width, uint32_t length) { - radixtree_t *r = radixtree_new(); - char *strings_end = strings + (width * length); - char *s = strings; - uint32_t idx = 0; - #ifdef RRRR_DEBUG - fprintf (stderr, "Indexing strings...\n"); - #endif - while (s < strings_end) { - radixtree_insert (r, s, idx); - s += width; - idx += 1; - } - - #if 0 - rxt_compress (root); - fprintf (stderr, "total number of edges: %d\n", edge_count(root)); - fprintf (stderr, "size of one edge: %ld\n", sizeof(struct rxt_edge)); - fprintf (stderr, "total size of all edges: %ld\n", - edge_count(root) * sizeof(struct rxt_edge)); - #endif - - return r; -} - -static void rxt_edge_free (struct rxt_edge *e) { +static void rxt_edge_free (rxt_edge_t *e) { if (e == NULL) return; rxt_edge_free (e->next); @@ -363,7 +374,7 @@ void radixtree_destroy (radixtree_t *r) { } #ifdef RRRR_DEBUG -static uint32_t edge_prefix_length (struct rxt_edge *e) { +static uint32_t edge_prefix_length (rxt_edge_t *e) { uint32_t n = 0; char *c = e->prefix; while (*c != '\0' && n < RRRR_RADIXTREE_PREFIX_SIZE) { @@ -373,7 +384,7 @@ static uint32_t edge_prefix_length (struct rxt_edge *e) { return n; } -uint32_t rxt_edge_count (struct rxt_edge *e) { +uint32_t rxt_edge_count (rxt_edge_t *e) { uint32_t n = 0; if (e != NULL) { n += 1; @@ -383,7 +394,7 @@ uint32_t rxt_edge_count (struct rxt_edge *e) { return n; } -void rxt_edge_print (struct rxt_edge *e) { +void rxt_edge_print (rxt_edge_t *e) { if (e == NULL) return; fprintf (stderr, "\nedge [%p]\n", (void *) e); /* variable width string format character */ @@ -399,22 +410,22 @@ void rxt_edge_print (struct rxt_edge *e) { #if 0 /* TODO: returns a compacted copy of the tree */ -struct node *compact (struct rxt_edge *root) { +struct node *compact (rxt_edge_t *root) { return NULL; } /* Compresses paths in place. Not well tested, * and only seems to remove a few edges from 600k when using numbers. */ -void rxt_compress (struct rxt_edge *root) { - struct rxt_edge *e = root; +void rxt_compress (rxt_edge_t *root) { + rxt_edge_t *e = root; if (e == NULL) return; while (e->child != NULL && e->child->next == NULL && e->value == RADIXTREE_NONE) { uint32_t l0 = edge_prefix_length(e); uint32_t l1 = edge_prefix_length(e->child); if (l0 + l1 <= RRRR_RADIXTREE_PREFIX_SIZE) { - struct rxt_edge *new_child; + rxt_edge_t *new_child; uint32_t i; char *c0, *c1; diff --git a/radixtree.h b/radixtree.h index 0c8383b..c10d709 100644 --- a/radixtree.h +++ b/radixtree.h @@ -1,6 +1,6 @@ -/* Copyright 2013 Bliksem Labs. - * See the LICENSE file at the top-level directory of this distribution and at - * https://github.com/bliksemlabs/rrrr/ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and + * at https://github.com/bliksemlabs/rrrr/ */ #ifndef _RADIXTREE_H @@ -18,28 +18,29 @@ * indicates an empty edge list. A NULL next-pointer indicates the last edge * in the list. */ +typedef struct rxt_edge rxt_edge_t; struct rxt_edge { /* the next parallel edge out of the same parent node, * NULL indicates end of list */ - struct rxt_edge *next; + rxt_edge_t *next; /* the first edge in the list reached by traversing this * edge (consuming its prefix) */ - struct rxt_edge *child; + rxt_edge_t *child; uint32_t value; char prefix[RRRR_RADIXTREE_PREFIX_SIZE]; }; typedef struct radixtree_s radixtree_t; struct radixtree_s { - struct rxt_edge *root; + rxt_edge_t *root; void *base; size_t size; }; -radixtree_t *radixtree_new (); +radixtree_t *radixtree_new (void); radixtree_t *radixtree_load_strings_from_file (char *filename); @@ -49,12 +50,14 @@ void radixtree_destroy (radixtree_t *r); bool radixtree_insert (radixtree_t *r, const char *key, uint32_t value); -uint32_t radixtree_find (radixtree_t *r, const char *key); +rxt_edge_t* radixtree_find (radixtree_t *r, const char *key); +uint32_t radixtree_find_exact (radixtree_t *r, const char *key); +uint32_t radixtree_find_prefix (radixtree_t *r, const char *key, rxt_edge_t *result); #ifdef RRRR_DEBUG -uint32_t radixtree_edge_count (struct rxt_edge *e); +uint32_t rxt_edge_count (rxt_edge_t *e); -void radixtree_edge_print (struct rxt_edge *e); +void rxt_edge_print (rxt_edge_t *e); #endif #endif /* _RADIXTREE_H */ diff --git a/router.c b/router.c index 2ccdea9..729b11f 100644 --- a/router.c +++ b/router.c @@ -1,4 +1,4 @@ -/* Copyright 2013 Bliksem Labs. +/* Copyright 2013-2015 Bliksem Labs B.V. * See the LICENSE file at the top-level directory of this distribution and at * https://github.com/bliksemlabs/rrrr/ */ @@ -6,49 +6,20 @@ /* router.c : the main routing algorithm */ #include "router.h" /* first to ensure it works alone */ #include "router_request.h" -#include "router_dump.h" - #include "util.h" -#include "config.h" -#include "tdata.h" -#include "bitset.h" -#include "hashgrid.h" +#include "set.h" #include -#include #include -#include -#include -#include -#include - -#ifdef RRRR_FEATURE_LATLON -static bool router_setup_hashgrid(router_t *router) { - coord_t *coords; - uint32_t i_stop; - - coords = (coord_t *) malloc(sizeof(coord_t) * router->tdata->n_stops); - if (!coords) return false; - - i_stop = router->tdata->n_stops; - do { - i_stop--; - coord_from_latlon(coords + i_stop, - router->tdata->stop_coords + i_stop); - } while(i_stop); - - hashgrid_init (&router->hg, 100, 500.0, coords, router->tdata->n_stops); - free(coords); - - return true; -} +#ifdef RRRR_DEBUG +#include "router_dump.h" #endif bool router_setup(router_t *router, tdata_t *tdata) { - uint64_t n_states = tdata->n_stops * RRRR_DEFAULT_MAX_ROUNDS; + uint64_t n_states = ((uint64_t) RRRR_DEFAULT_MAX_ROUNDS) * tdata->n_stop_points; router->tdata = tdata; - router->best_time = (rtime_t *) malloc(sizeof(rtime_t) * tdata->n_stops); - router->states_back_journey_pattern = (uint32_t *) malloc(sizeof(uint32_t) * n_states); - router->states_back_vehicle_journey = (uint32_t *) malloc(sizeof(uint32_t) * n_states); + router->best_time = (rtime_t *) malloc(sizeof(rtime_t) * tdata->n_stop_points); + router->states_back_journey_pattern = (jpidx_t *) malloc(sizeof(jpidx_t) * n_states); + router->states_back_vehicle_journey = (jp_vjoffset_t *) malloc(sizeof(jp_vjoffset_t) * n_states); router->states_ride_from = (spidx_t *) malloc(sizeof(spidx_t) * n_states); router->states_walk_from = (spidx_t *) malloc(sizeof(spidx_t) * n_states); router->states_walk_time = (rtime_t *) malloc(sizeof(rtime_t) * n_states); @@ -56,12 +27,12 @@ bool router_setup(router_t *router, tdata_t *tdata) { router->states_board_time = (rtime_t *) malloc(sizeof(rtime_t) * n_states); #ifdef RRRR_FEATURE_REALTIME_EXPANDED - router->states_back_journey_pattern_point = (uint16_t *) malloc(sizeof(uint16_t) * n_states); - router->states_journey_pattern_point = (uint16_t *) malloc(sizeof(uint16_t) * n_states); + router->states_back_journey_pattern_point = (jppidx_t *) malloc(sizeof(jppidx_t) * n_states); + router->states_journey_pattern_point = (jppidx_t *) malloc(sizeof(jppidx_t) * n_states); #endif - router->updated_stops = bitset_new(tdata->n_stops); - router->updated_walk_stops = bitset_new(tdata->n_stops); + router->updated_stop_points = bitset_new(tdata->n_stop_points); + router->updated_walk_stop_points = bitset_new(tdata->n_stop_points); router->updated_journey_patterns = bitset_new(tdata->n_journey_patterns); #if RRRR_BANNED_JOURNEY_PATTERNS_BITMASK == 1 @@ -80,8 +51,8 @@ bool router_setup(router_t *router, tdata_t *tdata) { && router->states_back_journey_pattern_point && router->states_journey_pattern_point #endif - && router->updated_stops - && router->updated_walk_stops + && router->updated_stop_points + && router->updated_walk_stop_points && router->updated_journey_patterns #if RRRR_BANNED_JOURNEY_PATTERNS_BITMASK == 1 && router->banned_journey_patterns @@ -92,11 +63,7 @@ bool router_setup(router_t *router, tdata_t *tdata) { return false; } -#ifdef RRRR_FEATURE_LATLON - return router_setup_hashgrid (router); -#else return true; -#endif } void router_teardown(router_t *router) { @@ -112,37 +79,27 @@ void router_teardown(router_t *router) { free(router->states_back_journey_pattern_point); free(router->states_journey_pattern_point); #endif - bitset_destroy(router->updated_stops); - bitset_destroy(router->updated_walk_stops); + bitset_destroy(router->updated_stop_points); + bitset_destroy(router->updated_walk_stop_points); bitset_destroy(router->updated_journey_patterns); #if RRRR_BANNED_JOURNEY_PATTERNS_BITMASK == 1 bitset_destroy(router->banned_journey_patterns); #endif - -#ifdef RRRR_FEATURE_LATLON - hashgrid_teardown (&router->hg); -#endif } void router_reset(router_t *router) { - /* Make sure both origin and target are initialised with NONE, so it - * becomes possible to validate that they have been set to a valid - * stop index. - */ - router->origin = STOP_NONE; - router->target = STOP_NONE; - - /* The best times to arrive at a stop scratch space is initialised with + /* The best times to arrive at a stop_point scratch space is initialised with * UNREACHED. This allows to compare for a lesser time candidate in the * search. */ - rrrr_memset (router->best_time, UNREACHED, router->tdata->n_stops); + rrrr_memset (router->best_time, UNREACHED, router->tdata->n_stop_points); + rrrr_memset (router->states_board_time, UNREACHED, router->tdata->n_stop_points); } static bool initialize_states (router_t *router) { - uint64_t i_state = ((uint64_t) RRRR_DEFAULT_MAX_ROUNDS) * router->tdata->n_stops; + uint64_t i_state = ((uint64_t) RRRR_DEFAULT_MAX_ROUNDS) * router->tdata->n_stop_points; do { i_state--; @@ -171,7 +128,7 @@ static bool initialize_servicedays (router_t *router, router_request_t *req) { #ifdef RRRR_FAKE_REALTIME realtime_mask = ~((calendar_t) 0); #else - realtime_mask = ((calendar_t) 1) << ((time(NULL) - router->tdata->calendar_start_time) / + realtime_mask = ((calendar_t) 1) << (((unsigned long) (time(NULL)) - router->tdata->calendar_start_time) / SEC_IN_ONE_DAY); #endif @@ -194,7 +151,7 @@ static bool initialize_servicedays (router_t *router, router_request_t *req) { router->day_mask |= tomorrow.mask; day_i++; } - if (req->time_cutoff < tomorrow.midnight && req->time > today.midnight) { + if (req->time_cutoff < (today.midnight + router->tdata->max_time) && req->time > today.midnight) { router->servicedays[day_i] = today; router->day_mask |= today.mask; day_i++; @@ -212,7 +169,7 @@ static bool initialize_servicedays (router_t *router, router_request_t *req) { router->servicedays[day_i] = yesterday; day_i++; } - if (req->time_cutoff > today.midnight && req->time < tomorrow.midnight) { + if (req->time_cutoff >= today.midnight && req->time < (today.midnight + router->tdata->max_time)) { router->servicedays[day_i] = today; router->day_mask |= today.mask; day_i++; @@ -237,12 +194,12 @@ static bool initialize_servicedays (router_t *router, router_request_t *req) { return true; } -/* Given a stop index, mark all journey_patterns that serve it as updated. */ -static void flag_journey_patterns_for_stop(router_t *router, router_request_t *req, - uint32_t stop_index) { - uint32_t *journey_patterns; - uint32_t i_jp = tdata_journey_patterns_for_stop(router->tdata, stop_index, - &journey_patterns); +/* Given a stop_point index, mark all journey_patterns that serve it as updated. */ +static void flag_journey_patterns_for_stop_point(router_t *router, router_request_t *req, + spidx_t sp_index) { + jpidx_t *journey_patterns; + uint32_t i_jp = tdata_journey_patterns_for_stop_point(router->tdata, sp_index, + &journey_patterns); if (i_jp == 0) return; @@ -252,8 +209,8 @@ static void flag_journey_patterns_for_stop(router_t *router, router_request_t *r i_jp--; #ifdef RRRR_INFO - fprintf (stderr, "flagging journey_pattern %d at stop %d\n", - journey_patterns[i_jp], stop_index); + fprintf (stderr, "flagging journey_pattern %d at stop_point %d\n", + journey_patterns[i_jp], sp_index); #endif jp_active_flags = router->tdata->journey_pattern_active[journey_patterns[i_jp]]; @@ -278,16 +235,16 @@ static void flag_journey_patterns_for_stop(router_t *router, router_request_t *r #ifdef RRRR_FEATURE_REALTIME_EXPANDED if (router->servicedays[1].apply_realtime && - router->tdata->rt_journey_patterns_at_stop[stop_index]) { - journey_patterns = router->tdata->rt_journey_patterns_at_stop[stop_index]->list; - i_jp = router->tdata->rt_journey_patterns_at_stop[stop_index]->len; + router->tdata->rt_journey_patterns_at_stop_point[sp_index]) { + journey_patterns = router->tdata->rt_journey_patterns_at_stop_point[sp_index]->list; + i_jp = router->tdata->rt_journey_patterns_at_stop_point[sp_index]->len; if (i_jp == 0) return; do { i_jp--; #ifdef RRRR_INFO - fprintf (stderr, " flagging changed journey_pattern %d at stop %d\n", - journey_patterns[i_jp], stop_index); + fprintf (stderr, " flagging changed journey_pattern %d at stop_point %d\n", + journey_patterns[i_jp], sp_index); #endif /* extra journey_patterns should only be applied on the current day */ if ((req->mode & router->tdata->journey_patterns[journey_patterns[i_jp]].attributes) > 0) { @@ -304,7 +261,9 @@ static void flag_journey_patterns_for_stop(router_t *router, router_request_t *r #if RRRR_MAX_BANNED_JOURNEY_PATTERNS > 0 #if RRRR_BANNED_JOURNEY_PATTERNS_BITMASK == 1 static void unflag_banned_journey_patterns (router_t *router, router_request_t *req) { - if (req->n_banned_journey_patterns == 0) return; + if (req->n_banned_journey_patterns == 0 && + req->n_banned_operators == 0 && + req->n_operators == 0) return; bitset_mask_and (router->updated_journey_patterns, router->banned_journey_patterns); } @@ -321,6 +280,32 @@ static void initialize_banned_journey_patterns (router_t *router, router_request req->banned_journey_patterns[i_banned_jp]); } while (i_banned_jp); } + +#if RRRR_MAX_FILTERED_OPERATORS > 0 +static void initialize_filtered_operators (router_t *router, router_request_t *req) { + if (req->n_operators > 0) { + jpidx_t jp_index = (jpidx_t) router->tdata->n_journey_patterns; + while (jp_index) { + jp_index--; + if (!set_in_uint8(req->operators, req->n_operators, + tdata_operator_idx_for_journey_pattern(router->tdata, jp_index))) { + bitset_unset (router->banned_journey_patterns, jp_index); + } + } + } else if (req->n_banned_operators > 0) { + /* TODO: should this be moved into the jp selection? */ + jpidx_t jp_index = (jpidx_t) router->tdata->n_journey_patterns; + while (jp_index) { + jp_index--; + if (set_in_uint8(req->banned_operators, req->n_banned_operators, + tdata_operator_idx_for_journey_pattern(router->tdata, jp_index))) { + bitset_unset (router->banned_journey_patterns, jp_index); + } + } + } +} +#endif + #else static void unflag_banned_journey_patterns(router_t *router, router_request_t *req) { uint8_t i_banned_jp = req->n_banned_journey_patterns; @@ -331,48 +316,33 @@ static void unflag_banned_journey_patterns(router_t *router, router_request_t *r req->banned_journey_patterns[i_banned_jp]); } while (i_banned_jp); } -#endif -#endif - -#if RRRR_MAX_BANNED_STOPS > 0 -static void unflag_banned_stops (router_t *router, router_request_t *req) { - uint8_t i_banned_stop = req->n_banned_stops; - if (i_banned_stop == 0) return; - do { - i_banned_stop--; - bitset_unset (router->updated_stops, - req->banned_stops[i_banned_stop]); - } while (i_banned_stop); -} -#endif -#if RRRR_MAX_BANNED_STOPS > 0 || RRRR_BAX_BANNED_STOPS_HARD > 0 -static bool set_in (spidx_t *set, uint8_t length, spidx_t value) { - uint8_t i = length; - if (i == 0) return false; +static bool journey_pattern_is_banned(router_request_t *req, jpidx_t jp_index){ + uint8_t i_banned_jp = req->n_banned_journey_patterns; + if (i_banned_jp == 0) return false; do { - i--; - if (set[i] == value) return true; - } while (i); + i_banned_jp--; + if (req->banned_journey_patterns[i_banned_jp] == jp_index){ + return true; + } + } while (i_banned_jp); return false; } #endif +#endif -#if RRRR_MAX_BANNED_VEHICLE_JOURNEYS > 0 -static bool set2_in (uint32_t *set1, uint16_t *set2, uint8_t length, - uint32_t value1, uint16_t value2) { - uint8_t i = length; - if (i == 0) return false; +#if RRRR_MAX_BANNED_STOP_POINTS > 0 +static void unflag_banned_stop_points(router_t *router, router_request_t *req) { + uint8_t i_banned_sp = req->n_banned_stops; + if (i_banned_sp == 0) return; do { - i--; - if (set1[i] == value1 && - set2[i] == value2) return true; - } while (i); - return false; + i_banned_sp--; + bitset_unset (router->updated_stop_points, + req->banned_stops[i_banned_sp]); + } while (i_banned_sp); } #endif -#ifdef RRRR_FEATURE_LATLON /* Because the first round begins with so few reached stops, the initial state * doesn't get its own full array of states * Instead we reuse one of the later rounds (round 1) for the initial state. @@ -381,47 +351,20 @@ static bool set2_in (uint32_t *set1, uint16_t *set2, uint8_t length, * maintain a list of all the stops that might have been added by the hashgrid. */ static void initialize_transfers_full (router_t *router, uint32_t round) { - uint32_t i_state = router->tdata->n_stops; - rtime_t *states_walk_time = router->states_walk_time + (round * router->tdata->n_stops); + uint32_t i_state = router->tdata->n_stop_points; + rtime_t *states_walk_time = router->states_walk_time + (round * router->tdata->n_stop_points); do { i_state--; states_walk_time[i_state] = UNREACHED; } while (i_state); } -#else - -/* Because the first round begins with so few reached stops, the initial state - * doesn't get its own full array of states. Instead we reuse one of the later - * rounds (round 1) for the initial state. This means we need to reset the - * walks in round 1 back to UNREACHED before using them in routing. Rather - * than iterating over all of them, we only initialize the stops that can be - * reached by transfers. - * Alternatively we could instead initialize walks to UNREACHED at the - * beginning of the transfer calculation function. - * We should not however reset the best times for those stops reached from the - * initial stop on foot. This will prevent finding circuitous itineraries that - * return to them. - */ -static void initialize_transfers (router_t *router, - uint32_t round, spidx_t stop_index_from) { - rtime_t *states_walk_time = router->states_walk_time + (round * router->tdata->n_stops); - uint32_t t = router->tdata->stops[stop_index_from ].transfers_offset; - uint32_t tN = router->tdata->stops[stop_index_from + 1].transfers_offset; - states_walk_time[stop_index_from] = UNREACHED; - for ( ; t < tN ; ++t) { - spidx_t stop_index_to = router->tdata->transfer_target_stops[t]; - states_walk_time[stop_index_to] = UNREACHED; - } -} -#endif - /* Get the departure or arrival time of the given vj on the given * service day, applying realtime data as needed. */ static rtime_t tdata_stoptime (tdata_t* tdata, serviceday_t *serviceday, - uint32_t jp_index, uint32_t vj_offset, uint32_t journey_pattern_point, + jpidx_t jp_index, jp_vjoffset_t vj_offset, jppidx_t journey_pattern_point, bool arrive) { rtime_t time, time_adjusted; stoptime_t *vj_stoptimes; @@ -435,7 +378,7 @@ tdata_stoptime (tdata_t* tdata, serviceday_t *serviceday, if (serviceday->apply_realtime) { /* the expanded stoptimes can be found at the same row as the vehicle_journey */ - vj_stoptimes = tdata->vj_stoptimes[tdata->journey_patterns[jp_index].vj_ids_offset + vj_offset]; + vj_stoptimes = tdata->vj_stoptimes[tdata->journey_patterns[jp_index].vj_index + vj_offset]; if (vj_stoptimes) { /* if the expanded stoptimes have been added, @@ -466,12 +409,6 @@ tdata_stoptime (tdata_t* tdata, serviceday_t *serviceday, time_adjusted = time + serviceday->midnight; - /* - printf ("boarding at stop %d, time is: %s \n", journey_pattern_point, timetext (time)); - printf (" after adjusting: %s \n", timetext (time_adjusted)); - printf (" midnight: %d \n", serviceday->midnight); - */ - /* Detect overflow (this will still not catch wrapping due to negative * delays on small positive times) actually this happens naturally with * times like '03:00+1day' transposed to serviceday 'tomorrow' @@ -482,134 +419,140 @@ tdata_stoptime (tdata_t* tdata, serviceday_t *serviceday, /* TODO: change the function name of tdata_next */ static bool -tdata_next (router_t *router, router_request_t *req, - uint32_t jp_index, uint32_t vj_offset, rtime_t qtime, - spidx_t *ret_stop_index, rtime_t *ret_stop_time) { +tdata_next (router_t *router, bool arrive_by, + jpidx_t jp_index, jp_vjoffset_t vj_offset, rtime_t qtime, + spidx_t *ret_sp_index, rtime_t *ret_stop_time, jppidx_t * ret_jpp_offset) { spidx_t *journey_pattern_points = tdata_points_for_journey_pattern(router->tdata, jp_index); journey_pattern_t *jp = router->tdata->journey_patterns + jp_index; + serviceday_t *serviceday; uint32_t jpp_i; - *ret_stop_index = STOP_NONE; + *ret_sp_index = STOP_NONE; *ret_stop_time = UNREACHED; + *ret_jpp_offset = 0; - for (jpp_i = 0; jpp_i < jp->n_stops; ++jpp_i) { - /* TODO: check if the arrive = false flag works with req->arrive_by */ - - rtime_t time = tdata_stoptime (router->tdata, &(router->servicedays[1]), - jp_index, vj_offset, jpp_i, false); + for (serviceday = router->servicedays; + serviceday < router->servicedays + router->n_servicedays; + ++serviceday) { - /* Find stop immediately after the given time on the given vj. */ - if (req->arrive_by ? time > qtime : time < qtime) { - if (*ret_stop_time == UNREACHED || - (req->arrive_by ? time < *ret_stop_time : - time > *ret_stop_time)) { - *ret_stop_index = (spidx_t) journey_pattern_points[jpp_i]; - *ret_stop_time = time; + for (jpp_i = 0; jpp_i < jp->n_stops; ++jpp_i) { + /* TODO: check if the arrive = false flag works with arrive_by */ + rtime_t time = tdata_stoptime(router->tdata, serviceday, + jp_index, vj_offset, (jppidx_t) jpp_i, false); + + /* Find stop_point immediately after the given time on the given vj. */ + if (arrive_by ? time > qtime : time < qtime) { + if (*ret_stop_time == UNREACHED || + (arrive_by ? time < *ret_stop_time : + time > *ret_stop_time)) { + *ret_sp_index = (spidx_t) journey_pattern_points[jpp_i]; + *ret_stop_time = time; + *ret_jpp_offset = (jppidx_t) jpp_i; + } } } } - return (*ret_stop_index != STOP_NONE); + return (*ret_sp_index != STOP_NONE); } -/* For each updated stop and each destination of a transfer from an updated - * stop, set the associated journey_patterns as updated. The journey_patterns bitset is cleared - * before the operation, and the stops bitset is cleared after all transfers +/* For each updated stop_point and each destination of a transfer from an updated + * stop_point, set the associated journey_patterns as updated. The journey_patterns bitset is cleared + * before the operation, and the stop_points bitset is cleared after all transfers * have been computed and all journey_patterns have been set. * Transfer results are computed within the same round, based on arrival time * in the ride phase and stored in the walk time member of states. */ static void apply_transfers (router_t *router, router_request_t *req, uint32_t round, bool transfer) { - rtime_t *states_time = router->states_time + (round * router->tdata->n_stops); - rtime_t *states_walk_time = router->states_walk_time + (round * router->tdata->n_stops); - spidx_t *states_walk_from = router->states_walk_from + (round * router->tdata->n_stops); - uint32_t stop_index_from; /* uint32_t: because we need to compare to BITSET_NONE */ + rtime_t *states_time = router->states_time + (round * router->tdata->n_stop_points); + rtime_t *states_walk_time = router->states_walk_time + (round * router->tdata->n_stop_points); + spidx_t *states_walk_from = router->states_walk_from + (round * router->tdata->n_stop_points); + uint32_t sp_index_from; /* uint32_t: because we need to compare to BITSET_NONE */ /* The transfer process will flag journey_patterns that should be explored in * the next round. */ bitset_clear (router->updated_journey_patterns); - for (stop_index_from = bitset_next_set_bit (router->updated_stops, 0); - stop_index_from != BITSET_NONE; - stop_index_from = bitset_next_set_bit (router->updated_stops, stop_index_from + 1)) { - rtime_t time_from = states_time[stop_index_from]; + for (sp_index_from = bitset_next_set_bit (router->updated_stop_points, 0); + sp_index_from != BITSET_NONE; + sp_index_from = bitset_next_set_bit (router->updated_stop_points, sp_index_from + 1)) { + rtime_t time_from = states_time[sp_index_from]; #ifdef RRRR_INFO - printf ("stop %d was marked as updated \n", stop_index_from); + printf ("stop_point %d was marked as updated \n", sp_index_from); #endif if (time_from == UNREACHED) { - fprintf (stderr, "ERROR: transferring from unreached stop %d in round %d. \n", stop_index_from, round); + fprintf (stderr, "ERROR: transferring from unreached stop_point %d in round %d. \n", sp_index_from, round); continue; } - /* At this point, the best time at the from stop may be different than + /* At this point, the best time at the from stop_point may be different than * the state_from->time, because the best time may have been updated * by a transfer. */ #ifdef RRRR_DEBUG - if (time_from != router->best_time[stop_index_from]) { + if (time_from != router->best_time[sp_index_from]) { char buf[13]; - fprintf (stderr, "ERROR: time at stop %d in round %d " \ + fprintf (stderr, "ERROR: time at stop_point %d in round %d " \ "is not the same as its best time. \n", - stop_index_from, round); + sp_index_from, round); fprintf (stderr, " from time %s \n", btimetext(time_from, buf)); fprintf (stderr, " walk time %s \n", - btimetext(states_walk_time[stop_index_from], buf)); + btimetext(states_walk_time[sp_index_from], buf)); fprintf (stderr, " best time %s \n", - btimetext(router->best_time[stop_index_from], buf)); + btimetext(router->best_time[sp_index_from], buf)); continue; } #endif #ifdef RRRR_INFO - fprintf (stderr, " applying transfer at %d (%s) \n", stop_index_from, - tdata_stop_name_for_index(router->tdata, stop_index_from)); + fprintf (stderr, " applying transfer at %d (%s) \n", sp_index_from, + tdata_stop_point_name_for_index(router->tdata, sp_index_from)); #endif - /* First apply a transfer from the stop to itself, - * if case that's the best way. - */ - if (states_time[stop_index_from] == router->best_time[stop_index_from]) { + if (states_time[sp_index_from] == router->best_time[sp_index_from]) { + rtime_t sp_waittime = tdata_stop_point_waittime(router->tdata, (spidx_t) sp_index_from); /* This state's best time is still its own. * No improvements from other transfers. */ - states_walk_time[stop_index_from] = time_from; - states_walk_from[stop_index_from] = (spidx_t) stop_index_from; + if (req->arrive_by){ + states_walk_time[sp_index_from] = time_from - sp_waittime; + }else{ + states_walk_time[sp_index_from] = time_from + sp_waittime; + } + states_walk_from[sp_index_from] = (spidx_t) sp_index_from; /* assert (router->best_time[stop_index_from] == time_from); */ - bitset_set(router->updated_walk_stops, stop_index_from); + bitset_set(router->updated_walk_stop_points, sp_index_from); } + if (transfer) { - /* Then apply transfers from the stop to nearby stops */ - uint32_t tr = router->tdata->stops[stop_index_from ].transfers_offset; - uint32_t tr_end = router->tdata->stops[stop_index_from + 1].transfers_offset; + /* Then apply transfers from the stop_point to nearby stops */ + uint32_t tr = router->tdata->stop_points[sp_index_from].transfers_offset; + uint32_t tr_end = router->tdata->stop_points[sp_index_from + 1].transfers_offset; for ( ; tr < tr_end ; ++tr) { - /* Transfer distances are stored in units of 16 meters, - * rounded not truncated, in a uint8_t - */ - spidx_t stop_index_to = router->tdata->transfer_target_stops[tr]; - rtime_t transfer_duration = router->tdata->transfer_dist_meters[tr] + req->walk_slack; + /* Transfer durations are stored in r_time */ + spidx_t sp_index_to = router->tdata->transfer_target_stops[tr]; + rtime_t transfer_duration = router->tdata->transfer_durations[tr] + req->walk_slack; rtime_t time_to = req->arrive_by ? time_from - transfer_duration : time_from + transfer_duration; - /* Avoid reserved values including UNREACHED */ - if (time_to > RTIME_THREE_DAYS) continue; - /* Catch wrapping/overflow due to limited range of rtime_t + /* Avoid reserved values including UNREACHED + * and catch wrapping/overflow due to limited range of rtime_t * this happens normally on overnight routing but should * be avoided rather than caught. */ - if (req->arrive_by ? time_to > time_from : - time_to < time_from) continue; + if (time_to > RTIME_THREE_DAYS || (req->arrive_by ? time_to > time_from :time_to < time_from)) continue; #ifdef RRRR_INFO { char buf[13]; fprintf (stderr, " target %d %s (%s) \n", - stop_index_to, - btimetext(router->best_time[stop_index_to], buf), - tdata_stop_name_for_index(router->tdata, stop_index_to)); + sp_index_to, + btimetext(router->best_time[sp_index_to], buf), + tdata_stop_point_name_for_index(router->tdata, sp_index_to)); fprintf (stderr, " transfer time %s\n", btimetext(transfer_duration, buf)); @@ -620,29 +563,29 @@ static void apply_transfers (router_t *router, router_request_t *req, #endif /* TODO verify state_to->walk_time versus - * router->best_time[stop_index_to] */ - if (router->best_time[stop_index_to] == UNREACHED || - (req->arrive_by ? time_to > router->best_time[stop_index_to] - : time_to < router->best_time[stop_index_to])) { + * router->best_time[sp_index_to] */ + if (router->best_time[sp_index_to] == UNREACHED || + (req->arrive_by ? time_to > router->best_time[sp_index_to] + : time_to < router->best_time[sp_index_to])) { #ifdef RRRR_INFO char buf[13]; fprintf (stderr, " setting %d to %s\n", - stop_index_to, btimetext(time_to, buf)); + sp_index_to, btimetext(time_to, buf)); #endif - states_walk_time[stop_index_to] = time_to; - states_walk_from[stop_index_to] = stop_index_from; - router->best_time[stop_index_to] = time_to; - bitset_set(router->updated_walk_stops, stop_index_to); + states_walk_time[sp_index_to] = time_to; + states_walk_from[sp_index_to] = (spidx_t) sp_index_from; + router->best_time[sp_index_to] = time_to; + bitset_set(router->updated_walk_stop_points, sp_index_to); } } } } - for (stop_index_from = bitset_next_set_bit (router->updated_walk_stops, 0); - stop_index_from != BITSET_NONE; - stop_index_from = bitset_next_set_bit (router->updated_walk_stops, stop_index_from + 1)) { - flag_journey_patterns_for_stop(router, req, stop_index_from); + for (sp_index_from = bitset_next_set_bit (router->updated_walk_stop_points, 0); + sp_index_from != BITSET_NONE; + sp_index_from = bitset_next_set_bit (router->updated_walk_stop_points, sp_index_from + 1)) { + flag_journey_patterns_for_stop_point(router, req, (spidx_t) sp_index_from); } #if RRRR_MAX_BANNED_JOURNEY_PATTERNS > 0 @@ -650,9 +593,9 @@ static void apply_transfers (router_t *router, router_request_t *req, #endif /* Done with all transfers, reset stop-reached bits for the next round */ - bitset_clear (router->updated_stops); - bitset_clear (router->updated_walk_stops); - /* Check invariant: Every stop reached in this round should have a + bitset_clear (router->updated_stop_points); + bitset_clear (router->updated_walk_stop_points); + /* Check invariant: Every stop_point reached in this round should have a * best time equal to its walk time, and * a walk arrival time <= its ride arrival time. */ @@ -664,11 +607,11 @@ static void apply_transfers (router_t *router, router_request_t *req, * unless we reach a vehicle_journey that matches our requirements (valid departure, valid calendar, valid trip_attributes) */ static void board_vehicle_journeys_within_days(router_t *router, router_request_t *req, - uint32_t jp_index, - uint16_t jpp_offset, + jpidx_t jp_index, + jppidx_t jpp_offset, rtime_t prev_time, serviceday_t **best_serviceday, - uint32_t *best_vj, rtime_t *best_time) { + jp_vjoffset_t *best_vj, rtime_t *best_time) { calendar_t *vj_masks = tdata_vj_masks_for_journey_pattern(router->tdata, jp_index); vehicle_journey_t *vjs_in_journey_pattern = tdata_vehicle_journeys_in_journey_pattern(router->tdata, jp_index); @@ -678,8 +621,8 @@ static void board_vehicle_journeys_within_days(router_t *router, router_request_ /* Search through the servicedays that are assumed to be put in search-order (counterclockwise for arrive_by) */ - for (serviceday = router->servicedays; - serviceday <= router->servicedays + router->n_servicedays; + for (serviceday = router->servicedays; + serviceday < router->servicedays + router->n_servicedays; ++serviceday) { int32_t i_vj_offset; @@ -696,7 +639,7 @@ static void board_vehicle_journeys_within_days(router_t *router, router_request_ * scanning additional days. Note that day list is * reversed for arrive-by searches. */ - if (*best_vj != NONE && !jp_overlap) break; + if (*best_vj != VJ_NONE && !jp_overlap) break; for (i_vj_offset = req->arrive_by ? jp->n_vjs - 1: 0; req->arrive_by ? i_vj_offset >= 0 @@ -711,28 +654,15 @@ static void board_vehicle_journeys_within_days(router_t *router, router_request_ fprintf(stderr, "\n"); #endif - #if RRRR_MAX_BANNED_VEHICLE_JOURNEYS > 0 - /* skip this vj if it is banned */ - if (set2_in(req->banned_vjs_journey_pattern, req->banned_vjs_offset, - req->n_banned_vjs, jp_index, - i_vj_offset)) continue; - #endif - /* skip this vj if it is not running on * the current service day */ if ( ! (serviceday->mask & vj_masks[i_vj_offset])) continue; - /* skip this vj if it doesn't have all our - * required attributes - * Checking whether we have required req->vj_attributes at all, before checking the attributes of the vehicle_journeys - * is about 4% more efficient for journeys without specific vj attribute requirements. - */ - if (req->vj_attributes && ! ((req->vj_attributes & vjs_in_journey_pattern[i_vj_offset].vj_attributes) == req->vj_attributes)) continue; /* consider the arrival or departure time on * the current service day */ - time = tdata_stoptime (router->tdata, serviceday, jp_index, i_vj_offset, jpp_offset, req->arrive_by); + time = tdata_stoptime (router->tdata, serviceday, jp_index, (jp_vjoffset_t) i_vj_offset, jpp_offset, req->arrive_by); #ifdef RRRR_DEBUG_VEHICLE_JOURNEY fprintf(stderr, " board option %d at %s \n", i_vj_offset, ""); @@ -743,6 +673,7 @@ static void board_vehicle_journeys_within_days(router_t *router, router_request_ if (req->arrive_by ? time < req->time_cutoff : time > req->time_cutoff){ + if (jp_overlap) continue; return; } @@ -754,7 +685,21 @@ static void board_vehicle_journeys_within_days(router_t *router, router_request_ */ if (req->arrive_by ? time <= prev_time && time > *best_time : time >= prev_time && time < *best_time) { - *best_vj = i_vj_offset; + /* skip this vj if it doesn't have all our + * required attributes + * Checking whether we have required req->vj_attributes at all, before checking the attributes of the vehicle_journeys + * is about 4% more efficient for journeys without specific vj attribute requirements. + */ + if (req->vj_attributes && (req->vj_attributes & vjs_in_journey_pattern[i_vj_offset].vj_attributes) != req->vj_attributes) continue; + + #if RRRR_MAX_BANNED_VEHICLE_JOURNEYS > 0 + /* skip this vj if it is banned */ + if (set_in_vj (req->banned_vjs_journey_pattern, req->banned_vjs_offset, + req->n_banned_vjs, jp_index, + (jp_vjoffset_t) i_vj_offset)) continue; + #endif + + *best_vj = (jp_vjoffset_t) i_vj_offset; *best_time = time; *best_serviceday = serviceday; /* Since FIFO ordering of trips is ensured, we can immediately return if the JourneyPattern does not overlap. @@ -774,13 +719,13 @@ static void board_vehicle_journeys_within_days(router_t *router, router_request_ * We are scanning counterclockwise for arrive_by and clockwise for depart_after. */ static void reboard_vehicle_journeys_within_days(router_t *router, router_request_t *req, - uint32_t jp_index, - uint16_t jpp_offset, + jpidx_t jp_index, + jppidx_t jpp_offset, serviceday_t *prev_serviceday, - uint16_t prev_vj_offset, + jp_vjoffset_t prev_vj_offset, rtime_t prev_time, serviceday_t **best_serviceday, - uint32_t *best_vj, rtime_t *best_time) { + jp_vjoffset_t *best_vj, rtime_t *best_time) { calendar_t *vj_masks = tdata_vj_masks_for_journey_pattern(router->tdata, jp_index); vehicle_journey_t *vjs_in_journey_pattern = tdata_vehicle_journeys_in_journey_pattern(router->tdata, jp_index); @@ -813,28 +758,15 @@ static void reboard_vehicle_journeys_within_days(router_t *router, router_reques fprintf(stderr, "\n"); #endif - #if RRRR_MAX_BANNED_VEHICLE_JOURNEYS > 0 - /* skip this vj if it is banned */ - if (set2_in(req->banned_vjs_journey_pattern, req->banned_vjs_offset, - req->n_banned_vjs, jp_index, - i_vj_offset)) continue; - #endif - /* skip this vj if it is not running on * the current service day */ if ( ! (serviceday->mask & vj_masks[i_vj_offset])) continue; - /* skip this vj if it doesn't have all our - * required attributes - * Checking whether we have required req->vj_attributes at all, before checking the attributes of the vehicle_journeys - * is about 4% more efficient for journeys without specific vj attribute requirements. - */ - if (req->vj_attributes && ! ((req->vj_attributes & vjs_in_journey_pattern[i_vj_offset].vj_attributes) == req->vj_attributes)) continue; /* consider the arrival or departure time on * the current service day */ - time = tdata_stoptime (router->tdata, serviceday, jp_index, i_vj_offset, jpp_offset, req->arrive_by); + time = tdata_stoptime (router->tdata, serviceday, jp_index, (jp_vjoffset_t) i_vj_offset, jpp_offset, req->arrive_by); #ifdef RRRR_DEBUG_VEHICLE_JOURNEY fprintf(stderr, " board option %d at %s \n", i_vj_offset, ""); @@ -856,50 +788,82 @@ static void reboard_vehicle_journeys_within_days(router_t *router, router_reques */ if (req->arrive_by ? time <= prev_time && time > *best_time : time >= prev_time && time < *best_time) { - *best_vj = i_vj_offset; + + /* skip this vj if it doesn't have all our + * required attributes + * Checking whether we have required req->vj_attributes at all, before checking the attributes of the vehicle_journeys + * is about 4% more efficient for journeys without specific vj attribute requirements. + */ + if (req->vj_attributes && (req->vj_attributes & vjs_in_journey_pattern[i_vj_offset].vj_attributes) != req->vj_attributes) continue; + + #if RRRR_MAX_BANNED_VEHICLE_JOURNEYS > 0 + /* skip this vj if it is banned */ + if (set_in_vj (req->banned_vjs_journey_pattern, req->banned_vjs_offset, + req->n_banned_vjs, jp_index, (jp_vjoffset_t) i_vj_offset)) + continue; + #endif + + *best_vj = (jp_vjoffset_t) i_vj_offset; *best_time = time; *best_serviceday = serviceday; } } /* end for (vehicle_journey's within this route) */ /* Reset the VJ offset to the highest value, after we scanned the original serviceday */ - prev_vj_offset = req->arrive_by ? 0 : jp->n_vjs - 1; + prev_vj_offset = (jp_vjoffset_t) (req->arrive_by ? 0 : jp->n_vjs - 1); } /* end for (service days: yesterday, today, tomorrow) */ } static bool write_state(router_t *router, router_request_t *req, - uint8_t round, uint32_t jpp_index, uint32_t vj_offset, - spidx_t stop_index, uint16_t jpp_offset, rtime_t time, - spidx_t board_stop, uint16_t board_jpp_stop, + uint8_t round, jpidx_t jp_index, jp_vjoffset_t vj_offset, + spidx_t sp_index, jppidx_t jpp_offset, rtime_t time, + spidx_t board_stop, jppidx_t board_jpp_offset, rtime_t board_time) { - uint64_t i_state = ((uint64_t) round) * router->tdata->n_stops + stop_index; + uint64_t i_state = ((uint64_t) round) * router->tdata->n_stop_points + sp_index; #ifndef RRRR_REALTIME UNUSED (jpp_offset); - UNUSED (board_jpp_stop); + UNUSED (board_jpp_offset); #endif #ifdef RRRR_INFO { char buf[13]; - fprintf(stderr, " setting stop to %s \n", btimetext(time, buf)); + fprintf(stderr, " setting stop_point to %s \n", btimetext(time, buf)); } #endif - router->best_time[stop_index] = time; + router->best_time[sp_index] = time; router->states_time[i_state] = time; - router->states_back_journey_pattern[i_state] = jpp_index; + router->states_back_journey_pattern[i_state] = jp_index; router->states_back_vehicle_journey[i_state] = vj_offset; router->states_ride_from[i_state] = board_stop; router->states_board_time[i_state] = board_time; #ifdef RRRR_FEATURE_REALTIME_EXPANDED - router->states_back_journey_pattern_point[i_state] = board_jpp_stop; + router->states_back_journey_pattern_point[i_state] = board_jpp_offset; router->states_journey_pattern_point[i_state] = jpp_offset; #endif + { /* Target pruning, section 3.1 of RAPTOR paper. */ + /* TODO consider a buffer that allows for less optimal (in time) trips + but for more optimal trips in other factors (price, no operator change,etc.) + */ + street_network_t *target = req->arrive_by ? &req->entry : &req->exit; + int32_t i_target = target->n_points; + while (i_target){ + --i_target; + if (target->stop_points[i_target] == sp_index + && (( req->arrive_by && time - target->durations[i_target] > req->time_cutoff) || + (!req->arrive_by && time + target->durations[i_target] < req->time_cutoff))){ + req->time_cutoff = req->arrive_by ? time - target->durations[i_target] : + time + target->durations[i_target]; + } + } + } + #ifdef RRRR_STRICT if (req->arrive_by && board_time < time) { fprintf (stderr, "board time non-decreasing\n"); @@ -908,59 +872,196 @@ write_state(router_t *router, router_request_t *req, fprintf (stderr, "board time non-increasing\n"); return false; } + #else + UNUSED (req); #endif return true; } +static void vehicle_journey_extend(router_t *router, router_request_t *req, uint8_t round, serviceday_t *board_serviceday, jpidx_t from_jp_index, + vehicle_journey_ref_t *interline, rtime_t *states_walk_time) { + jpidx_t jp_index = interline->jp_index; + jp_vjoffset_t vj_offset = interline->vj_offset; + while (jp_index != JP_NONE){ + journey_pattern_t *jp = &(router->tdata->journey_patterns[jp_index]); + spidx_t *journey_pattern_points = tdata_points_for_journey_pattern(router->tdata, (jpidx_t) jp_index); + uint8_t *journey_pattern_point_attributes = tdata_stop_point_attributes_for_journey_pattern(router->tdata, (jpidx_t) jp_index); + + /* journey_pattern_point index where that vj was boarded */ + jppidx_t board_jpp = (jppidx_t) (req->arrive_by ? jp->n_stops-1 :0); + + /* stop_point index where that vj was boarded */ + spidx_t board_sp = journey_pattern_points[board_jpp]; + + /* time when that vj was boarded */ + rtime_t board_time = tdata_stoptime (router->tdata, board_serviceday, jp_index, vj_offset, board_jpp, !req->arrive_by); + + rtime_t time; + int32_t jpp_offset; + + { + rtime_t prev_time = states_walk_time[board_sp]; + if (prev_time == UNREACHED || + req->arrive_by ? prev_time < board_time: + prev_time > board_time ){ + /* Do not extend with Vehicle Journey's that are already reachable */ + return; + } + } + + #if RRRR_BANNED_JOURNEY_PATTERNS_BITMASK == 0 && RRRR_MAX_BANNED_JOURNEY_PATTERNS > 0 + /* Check if the journey_pattern of this vj-extension is not banned */ + if (journey_pattern_is_banned(req,jp_index)){ + return; + } + #endif + + #if RRRR_MAX_BANNED_VEHICLE_JOURNEYS > 0 + /* Break the extension if this vj if it is banned */ + if (set_in_vj (req->banned_vjs_journey_pattern, req->banned_vjs_offset, + req->n_banned_vjs, jp_index, + (jp_vjoffset_t) vj_offset)) break; + #endif + + /* Lets assume the VJ extension is by defintion valid because of the earlier journey... */ + if ( !(board_serviceday->mask & tdata_vj_masks_for_journey_pattern(router->tdata, jp_index)[vj_offset])) break; + + /* Break this vj-extension if this VJ doesn't have all our + * required attributes + * Checking whether we have required req->vj_attributes at all, before checking the attributes of the vehicle_journeys + * is about 4% more efficient for journeys without specific vj attribute requirements. + */ + if (req->vj_attributes && (req->vj_attributes & tdata_vehicle_journeys_in_journey_pattern(router->tdata, jp_index)[vj_offset].vj_attributes) != req->vj_attributes) break; + + for (jpp_offset = (req->arrive_by ? jp->n_stops - 2 : 1); + req->arrive_by ? jpp_offset >= 0 : + jpp_offset < jp->n_stops; + req->arrive_by ? --jpp_offset : + ++jpp_offset) { + + spidx_t sp_index = journey_pattern_points[jpp_offset]; + bool forboarding = (journey_pattern_point_attributes[jpp_offset] & rsa_boarding); + bool foralighting = (journey_pattern_point_attributes[jpp_offset] & rsa_alighting); + time = tdata_stoptime (router->tdata, board_serviceday, + jp_index, vj_offset, (jppidx_t) jpp_offset,!req->arrive_by); + + /* overflow due to long overnight vehicle_journeys on day 2 */ + if (time == UNREACHED) continue; + + if (!(req->arrive_by ? forboarding : foralighting)){ + continue; + } + + /* TODO: make this variable and verify */ + if ((req->time_cutoff != UNREACHED) && + (req->arrive_by ? time < req->time_cutoff + : time > req->time_cutoff)) { + continue; + } + + /* Do we need best_time at all? + * Yes, because the best time may not have been found in the + * previous round. + */ + if (!((router->best_time[sp_index] == UNREACHED) || + (req->arrive_by ? time > router->best_time[sp_index] + : time < router->best_time[sp_index]))) { + #ifdef RRRR_INFO + fprintf(stderr, " (no improvement)\n"); + #endif + /* the current vj does not improve on the best time + * at this stop + */ + continue; + } + if (time > RTIME_THREE_DAYS) { + /* Reserve all time past three days for + * special values like UNREACHED. + */ + } else if (req->arrive_by ? time > req->time : + time < req->time) { + + /* Wrapping/overflow. This happens due to overnight + * vehicle_journeys on day 2. Prune them. + */ + + #ifdef RRRR_DEBUG + fprintf(stderr, "ERROR: setting state to time before" \ + "start time. journey_pattern %d vj %d stop_point %d \n", + jp_index, vj_offset, sp_index); + #endif + } else { +#ifdef RRRR_INTERLINE_DEBUG + char buf32[32]; + printf("Extend to %s @ %s\n", tdata_stop_point_name_for_index(router->tdata,sp_index), + btimetext(time, buf32)); +#endif + write_state(router, req, round, (jpidx_t) jp_index, vj_offset, + (spidx_t) sp_index, (jppidx_t) jpp_offset, time, + board_sp, board_jpp, board_time); + /* mark stop_point for next round. */ + bitset_set(router->updated_stop_points, sp_index); + } + } + interline = req->arrive_by ? + &router->tdata->vehicle_journey_transfers_backward[jp->vj_index+vj_offset] + : &router->tdata->vehicle_journey_transfers_forward [jp->vj_index+vj_offset]; + jp_index = JP_NONE; + vj_offset = VJ_NONE; + if (interline->jp_index != JP_NONE && interline->jp_index != from_jp_index) { + jp_index = interline->jp_index; + vj_offset = interline->vj_offset; + } + } +} + static void router_round(router_t *router, router_request_t *req, uint8_t round) { /* TODO restrict pointers? */ - rtime_t *states_walk_time = router->states_walk_time + (((round == 0) ? 1 : round - 1) * router->tdata->n_stops); + rtime_t *states_walk_time = router->states_walk_time + (((round == 0) ? 1 : round - 1) * router->tdata->n_stop_points); uint32_t jp_index; #ifdef RRRR_INFO fprintf(stderr, "round %d\n", round); #endif - /* Iterate over all journey_patterns which contain a stop that was updated in the last round. */ + /* Iterate over all journey_patterns which contain a stop_point that was updated in the last round. */ for (jp_index = bitset_next_set_bit (router->updated_journey_patterns, 0); jp_index != BITSET_NONE; jp_index = bitset_next_set_bit (router->updated_journey_patterns, jp_index + 1)) { journey_pattern_t *jp = &(router->tdata->journey_patterns[jp_index]); - spidx_t *journey_pattern_points = tdata_points_for_journey_pattern(router->tdata, jp_index); - uint8_t *journey_pattern_point_attributes = tdata_stop_attributes_for_journey_pattern(router->tdata, jp_index); + spidx_t *journey_pattern_points = tdata_points_for_journey_pattern(router->tdata, (jpidx_t) jp_index); + uint8_t *journey_pattern_point_attributes = tdata_stop_point_attributes_for_journey_pattern(router->tdata, (jpidx_t) jp_index); /* Service day on which that vj was boarded */ serviceday_t *board_serviceday = NULL; - /* vj index within the route. NONE means not yet boarded. */ - uint32_t vj_index = NONE; + /* vj index within the journey_pattern. VJ_NONE means not yet boarded. */ + jp_vjoffset_t vj_offset = VJ_NONE; - /* stop index where that vj was boarded */ - spidx_t board_stop = 0; + /* stop_point index where that vj was boarded */ + spidx_t board_sp = 0; /* journey_pattern_point index where that vj was boarded */ - uint16_t board_jpp = 0; + jppidx_t board_jpp = 0; /* time when that vj was boarded */ rtime_t board_time = 0; + /* Is true when there is a vehicle_journey boarded at the last stop */ + rtime_t time_at_last_stop = UNREACHED; + rtime_t time = UNREACHED; - /* Iterate over stop indexes within the route. Each one corresponds to - * a global stop index. Note that the stop times array should be - * accessed with [vj_index][jpp_index] not [vj_index][jpp_index]. + /* Iterate over stop_point indexes within the route. Each one corresponds to + * a global stop_point index. Note that the stop times array should be + * accessed with [vj_offset][jpp_offset] not [vj_offset][jpp_offset]. * * The iteration variable is signed to allow ending the iteration at * the beginning of the route, hence we decrement to 0 and need to * test for >= 0. An unsigned variable would always be true. */ - int32_t jpp_index; - - #ifdef FEATURE_AGENCY_FILTER - if (req->agency != AGENCY_UNFILTERED && - req->agency != jp->agency_index) continue; - #endif + int32_t jpp_offset; #if 0 if (jp_overlap) fprintf (stderr, "min time %d max time %d overlap %d \n", jp->min_time, jp->max_time, jp_overlap); @@ -968,58 +1069,41 @@ static void router_round(router_t *router, router_request_t *req, uint8_t round) fprintf (stderr, " actual first time: %d \n", tdata_depart(router->tdata, jp_index, 0, 0)); fprintf (stderr, " actual last time: %d \n", tdata_arrive(router->tdata, jp_index, jp->n_vjs - 1, jp->n_stops - 1)); fprintf(stderr, " journey_pattern %d: %s;%s\n", jp_index, tdata_line_code_for_journey_pattern(router->tdata, jp_index), tdata_headsign_for_journey_pattern(router->tdata, jp_index)); - tdata_dump_journey_pattern(router->tdata, jp_index, NONE); + tdata_dump_journey_pattern(router->tdata, jp_index, VJ_NONE); #endif + for (jpp_offset = (req->arrive_by ? jp->n_stops - 1 : 0); + req->arrive_by ? jpp_offset >= 0 : + jpp_offset < jp->n_stops; + req->arrive_by ? --jpp_offset : + ++jpp_offset) { - for (jpp_index = (req->arrive_by ? jp->n_stops - 1 : 0); - req->arrive_by ? jpp_index >= 0 : - jpp_index < jp->n_stops; - req->arrive_by ? --jpp_index : - ++jpp_index) { - - uint32_t stop_index = journey_pattern_points[jpp_index]; + spidx_t sp_index = journey_pattern_points[jpp_offset]; rtime_t prev_time; bool attempt_board = false; - bool forboarding = (journey_pattern_point_attributes[jpp_index] & rsa_boarding); - bool foralighting = (journey_pattern_point_attributes[jpp_index] & rsa_alighting); + bool forboarding = (journey_pattern_point_attributes[jpp_offset] & rsa_boarding); + bool foralighting = (journey_pattern_point_attributes[jpp_offset] & rsa_alighting); #ifdef RRRR_INFO char buf[13]; - fprintf(stderr, " stop %2d [%d] %c%c %s %s\n", jpp_index, - stop_index, + fprintf(stderr, " sp %2d [%d] %c%c %s %s\n", jpp_offset, + sp_index, forboarding ? 'B' : ' ', foralighting ? 'A' : ' ', - btimetext(router->best_time[stop_index], buf), - tdata_stop_name_for_index (router->tdata, - stop_index)); + btimetext(router->best_time[sp_index], buf), + tdata_stop_point_name_for_index (router->tdata, + sp_index)); #endif - if (vj_index != NONE && - /* When currently on a vehicle, skip stops where - * alighting is not allowed at the route-point. - */ - ((!forboarding && req->arrive_by) || - (!foralighting && !req->arrive_by))) { - continue; - } else if (vj_index == NONE && - /* When looking to board a vehicle, skip stops where - * boarding is not allowed at the route-point. - */ - ((!forboarding && !req->arrive_by) || - (!foralighting && req->arrive_by))) { - continue; - } - - #if RRRR_MAX_BANNED_STOPS_HARD > 0 - /* If a stop in in banned_stops_hard, we do not want to transit - * through this stationwe reset the current vj to NONE and skip + #if RRRR_MAX_BANNED_STOP_POINTS_HARD > 0 + /* If a stop_point in in banned_stop_points_hard, we do not want to transit + * through this statio nwe reset the current vj to VJ_NONE and skip * the currect stop. This effectively splits the journey_pattern in two, * and forces a re-board afterwards. */ - if (set_in (req->banned_stops_hard, req->n_banned_stops_hard, - stop_index)) { - vj_index = NONE; + if (set_in_sp (req->banned_stop_points_hard, req->n_banned_stop_points_hard, + (spidx_t) sp_index)) { + vj_offset = VJ_NONE; continue; } #endif @@ -1028,20 +1112,19 @@ static void router_round(router_t *router, router_request_t *req, uint8_t round) * a better vj on this journey_pattern at this location, indicate that we * want to search for a vj. */ - prev_time = states_walk_time[stop_index]; + prev_time = states_walk_time[sp_index]; /* Only board at placed that have been reached. */ if (prev_time != UNREACHED) { - if (vj_index == NONE || req->via == stop_index) { - attempt_board = true; - } else if (vj_index != NONE && req->via != STOP_NONE && - req->via == board_stop) { + if (vj_offset == VJ_NONE || req->via_stop_point == sp_index) { + attempt_board = req->arrive_by ? foralighting : forboarding; + } else if (vj_offset != VJ_NONE && req->via_stop_point != STOP_NONE && + req->via_stop_point == board_sp) { attempt_board = false; } else { rtime_t vj_stoptime = tdata_stoptime (router->tdata, board_serviceday, - jp_index, vj_index, - (uint16_t) jpp_index, + (jpidx_t) jp_index, vj_offset, (jppidx_t) jpp_offset, req->arrive_by); if (vj_stoptime == UNREACHED) { attempt_board = false; @@ -1052,12 +1135,12 @@ static void router_round(router_t *router, router_request_t *req, uint8_t round) fprintf (stderr, " [reboarding here] vj = %s\n", btimetext(vj_stoptime, buf)); #endif - attempt_board = true; + attempt_board = req->arrive_by ? foralighting : forboarding; } } } /* If we have not yet boarded a vj on this route, see if we can - * board one. Also handle the case where we hit a stop with an + * board one. Also handle the case where we hit a stop_point with an * existing better arrival time. */ if (attempt_board) { @@ -1067,28 +1150,28 @@ static void router_round(router_t *router, router_request_t *req, uint8_t round) * reduces speed by ~20 percent over binary search. */ serviceday_t *best_serviceday = NULL; - uint32_t best_vj = NONE; + jp_vjoffset_t best_vj = VJ_NONE; rtime_t best_time = (rtime_t) (req->arrive_by ? 0 : UNREACHED); #ifdef RRRR_INFO - fprintf (stderr, " attempting boarding at stop %d\n", - stop_index); + fprintf (stderr, " attempting boarding at stop_point %d\n", + sp_index); #endif #ifdef RRRR_TDATA - tdata_dump_journey_pattern(router->tdata, jp_index, NONE); + tdata_dump_journey_pattern(router->tdata, jp_index, VJ_NONE); #endif - if (vj_index == NONE) { - board_vehicle_journeys_within_days(router, req, jp_index, (uint16_t) jpp_index, + if (vj_offset == VJ_NONE) { + board_vehicle_journeys_within_days(router, req, (jpidx_t) jp_index, (jppidx_t) jpp_offset, prev_time, &best_serviceday, &best_vj, &best_time); }else{ - reboard_vehicle_journeys_within_days(router, req, jp_index, (uint16_t) jpp_index, - board_serviceday, vj_index, prev_time, &best_serviceday, + reboard_vehicle_journeys_within_days(router, req, (jpidx_t) jp_index, (jppidx_t) jpp_offset, + board_serviceday, vj_offset, prev_time, &best_serviceday, &best_vj, &best_time); } - if (best_vj != NONE) { + if (best_vj != VJ_NONE) { #ifdef RRRR_INFO char buf[13]; fprintf(stderr, " boarding vj %d at %s \n", @@ -1096,53 +1179,43 @@ static void router_round(router_t *router, router_request_t *req, uint8_t round) #endif if ((req->arrive_by ? best_time > req->time : best_time < req->time) && - req->from != ONBOARD) { + req->from_stop_point != ONBOARD) { fprintf(stderr, "ERROR: boarded before start time, " - "vj %d stop %d \n", - best_vj, stop_index); - } else { - /* TODO: use a router_state struct for all this? */ + "vj %d stop_point %d \n", + best_vj, sp_index); + } else if (vj_offset != best_vj) { + /* TODO probably better results if we take a transfer cost function into account and + * Check if later transfer-points are better (more buffertime,aircondition, elevators,etc.) + * Currently we take the first possible transfer point as entry point */ + + /* TODO: use a router_state struct for all this? */ board_time = best_time; - board_stop = stop_index; - board_jpp = (uint16_t) jpp_index; + board_sp = (spidx_t) sp_index; + board_jpp = (jppidx_t) jpp_offset; board_serviceday = best_serviceday; - vj_index = best_vj; + vj_offset = best_vj; } } else { #ifdef RRRR_DEBUG_VEHICLE_JOURNEY fprintf(stderr, " no suitable vj to board.\n"); #endif } - continue; /* to the next stop in the journey_pattern */ + continue; /* to the next stop_point in the journey_pattern */ /* We have already boarded a vehicle_journey along this journey_pattern. */ - } else if (vj_index != NONE) { - rtime_t time = tdata_stoptime (router->tdata, board_serviceday, - jp_index, vj_index, - (uint16_t) jpp_index, + } else if (vj_offset != VJ_NONE) { + time = tdata_stoptime (router->tdata, board_serviceday, + (jpidx_t) jp_index, vj_offset, + (jppidx_t ) jpp_offset, !req->arrive_by); /* overflow due to long overnight vehicle_journeys on day 2 */ if (time == UNREACHED) continue; - #ifdef RRRR_DEBUG_VEHICLE_JOURNEY - fprintf(stderr, " on board vj %d considering time %s \n", - vj_index, timetext(time)); - #endif - - /* Target pruning, section 3.1 of RAPTOR paper. */ - if ((router->best_time[router->target] != UNREACHED) && - (req->arrive_by ? time < router->best_time[router->target] - : time > router->best_time[router->target])) { - #ifdef RRRR_DEBUG_VEHICLE_JOURNEY - fprintf(stderr, " (target pruning)\n"); - #endif - - /* We cannot break out of this journey_pattern entirely, - * because re-boarding may occur at a later stop. - */ + if (!(req->arrive_by ? forboarding : foralighting)){ continue; } + if ((req->time_cutoff != UNREACHED) && (req->arrive_by ? time < req->time_cutoff : time > req->time_cutoff)) { @@ -1153,9 +1226,9 @@ static void router_round(router_t *router, router_request_t *req, uint8_t round) * Yes, because the best time may not have been found in the * previous round. */ - if (!((router->best_time[stop_index] == UNREACHED) || - (req->arrive_by ? time > router->best_time[stop_index] - : time < router->best_time[stop_index]))) { + if (!((router->best_time[sp_index] == UNREACHED) || + (req->arrive_by ? time > router->best_time[sp_index] + : time < router->best_time[sp_index]))) { #ifdef RRRR_INFO fprintf(stderr, " (no improvement)\n"); #endif @@ -1178,26 +1251,34 @@ static void router_round(router_t *router, router_request_t *req, uint8_t round) #ifdef RRRR_DEBUG fprintf(stderr, "ERROR: setting state to time before" \ - "start time. journey_pattern %d vj %d stop %d \n", - jp_index, vj_index, stop_index); + "start time. journey_pattern %d vj %d stop_point %d \n", + jp_index, vj_offset, sp_index); #endif } else { - write_state(router, req, round, jp_index, vj_index, - stop_index, (uint16_t) jpp_index, time, - board_stop, board_jpp, board_time); - - /* mark stop for next round. */ - bitset_set(router->updated_stops, stop_index); + write_state(router, req, round, (jpidx_t) jp_index, vj_offset, + (spidx_t) sp_index, (jppidx_t) jpp_offset, time, + board_sp, board_jpp, board_time); + /* mark stop_point for next round. */ + bitset_set(router->updated_stop_points, sp_index); } } - } /* end for (stop_index) */ + } /* end for (sp_index) */ + time_at_last_stop = time; + + if (vj_offset != VJ_NONE && time_at_last_stop != UNREACHED){ + vehicle_journey_ref_t *vj_interline = req->arrive_by ? &router->tdata->vehicle_journey_transfers_backward[jp->vj_index+vj_offset] + : &router->tdata->vehicle_journey_transfers_forward[jp->vj_index+vj_offset]; + if (vj_interline->jp_index != JP_NONE) { + vehicle_journey_extend(router, req, round, board_serviceday, (jpidx_t) jp_index, vj_interline, states_walk_time); + } + } } /* end for (route) */ - #if RRRR_MAX_BANNED_STOPS > 0 + #if RRRR_MAX_BANNED_STOP_POINTS > 0 /* Remove the banned stops from the bitset, * so no transfers will happen there. */ - unflag_banned_stops(router, req); + unflag_banned_stop_points(router, req); #endif /* Also updates the list of journey_patterns for next round @@ -1208,17 +1289,10 @@ static void router_round(router_t *router, router_request_t *req, uint8_t round) /* Initialize the stops in round 1 that were used as * starting points for round 0. */ - /* TODO: also must be done for the hashgrid */ if (round == 0) { - #ifdef RRRR_FEATURE_LATLON initialize_transfers_full (router, 1); - #else - initialize_transfers (router, 1, router->origin); - #endif } - /* TODO add arrival hashgrid timings */ - #ifdef RRRR_DEBUG dump_results(router); #endif @@ -1228,328 +1302,133 @@ static bool initialize_origin_onboard (router_t *router, router_request_t *req) /* We cannot expand the start vj into the temporary round (1) * during initialization because we may be able to reach the * destination on that starting vj. - * We discover the previous stop and flag only the selected journey_pattern + * We discover the previous stop_point and flag only the selected journey_pattern * for exploration in round 0. This would interfere with search * reversal, but reversal is meaningless/useless in on-board * depart vehicle_journeys anyway. */ - spidx_t stop_index; + spidx_t sp_index; rtime_t stop_time; + jppidx_t jpp_offset; - if (tdata_next (router, req, - req->onboard_vj_journey_pattern, req->onboard_journey_pattern_offset, - req->time, &stop_index, &stop_time) ){ - uint64_t i_state; - - req->from = ONBOARD; - - /* Initialize the origin */ - router->origin = stop_index; - router->best_time[router->origin] = stop_time; - - /* Set the origin stop in the "2nd round" */ - i_state = router->tdata->n_stops + router->origin; - router->states_time[i_state] = stop_time; + if (tdata_next (router, req->arrive_by, + req->onboard_journey_pattern, req->onboard_journey_pattern_vjoffset, + req->time, &sp_index, &stop_time, &jpp_offset) ){ + uint64_t i_state = router->tdata->n_stop_points + sp_index; + req->from_stop_point = ONBOARD; router->states_walk_time[i_state] = stop_time; - - /* When starting on board, only flag one journey_pattern and - * do not apply transfers, only a single walk. - */ - bitset_clear (router->updated_stops); - bitset_clear (router->updated_journey_patterns); - bitset_set (router->updated_journey_patterns, req->onboard_vj_journey_pattern); - + bitset_set(router->updated_journey_patterns, req->onboard_journey_pattern); + router_round(router, req, 0); return true; - } - + }; return false; } -static bool initialize_origin_index (router_t *router, router_request_t *req) { - uint32_t i_state; - - router->origin = (req->arrive_by ? req->to : req->from); - - if (router->origin == STOP_NONE) return false; - - router->best_time[router->origin] = req->time; - - /* TODO: This is a hack to communicate the origin time to itinerary - * renderer. It would be better to just include rtime_t in request - * structs. eliminate this now that we have rtimes in requests. - */ - router->states_time[router->origin] = req->time; - bitset_clear(router->updated_stops); - - /* This is inefficient, as it depends on iterating over - * a bitset with only one bit true. - */ - bitset_set(router->updated_stops, router->origin); - - /* We will use round 1 to hold the initial state for round 0. - * Round 1 must then be re-initialized before use. - */ - i_state = router->tdata->n_stops + router->origin; - router->states_time[i_state] = req->time; - - /* the rest of these should be unnecessary */ - router->states_ride_from[i_state] = STOP_NONE; - router->states_back_journey_pattern[i_state] = NONE; - router->states_back_vehicle_journey[i_state] = NONE; - router->states_board_time[i_state] = UNREACHED; - - /* Apply transfers to initial state, - * which also initializes the updated journey_patterns bitset. +static bool stop_point_is_banned(router_request_t *req, spidx_t sp_index){ + /* TODO: this is terrible. For each result we explicitly remove if it + * is banned. While banning doesn't happen that often a more elegant + * way would be to just overwrite the state and best_time. + * Sadly that might not give us an accurate best_sp_index. */ - apply_transfers(router, req, 1, true); - return true; -} - -static bool initialize_target_index (router_t *router, router_request_t *req) { - router->target = (req->arrive_by ? req->from : req->to); + #if RRRR_MAX_BANNED_STOP_POINTS > 0 + /* if a stop_point is banned, we should not act upon it here */ + if (set_in_sp (req->banned_stops, req->n_banned_stops, + (spidx_t) sp_index)) return true; + #endif - return (router->target != STOP_NONE); + #if RRRR_MAX_BANNED_STOP_POINTS_HARD > 0 + /* if a stop_point is banned hard, we should not act upon it here */ + if (set_in_sp (req->banned_stop_points_hard, req->n_banned_stop_points_hard, + (spidx_t) sp_index)) return true; + #endif + return false; } -#ifdef RRRR_FEATURE_LATLON -static bool latlon_best_stop_index(router_t *router, router_request_t *req, - hashgrid_result_t *hg_result) { - double distance, best_distance = INFINITY; - uint32_t stop_index, best_stop_index = HASHGRID_NONE; - - hashgrid_result_reset(hg_result); - stop_index = hashgrid_result_next_filtered(hg_result, &distance); - - while (stop_index != HASHGRID_NONE) { +static bool initialize_origins(router_t *router, router_request_t *req){ + street_network_t *origin = req->arrive_by ? &req->exit : &req->entry; + int32_t i_origin = origin->n_points; + if (i_origin == 0) { return false; } + #ifdef RRRR_DEV + fprintf(stderr,"\n INITIALIZING ORIGINS\n"); + #endif + while (i_origin){ + rtime_t start_time; + spidx_t sp_index; + rtime_t duration_to_origin; uint32_t i_state; - rtime_t extra_walktime; - - /* TODO: this is terrible. For each result we explicitly remove if it - * is banned. While banning doesn't happen that often a more elegant - * way would be to just overwrite the state and best_time. - * Sadly that might not give us an accurate best_stop_index. - */ + --i_origin; - #if RRRR_MAX_BANNED_STOPS > 0 - /* if a stop is banned, we should not act upon it here */ - if (set_in (req->banned_stops, req->n_banned_stops, - stop_index)) continue; - #endif - - #if RRRR_MAX_BANNED_STOPS_HARD > 0 - /* if a stop is banned hard, we should not act upon it here */ - if (set_in (req->banned_stops_hard, req->n_banned_stops_hard, - stop_index)) continue; - #endif - - i_state = router->tdata->n_stops + stop_index; - extra_walktime = SEC_TO_RTIME((uint32_t)((distance * RRRR_WALK_COMP) / - req->walk_speed)); - - if (req->arrive_by) { - router->best_time[stop_index] = req->time - extra_walktime; - router->states_time[i_state] = req->time - extra_walktime; - } else { - router->best_time[stop_index] = req->time + extra_walktime; - router->states_time[i_state] = req->time + extra_walktime; + duration_to_origin = origin->durations[i_origin]; + sp_index = origin->stop_points[i_origin]; + i_state = router->tdata->n_stop_points + sp_index; + if (stop_point_is_banned(req,sp_index)){ + continue; } + #ifdef RRRR_DEV + fprintf (stderr, "ORIGIN %d :%d %s %s : %d seconds\n", i_origin, sp_index, + tdata_stop_point_id_for_index(router->tdata, sp_index), + tdata_stop_point_name_for_index(router->tdata, sp_index), + RTIME_TO_SEC(origin->durations[i_origin])); + #endif - /* the rest of these should be unnecessary */ + start_time = req->arrive_by ? req->time - duration_to_origin : + req->time + duration_to_origin; + router->best_time[sp_index] = start_time; + router->states_time[i_state] = start_time; + router->states_walk_time[i_state] = start_time; + router->states_walk_from[i_state] = STOP_NONE; router->states_ride_from[i_state] = STOP_NONE; - router->states_back_journey_pattern[i_state] = NONE; - router->states_back_vehicle_journey[i_state] = NONE; + router->states_back_journey_pattern[i_state] = JP_NONE; + router->states_back_vehicle_journey[i_state] = VJ_NONE; router->states_board_time[i_state] = UNREACHED; - bitset_set(router->updated_stops, stop_index); + flag_journey_patterns_for_stop_point(router, req, (spidx_t) sp_index); + }; - if (distance < best_distance) { - best_distance = distance; - best_stop_index = stop_index; - } - - #ifdef RRRR_INFO - fprintf (stderr, "%d %s %s (%.0fm)\n", - stop_index, - tdata_stop_id_for_index(router->tdata, stop_index), - tdata_stop_name_for_index(router->tdata, stop_index), - distance); - #endif - - /* get the next potential start stop */ - stop_index = hashgrid_result_next_filtered(hg_result, &distance); - } - - router->origin = best_stop_index; - - if (router->origin == STOP_NONE) return false; - - /* TODO eliminate this now that we have rtimes in requests */ - router->states_time[router->origin] = req->time; + #if RRRR_MAX_BANNED_JOURNEY_PATTERNS > 0 + unflag_banned_journey_patterns(router, req); + #endif return true; } -#endif - -#ifdef RRRR_FEATURE_LATLON -static bool initialize_origin_latlon (router_t *router, router_request_t *req) { - if (req->arrive_by) { - if (req->to_latlon.lat == 0.0 && - req->to_latlon.lon == 0.0) { - return false; - } - - if (req->to_hg_result.hg == NULL) { - coord_t coord; - coord_from_latlon (&coord, &req->to_latlon); - hashgrid_query (&router->hg, &req->to_hg_result, - coord, req->walk_max_distance); - } - return latlon_best_stop_index (router, req, &req->to_hg_result); - } else { - if (req->from_latlon.lat == 0.0 && - req->from_latlon.lon == 0.0) { - return false; - } - - if (req->from_hg_result.hg == NULL ) { - coord_t coord; - coord_from_latlon (&coord, &req->from_latlon); - hashgrid_query (&router->hg, &req->from_hg_result, - coord, req->walk_max_distance); - } - return latlon_best_stop_index (router, req, &req->from_hg_result); - } - - return false; -} - -static bool initialize_target_latlon (router_t *router, router_request_t *req) { - if (req->arrive_by) { - if (req->from_latlon.lat == 0.0 && - req->from_latlon.lon == 0.0) { - return false; - } - - if (req->from_hg_result.hg == NULL) { - coord_t coord; - coord_from_latlon (&coord, &req->from_latlon); - hashgrid_query (&router->hg, &req->from_hg_result, - coord, req->walk_max_distance); - } - hashgrid_result_reset (&req->from_hg_result); - router->target = hashgrid_result_closest (&req->from_hg_result); - } else { - if (req->to_latlon.lat == 0.0 && - req->to_latlon.lon == 0.0) { - return false; - } - - if (req->to_hg_result.hg == NULL ) { - coord_t coord; - coord_from_latlon (&coord, &req->to_latlon); - hashgrid_query (&router->hg, &req->to_hg_result, - coord, req->walk_max_distance); - } - hashgrid_result_reset (&req->to_hg_result); - router->target = hashgrid_result_closest (&req->to_hg_result); - } - - return (router->target != STOP_NONE); -} -#endif static bool initialize_origin (router_t *router, router_request_t *req) { /* In this function we are setting up all initial required elements of the * routing engine we also infer what the requestee wants to do, the * following use cases can be observed: * - * 0) from is actually onboard an existing vj - * 1) from/to a station, req->from and/or req->to are filled - * 2) from/to a coordinate, req->from_coord and/or req->to_coord are filled + * 0) from onboard an existing vj. We have a set of stop_points where we can exit the transit-network to reach + * the destination location + * 1) From/to a location, that has duration of 0 or more, to more than one stop_point. + * Theses durations are previously calculated using the street_network and + * stored in the router_request_t structure under either entry (counter-clockwise) or exit (clockwise). + * The same is mirrored for the exit of the public transit network. * - * Given 0, we calculate the previous stop from an existing running vj + * Given 0, we calculate the previous stop_point from an existing running vj * and determine the best way to the destination. * - * Given 1, the user is actually at a stop and wants to leave from there, - * in the second round transfers are applied which may move the user by - * feet to a different stop. We consider the origin and destination as - * constraint, explicitly enforced by the user. - * "I am here, only move me if it is strictly required." - * - * Given 2, the user is at a location, which may be stop. We start with a - * search around this coordinate up to a maximum walk distances, - * configurable by the user. The first forward search starts from all - * found locations. Based on the distance, a weight factor and the - * walk-speed an extra time calculated and encounted for. - * A normal search will match up to the stop which is the closest to the - * destination. - * TODO: We store all possible paths from the forward search to all - * possible destinations. - * The backward search uses the best times for all near by stops, again - * the extra walk time is added. The search will now unambiously determine - * the last possible departure. - * From the second forward search we only search for the best plan to the - * best destination. This plan will be marked as "best". - * - * De overweging om bij een alternatieve eerdere halte uit te stappen - * en te gaan lopen baseren op het feit dat een andere, niet dezelfde - * back_journey_pattern wordt gebruikt + * Given 1, we set all the stop_point's to req->time with the duration it takes to get to that stop_point. + * In the first round all these stop_point's are a candidate to enter the public-transit network. + * After the execution of router_route we have a router_state, where we check using the list of exit stop_points, + * the earliest arrival-time at our destination. + * This time is then used for a backward-search, where we determine the latest departure-time from our start-location. + * For clockwise searches we possible execute a third search where we get the earliest travel-possibility for each leg. */ /* We are starting on board a vj, not at a station. */ - if (req->onboard_vj_journey_pattern != NONE && req->onboard_journey_pattern_offset != NONE) { - - /* On-board departure only makes sense for depart-after requests. */ - if (!req->arrive_by) { - return initialize_origin_onboard (router, req); - - } else { - fprintf (stderr, "An arrive-by search does not make any" \ - "sense if you are starting on-board.\n"); - return false; - } + if (!req->arrive_by && req->onboard_journey_pattern != JP_NONE && req->onboard_journey_pattern_vjoffset != VJ_NONE) { + return initialize_origin_onboard (router, req); } else { - #ifdef RRRR_FEATURE_LATLON - /* In the first two searches the LATLON search will find the best - * values for req->to and req->from the final interation have both - * set to the walk optimum. For the geographic optimisation to start - * a latlon must be set and the stop_index must be set to NONE. - */ - if (req->to == STOP_NONE || req->from == STOP_NONE) { - /* search the target based on latlon */ - return initialize_origin_latlon (router, req); - } else - #endif - { - /* search the origin based on a provided index */ - return initialize_origin_index (router, req); - } - } -} - -static bool initialize_target (router_t *router, router_request_t *req) { - /* In the first two searches the LATLON search will find the best - * values for req->to and req->from the final interation have both - * set to the walk optimum. For the geographic optimisation to start - * a latlon must be set and the stop_index must be set to NONE. - */ - #ifdef RRRR_FEATURE_LATLON - if (req->to == STOP_NONE || req->from == STOP_NONE) { - /* search the target based on latlon */ - return initialize_target_latlon (router, req); - } else - #endif - { - /* search the origin based on a provided index */ - return initialize_target_index (router, req); + return initialize_origins(router,req); } } - bool router_route(router_t *router, router_request_t *req) { uint8_t i_round, n_rounds; - + #ifdef RRRR_DEV + router_request_dump(req, router->tdata); + #endif /* populate router->states */ if (!initialize_states (router)) { fprintf(stderr, "States could not be initialised.\n"); @@ -1567,6 +1446,12 @@ bool router_route(router_t *router, router_request_t *req) { * is used via initialize_origin, apply_transfers */ initialize_banned_journey_patterns (router, req); + + #if RRRR_MAX_FILTERED_OPERATORS > 0 || RRRR_MAX_BANNED_OPERATORS > 0 + /* populate router->banned_journey_patterns to only include or exclude specific operators */ + initialize_filtered_operators (router, req); + #endif + #endif /* populate router->origin */ @@ -1574,9 +1459,7 @@ bool router_route(router_t *router, router_request_t *req) { fprintf(stderr, "Search origin could not be initialised.\n"); return false; } - - /* populate router->target */ - if (!initialize_target (router, req)) { + if (req->from_stop_point != ONBOARD && req->arrive_by ? req->entry.n_points == 0 : req->exit.n_points == 0){ fprintf(stderr, "Search target could not be initialised.\n"); return false; } @@ -1589,7 +1472,7 @@ bool router_route(router_t *router, router_request_t *req) { } /* Iterate over rounds. In round N, we have made N transfers. */ - for (i_round = 0; i_round < n_rounds; ++i_round) { + for (i_round = (uint8_t) (req->from_stop_point == ONBOARD ? 1 : 0); i_round < n_rounds; ++i_round) { router_round(router, req, i_round); } diff --git a/router.h b/router.h index 13c7f60..86e982a 100644 --- a/router.h +++ b/router.h @@ -1,3 +1,8 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and at + * https://github.com/bliksemlabs/rrrr/ + */ + /* router.h */ #ifndef _ROUTER_H @@ -9,20 +14,16 @@ #include "rrrr_types.h" #include "tdata.h" #include "bitset.h" -#include "hashgrid.h" #include #include #include -/* When associated with a stop index, - * a router_state_t describes a leg of an itinerary. - */ -/* We could potentially remove the back_time from router_state, +/* We could potentially remove the states_board_time from router, * but this requires implementing some lookup functions and storing - * the back_vj_stop rather than the back_stop (global stop index): - * a vehicle_journey can pass through a stop more than once. + * the back_vj_stop_point rather than the back_stop_point (global stop_point index): + * a vehicle_journey can pass through a stop_point more than once. */ /* Scratch space for use by the routing algorithm. @@ -33,60 +34,54 @@ struct router { /* The transit / timetable data tables */ tdata_t *tdata; - /* The best known time at each stop */ + /* The best known time at each stop_point */ rtime_t *best_time; - /* The index of the journey_pattern used to travel from back_stop to here, or WALK */ - uint32_t *states_back_journey_pattern; + /* The index of the journey_pattern used to travel from back_stop_point to here, or WALK */ + jpidx_t *states_back_journey_pattern; - /* The index of the vehicle_journey used to travel from back_stop */ - uint32_t *states_back_vehicle_journey; + /* The index of the vehicle_journey used to travel from back_stop_point */ + jp_vjoffset_t *states_back_vehicle_journey; - /* The index of the previous stop in the itinerary */ + /* The index of the previous stop_point in the itinerary */ spidx_t *states_ride_from; /* Second phase footpath/transfer results */ - /* The stop from which this stop was reached by walking (2nd phase) */ + /* The stop_point from which this stop_point was reached by walking (2nd phase) */ spidx_t *states_walk_from; /* Second phase footpath/transfer results */ - /* The time when this stop was reached by walking (2nd phase) */ + /* The time when this stop_point was reached by walking (2nd phase) */ rtime_t *states_walk_time; - /* The time when this stop was reached */ + /* The time when this stop_point was reached */ rtime_t *states_time; - /* The time at which the vehicle_journey within back_journey_pattern left back_stop */ + /* The time at which the vehicle_journey within back_journey_pattern left back_stop_point */ rtime_t *states_board_time; #ifdef RRRR_FEATURE_REALTIME_EXPANDED - uint16_t *states_back_journey_pattern_point; - uint16_t *states_journey_pattern_point; + jppidx_t *states_back_journey_pattern_point; + jppidx_t *states_journey_pattern_point; #endif - /* Used to track which stops improved during each round */ - bitset_t *updated_stops; + /* Used to track which stop_points improved during each round */ + bitset_t *updated_stop_points; /* Used to track which journey_patterns might have changed during each round */ bitset_t *updated_journey_patterns; - /* Used to track to which stops we changed the walk_time during each round */ - bitset_t *updated_walk_stops; + /* Used to track to which stop_points we changed the walk_time during each round */ + bitset_t *updated_walk_stop_points; #ifdef RRRR_BANNED_JOURNEY_PATTERNS_BITMASK /* Used to ban journey_patterns and in the final clockwise search optimise */ bitset_t *banned_journey_patterns; #endif - spidx_t origin; - spidx_t target; - calendar_t day_mask; serviceday_t servicedays[3]; uint8_t n_servicedays; -#ifdef RRRR_FEATURE_LATLON - hashgrid_t hg; -#endif /* TODO: We should move more routing state in here, * like round and sub-scratch pointers. */ @@ -103,4 +98,3 @@ void router_teardown(router_t*); bool router_route(router_t*, router_request_t*); #endif /* _ROUTER_H */ - diff --git a/router/one_itinerary/router_one_itinerary.c b/router/one_itinerary/router_one_itinerary.c new file mode 100644 index 0000000..a1ef8d5 --- /dev/null +++ b/router/one_itinerary/router_one_itinerary.c @@ -0,0 +1,162 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and + * at https://github.com/bliksemlabs/rrrr/ + */ + +#include "config.h" +#include "bitset.h" +#include "plan.h" +#include "util.h" +#include "set.h" +#include "street_network.h" +#include "router_request.h" +#include "router_result.h" +#include "router_one_itinerary.h" + +#include + +static void set_street_network_matching_journey_patterns (tdata_t *tdata, bitset_t *bs, street_network_t *sn, const router_request_t *req) { + spidx_t n_points; + + bitset_clear (bs); + n_points = sn->n_points; + while (n_points) { + jpidx_t *jp_ret; + jpidx_t n_jps; + n_points--; + + n_jps = tdata_journey_patterns_for_stop_point (tdata, sn->stop_points[n_points], &jp_ret); + + while (n_jps) { + n_jps--; + + if (tdata->journey_pattern_active[jp_ret[n_jps]] & req->day_mask && + (req->n_operators == 0 || + set_in_uint8(req->operators, req->n_operators, + tdata_operator_idx_for_journey_pattern(tdata, jp_ret[n_jps])))) { + bitset_set (bs, jp_ret[n_jps]); + } + } + } +} + +static bool get_jpp_for_journey_pattern (tdata_t *tdata, jpidx_t jp_index, street_network_t *sn, jppidx_t *jpp, jpidx_t *sp) { + spidx_t *sps = tdata_points_for_journey_pattern(tdata, jp_index); + jppidx_t jpp_idx = (*jpp != JPP_NONE ? *jpp : tdata->journey_patterns[jp_index].n_stops); + while (jpp_idx) { + spidx_t n_points = sn->n_points; + jpp_idx--; + + while (n_points) { + n_points--; + + if (sps[jpp_idx] == sn->stop_points[n_points]) { + *jpp = jpp_idx; + *sp = sps[jpp_idx]; + goto found; + } + } + } + + return false; + +found: + return true; +} +static void leg_swap(leg_t *leg) { + struct leg temp = *leg; + leg->sp_from = temp.sp_to; + leg->sp_to = temp.sp_from; + leg->t0 = temp.t1; + leg->t1 = temp.t0; +#ifdef RRRR_FEATURE_REALTIME_EXPANDED + leg->jpp0 = temp.jpp1; + leg->jpp1 = temp.jpp0; + leg->d0 = temp.d0; + leg->d1 = temp.d1; +#endif +} + +bool router_route_one_trip (tdata_t *tdata, router_request_t *req, plan_t *plan) { + /* search in journey_patterns_at_stop for both origin and destination + * for each result maintain either a set (to cross correlate) or + * a bitmask (and, find results). Results should be filtered based on + * jp_active and/or vj_active. + * + * For the matching patterns returnall searches given the departure time. + */ + uint32_t jp_index; + calendar_t day_mask = req->day_mask; + calendar_t cal_day = 0; + + bitset_t *entry = bitset_new(tdata->n_journey_patterns); + bitset_t *exit = bitset_new(tdata->n_journey_patterns); + + router_request_search_street_network (req, tdata); + street_network_null_duration (&req->entry); + + set_street_network_matching_journey_patterns (tdata, entry, &req->entry, req); + set_street_network_matching_journey_patterns (tdata, exit, &req->exit, req); + + bitset_mask_and (entry, exit); + + while (day_mask >>= 1) cal_day++; + + for (jp_index = bitset_next_set_bit (entry, 0); + jp_index != BITSET_NONE; + jp_index = bitset_next_set_bit (entry, jp_index + 1)) { + + vehicle_journey_t *vj; + calendar_t *vj_masks; + jp_vjoffset_t vj_offset; + jppidx_t jpp_from, jpp_to = JPP_NONE; + spidx_t sp_from, sp_to; + + vj = tdata_vehicle_journeys_in_journey_pattern(tdata, (jpidx_t) jp_index); + + get_jpp_for_journey_pattern (tdata, (jpidx_t) jp_index, (req->arrive_by ? &req->entry : &req->exit), &jpp_to, &sp_to); + jpp_from = jpp_to; /* make sure the direction is correct */ + if (!get_jpp_for_journey_pattern (tdata, (jpidx_t) jp_index, (req->arrive_by ? &req->exit : &req->entry), &jpp_from, &sp_from)) { + continue; + } + + vj_masks = tdata_vj_masks_for_journey_pattern(tdata, (jpidx_t) jp_index); + + for (vj_offset = 0; + vj_offset < tdata->journey_patterns[jp_index].n_vjs; + vj_offset++) { + + rtime_t board_time; + if ( ! (req->day_mask & vj_masks[vj_offset])) continue; + + board_time = tdata_stoptime_for_index(tdata, (jpidx_t) jp_index, jpp_from, vj_offset, req->arrive_by) + RTIME_ONE_DAY; + if (board_time > (req->time + 1800)) break; + + if (board_time >= (req->time)) { + rtime_t arrival_time = tdata_stoptime_for_index(tdata, (jpidx_t) jp_index, jpp_to, vj_offset, req->arrive_by) + RTIME_ONE_DAY; + itinerary_t *itin = &plan->itineraries[plan->n_itineraries]; + itin->legs[0].sp_from = sp_from; + itin->legs[0].sp_to = sp_to; + itin->legs[0].t0 = board_time; + itin->legs[0].t1 = arrival_time; + itin->legs[0].journey_pattern = (jpidx_t) jp_index; + itin->legs[0].vj = vj_offset; + itin->legs[0].cal_day = cal_day; +#ifdef RRRR_FEATURE_REALTIME_EXPANDED + itin->legs[0].jpp0 = jpp_from; + itin->legs[0].jpp1 = jpp_to; +#endif + if (req->arrive_by) leg_swap(&itin->legs[0]); + itin->n_legs = 1; + plan->n_itineraries++; + if (plan->n_itineraries >= RRRR_DEFAULT_PLAN_ITIN) goto done; + } + } + } + +done: + bitset_destroy (entry); + bitset_destroy (exit); + + return true; +} diff --git a/router/one_itinerary/router_one_itinerary.h b/router/one_itinerary/router_one_itinerary.h new file mode 100644 index 0000000..e3a510e --- /dev/null +++ b/router/one_itinerary/router_one_itinerary.h @@ -0,0 +1,11 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and + * at https://github.com/bliksemlabs/rrrr/ + */ + +#include "config.h" +#include "router_request.h" +#include "tdata.h" +#include "plan.h" + +bool router_route_one_trip (tdata_t *tdata, router_request_t *req, plan_t *plan); diff --git a/router_dump.c b/router_dump.c index 9f1f433..3721564 100644 --- a/router_dump.c +++ b/router_dump.c @@ -1,9 +1,11 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and at + * https://github.com/bliksemlabs/rrrr/ + */ + #include "config.h" #include "router_dump.h" -#include "router.h" #include "util.h" -#include "rrrr_types.h" -#include "tdata.h" #include @@ -11,7 +13,7 @@ void router_state_dump (router_t *router, uint64_t i_state) { char walk_time[13], time[13], board_time[13]; fprintf (stderr, "-- Router State --\n" "walk time: %s\n" - "walk from: %d\n" + "walk from_stop_point: %d\n" "time: %s\n" "board time: %s\n" "back route: ", @@ -22,18 +24,18 @@ void router_state_dump (router_t *router, uint64_t i_state) { ); /* TODO */ - if (router->states_back_journey_pattern[i_state] == NONE) fprintf (stderr, "NONE\n"); + if (router->states_back_journey_pattern[i_state] == JP_NONE) fprintf (stderr, "NONE\n"); else fprintf (stderr, "%d\n", router->states_back_journey_pattern[i_state]); } void dump_results(router_t *router) { - spidx_t i_stop; + spidx_t i_sp; uint8_t i_round; #if 0 char id_fmt[10]; sprintf(id_fmt, "%%%ds", router.tdata.stop_id_width); #else - char *id_fmt = "%30.30s"; + #define id_fmt "%30.30s" #endif fprintf(stderr, "\nRouter states:\n"); @@ -45,20 +47,20 @@ void dump_results(router_t *router) { } fprintf(stderr, "\n"); - for (i_stop = 0; i_stop < router->tdata->n_stops; ++i_stop) { + for (i_sp = 0; i_sp < router->tdata->n_stop_points; ++i_sp) { const char *stop_id; char time[13], walk_time[13]; /* filter out stops which will not be reached */ - if (router->best_time[i_stop] == UNREACHED) continue; + if (router->best_time[i_sp] == UNREACHED) continue; - stop_id = tdata_stop_name_for_index (router->tdata, i_stop); + stop_id = tdata_stop_point_name_for_index(router->tdata, i_sp); fprintf(stderr, id_fmt, stop_id); - fprintf(stderr, " [%6d]", i_stop); + fprintf(stderr, " [%6d]", i_sp); for (i_round = 0; i_round < RRRR_DEFAULT_MAX_ROUNDS; ++i_round) { fprintf(stderr, " %8s %8s", - btimetext(router->states_time[i_round * router->tdata->n_stops + i_stop], time), - btimetext(router->states_walk_time[i_round * router->tdata->n_stops + i_stop], walk_time)); + btimetext(router->states_time[i_round * router->tdata->n_stop_points + i_sp], time), + btimetext(router->states_walk_time[i_round * router->tdata->n_stop_points + i_sp], walk_time)); } fprintf(stderr, "\n"); } diff --git a/router_dump.h b/router_dump.h index 0e6c6c2..3b43db0 100644 --- a/router_dump.h +++ b/router_dump.h @@ -1,4 +1,4 @@ -/* Copyright 2013 Bliksem Labs. +/* Copyright 2013-2015 Bliksem Labs B.V. * See the LICENSE file at the top-level directory of this distribution and at * https://github.com/bliksemlabs/rrrr/ */ @@ -15,7 +15,7 @@ #include #include "config.h" void router_state_dump (router_t *router, uint64_t i_state); -bool stop_is_reached(router_t *router, uint32_t stop_index); +bool stop_is_reached(router_t *router, uint32_t sp_index); void dump_results(router_t *router); void day_mask_dump (uint32_t mask); void service_day_dump (struct service_day *sd); diff --git a/router_request.c b/router_request.c index 7b732ee..db51e33 100644 --- a/router_request.c +++ b/router_request.c @@ -1,46 +1,48 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and at + * https://github.com/bliksemlabs/rrrr/ + */ + #include "config.h" #include "router_request.h" -#include "util.h" - -#include +#include "street_network.h" +#include /* router_request_to_epoch returns the time-date * used in the request in seconds since epoch. */ - -time_t router_request_to_epoch (router_request_t *req, tdata_t *tdata, struct tm *tm_out) { +time_t +router_request_to_epoch (router_request_t *req, tdata_t *tdata, + struct tm *tm_out) { time_t seconds; calendar_t day_mask = req->day_mask; uint8_t cal_day = 0; while (day_mask >>= 1) cal_day++; - seconds = tdata->calendar_start_time + (cal_day * SEC_IN_ONE_DAY) + - RTIME_TO_SEC(req->time - RTIME_ONE_DAY) - - ((tdata->dst_active & 1 << cal_day) ? SEC_IN_ONE_HOUR : 0); - - rrrr_localtime_r (&seconds, tm_out); + seconds = (time_t) (tdata->calendar_start_time + (cal_day * SEC_IN_ONE_DAY) ) + + RTIME_TO_SEC(req->time - RTIME_ONE_DAY) - tdata->utc_offset; + rrrr_gmtime_r (&seconds, tm_out); return seconds; } -/* router_request_to_date returns the date used +/* router_request_to_date returns the UTC date used * in the request in seconds since epoch. */ - -time_t router_request_to_date (router_request_t *req, tdata_t *tdata, struct tm *tm_out) { +time_t +router_request_to_date (router_request_t *req, tdata_t *tdata, + struct tm *tm_out) { time_t seconds; calendar_t day_mask = req->day_mask; uint8_t cal_day = 0; while (day_mask >>= 1) cal_day++; - seconds = tdata->calendar_start_time + (cal_day * SEC_IN_ONE_DAY) - - ((tdata->dst_active & 1 << cal_day) ? SEC_IN_ONE_HOUR : 0); - - rrrr_localtime_r (&seconds, tm_out); + seconds = (time_t) (tdata->calendar_start_time + (cal_day * SEC_IN_ONE_DAY)); + rrrr_gmtime_r (&seconds, tm_out); return seconds; } @@ -48,12 +50,15 @@ time_t router_request_to_date (router_request_t *req, tdata_t *tdata, struct tm * it will not set required arguments such as arrival and departure * stops, not it will set the time. */ - -void router_request_initialize(router_request_t *req) { +void +router_request_initialize(router_request_t *req) { + req->exit.n_points = 0; + req->entry.n_points = 0; req->walk_speed = RRRR_DEFAULT_WALK_SPEED; req->walk_slack = RRRR_DEFAULT_WALK_SLACK; req->walk_max_distance = RRRR_DEFAULT_WALK_MAX_DISTANCE; - req->from = req->to = req->via = STOP_NONE; + req->from_stop_point = req->to_stop_point = req->via_stop_point = STOP_NONE; + req->from_stop_area = req->to_stop_area = STOP_NONE; req->time = UNREACHED; req->time_cutoff = UNREACHED; req->arrive_by = true; @@ -65,59 +70,66 @@ void router_request_initialize(router_request_t *req) { req->optimise = o_all; #if RRRR_MAX_BANNED_JOURNEY_PATTERNS > 0 req->n_banned_journey_patterns = 0; - rrrr_memset (req->banned_journey_patterns, NONE, RRRR_MAX_BANNED_JOURNEY_PATTERNS); + rrrr_memset (req->banned_journey_patterns, JP_NONE, RRRR_MAX_BANNED_JOURNEY_PATTERNS); + #endif + #if RRRR_MAX_BANNED_OPERATORS > 0 + req->n_banned_operators = 0; + rrrr_memset (req->banned_operators, OP_NONE, RRRR_MAX_BANNED_OPERATORS); #endif - #if RRRR_MAX_BANNED_STOPS > 0 + #if RRRR_MAX_BANNED_STOP_POINTS > 0 req->n_banned_stops = 0; - rrrr_memset (req->banned_stops, STOP_NONE, RRRR_MAX_BANNED_STOPS); + rrrr_memset (req->banned_stops, STOP_NONE, RRRR_MAX_BANNED_STOP_POINTS); #endif - #if RRRR_MAX_BANNED_STOPS_HARD > 0 - req->n_banned_stops_hard = 0; - rrrr_memset (req->banned_stops_hard, STOP_NONE, RRRR_MAX_BANNED_STOPS_HARD); + #if RRRR_MAX_BANNED_STOP_POINTS_HARD > 0 + req->n_banned_stop_points_hard = 0; + rrrr_memset (req->banned_stop_points_hard, STOP_NONE, RRRR_MAX_BANNED_STOP_POINTS_HARD); #endif #if RRRR_MAX_BANNED_VEHICLE_JOURNEYS > 0 req->n_banned_vjs = 0; - rrrr_memset (req->banned_vjs_journey_pattern, NONE, RRRR_MAX_BANNED_VEHICLE_JOURNEYS); + rrrr_memset (req->banned_vjs_journey_pattern, JP_NONE, RRRR_MAX_BANNED_VEHICLE_JOURNEYS); rrrr_memset (req->banned_vjs_offset, 0, RRRR_MAX_BANNED_VEHICLE_JOURNEYS); #endif - req->onboard_vj_journey_pattern = NONE; - req->onboard_journey_pattern_offset = NONE; - req->intermediatestops = false; - - #ifdef RRRR_FEATURE_LATLON - req->from_latlon.lat = 0.0; - req->from_latlon.lon = 0.0; - req->to_latlon.lat = 0.0; - req->to_latlon.lon = 0.0; + #if RRRR_MAX_FILTERED_OPERATORS > 0 + req->n_operators = 0; + rrrr_memset (req->operators, 0, RRRR_MAX_FILTERED_OPERATORS); #endif + req->onboard_journey_pattern = JP_NONE; + req->onboard_journey_pattern_vjoffset = VJ_NONE; + req->intermediatestops = false; - #ifdef RRRR_FEATURE_AGENCY_FILTER - req->agency = AGENCY_UNFILTERED; - #endif + req->from_latlon.lat = 0.0f; + req->from_latlon.lon = 0.0f; + req->to_latlon.lat = 0.0f; + req->to_latlon.lon = 0.0f; } -/* Initializes the router request then fills in its time and datemask fields from the given epoch time. */ -/* TODO: if we set the date mask in the router itself we wouldn't need the tdata here. */ -void router_request_from_epoch(router_request_t *req, tdata_t *tdata, time_t epochtime) { +/* Initializes the router request then fills in its time and datemask fields + * from the given epoch time. + */ +void +router_request_from_epoch(router_request_t *req, tdata_t *tdata, + time_t epochtime) { #if 0 char etime[32]; strftime(etime, 32, "%Y-%m-%d %H:%M:%S\0", localtime(&epochtime)); fprintf (stderr, "epoch time: %s [%ld]\n", etime, epochtime); - router_request_initialize (req); + fprintf (stderr, "calendar_start_time: %s [%ld]\n", etime, tdata->calendar_start_time); #endif - struct tm origin_tm; - uint32_t cal_day; - - req->time = epoch_to_rtime (epochtime, &origin_tm); - req->time_rounded = ((origin_tm.tm_sec % 4) > 0); - /* TODO not DST-proof, use noons */ - cal_day = (mktime(&origin_tm) - tdata->calendar_start_time) / SEC_IN_ONE_DAY; - if (cal_day > 31 ) { - /* date not within validity period of the timetable file, wrap to validity range - * 28 is a multiple of 7, so we always wrap up to the same day of the week + calendar_t cal_day; + time_t request_time = (epochtime - (time_t) tdata->calendar_start_time + tdata->utc_offset); + + req->time = RTIME_ONE_DAY + SEC_TO_RTIME(request_time%SEC_IN_ONE_DAY); + req->time_rounded = ((request_time%SEC_IN_ONE_DAY) % 4) > 0; + cal_day = (calendar_t) (request_time/SEC_IN_ONE_DAY); + + if (cal_day >= tdata->n_days ) { + /* date not within validity period of the timetable file, + * wrap to validity range 28 is a multiple of 7, so we always wrap + * up to the same day of the week. */ + /* TODO: Make 28 depended on tdata->n_days */ cal_day %= 28; - fprintf (stderr, "calendar day out of range. wrapping to %d, " + fprintf (stderr, "calendar day out of range. wrapping to %u, " "which is on the same day of the week.\n", cal_day); req->calendar_wrapped = true; } @@ -126,16 +138,16 @@ void router_request_from_epoch(router_request_t *req, tdata_t *tdata, time_t epo /* router_request_randomize creates a completely filled in, working request. */ - -void router_request_randomize (router_request_t *req, tdata_t *tdata) { +void +router_request_randomize (router_request_t *req, tdata_t *tdata) { req->walk_speed = RRRR_DEFAULT_WALK_SPEED; req->walk_slack = RRRR_DEFAULT_WALK_SLACK; req->walk_max_distance = RRRR_DEFAULT_WALK_MAX_DISTANCE; req->time = RTIME_ONE_DAY + SEC_TO_RTIME(3600 * 9 + rrrrandom(3600 * 12)); - req->via = STOP_NONE; + req->via_stop_point = STOP_NONE; req->time_cutoff = UNREACHED; /* 0 or 1 */ - req->arrive_by = (bool) rrrrandom(2); + req->arrive_by = (bool) (rrrrandom(2)); req->max_transfers = RRRR_DEFAULT_MAX_ROUNDS - 1; req->day_mask = ((calendar_t) 1) << rrrrandom(32); req->mode = m_all; @@ -143,224 +155,316 @@ void router_request_randomize (router_request_t *req, tdata_t *tdata) { req->optimise = o_all; #if RRRR_MAX_BANNED_JOURNEY_PATTERNS > 0 req->n_banned_journey_patterns = 0; - rrrr_memset (req->banned_journey_patterns, NONE, RRRR_MAX_BANNED_JOURNEY_PATTERNS); + rrrr_memset (req->banned_journey_patterns, JP_NONE, RRRR_MAX_BANNED_JOURNEY_PATTERNS); + #endif + #if RRRR_MAX_BANNED_OPERATORS > 0 + req->n_banned_operators = 0; + rrrr_memset (req->banned_operators, OP_NONE, RRRR_MAX_BANNED_OPERATORS); #endif - #if RRRR_MAX_BANNED_STOPS > 0 + #if RRRR_MAX_BANNED_STOP_POINTS > 0 req->n_banned_stops = 0; - rrrr_memset (req->banned_stops, STOP_NONE, RRRR_MAX_BANNED_STOPS); + rrrr_memset (req->banned_stops, STOP_NONE, RRRR_MAX_BANNED_STOP_POINTS); #endif - #if RRRR_MAX_BANNED_STOPS_HARD > 0 - req->n_banned_stops_hard = 0; - rrrr_memset (req->banned_stops_hard, STOP_NONE, RRRR_MAX_BANNED_STOPS_HARD); + #if RRRR_MAX_BANNED_STOP_POINTS_HARD > 0 + req->n_banned_stop_points_hard = 0; + rrrr_memset (req->banned_stop_points_hard, STOP_NONE, RRRR_MAX_BANNED_STOP_POINTS_HARD); #endif #if RRRR_MAX_BANNED_VEHICLE_JOURNEYS > 0 req->n_banned_vjs = 0; - rrrr_memset (req->banned_vjs_journey_pattern, NONE, RRRR_MAX_BANNED_VEHICLE_JOURNEYS); + rrrr_memset (req->banned_vjs_journey_pattern, JP_NONE, RRRR_MAX_BANNED_VEHICLE_JOURNEYS); rrrr_memset (req->banned_vjs_offset, 0, RRRR_MAX_BANNED_VEHICLE_JOURNEYS); #endif - req->intermediatestops = false; - req->from = rrrrandom(tdata->n_stops); - req->to = rrrrandom(tdata->n_stops); - - #ifdef RRRR_FEATURE_LATLON - req->from_latlon = tdata->stop_coords[rrrrandom(tdata->n_stops)]; - req->to_latlon = tdata->stop_coords[rrrrandom(tdata->n_stops)]; - req->from = STOP_NONE; - req->to = STOP_NONE; + #if RRRR_MAX_FILTERED_OPERATORS > 0 + req->n_operators = 0; + rrrr_memset (req->operators, 0, RRRR_MAX_FILTERED_OPERATORS); #endif + req->intermediatestops = false; + req->from_stop_point = (spidx_t) rrrrandom(tdata->n_stop_points); + req->to_stop_point = (spidx_t) rrrrandom(tdata->n_stop_points); - #ifdef RRRR_FEATURE_AGENCY_FILTER - req->agency = AGENCY_UNFILTERED; - #endif + req->from_latlon = tdata->stop_point_coords[rrrrandom(tdata->n_stop_points)]; + req->to_latlon = tdata->stop_point_coords[rrrrandom(tdata->n_stop_points)]; + req->from_stop_point = STOP_NONE; + req->to_stop_point = STOP_NONE; } -/* router_request_next updates the current request structure with - * the next request using the rtime_t resolution (4s) - */ +static bool +best_time_by_round(router_t *router, router_request_t *req, uint8_t round, rtime_t *time) { + uint64_t offset_state = ((uint64_t) round) * router->tdata->n_stop_points; + spidx_t sp_index; + spidx_t best_sp_index = STOP_NONE; + rtime_t best_time = (rtime_t) (req->arrive_by ? 0 : UNREACHED); + rtime_t *round_best_time = &router->states_time[offset_state]; + + street_network_t *target = req->arrive_by ? &req->entry : &req->exit; + int32_t i_target = target->n_points; + while (i_target){ + --i_target; + sp_index = target->stop_points[i_target]; + if (round_best_time[sp_index] != UNREACHED) { + if (req->arrive_by && round_best_time[sp_index] - target->durations[i_target] > best_time) { + best_sp_index = (spidx_t) sp_index; + best_time = round_best_time[sp_index] - target->durations[i_target]; + } else if (!req->arrive_by && round_best_time[sp_index] + target->durations[i_target] < best_time) { + best_sp_index = (spidx_t) sp_index; + best_time = round_best_time[sp_index] + target->durations[i_target]; + } + } + } -void router_request_next(router_request_t *req, rtime_t inc) { - req->time += inc; + *time = best_time; - if (req->time >= 21600) { - req->day_mask++; - req->time -= 21600; + if (best_sp_index != STOP_NONE) { + if (round > 0) { + offset_state -= router->tdata->n_stop_points; + if (best_time >= router->states_time[offset_state + best_sp_index]) { + fprintf(stderr, "A later round shows solutions which are not better.\n"); + return false; + } + } + return true; } - req->time_cutoff = UNREACHED; - req->time_rounded = false; - req->max_transfers = RRRR_DEFAULT_MAX_ROUNDS - 1; + return false; } -/* Reverse the direction of the search leaving most request parameters unchanged but applying time - * and transfer cutoffs based on an existing result for the same request. - * Returns a boolean value indicating whether the request was successfully reversed. +/* Reverse request and eliminate targets that are not reached at best_time + * We could do this in best_time_to_round in one single loop but that would either require a second loop like this + * or multiple resets when a better best_time is found. + * There are multiple targets necessary for the reversal in the theoretical case that there are + * an equal arrival_time on multiple stop_point's but a less travel duration on one of them. + * In the third reversal we expect routing between one origin and one target. */ -bool router_request_reverse(router_t *router, router_request_t *req) { - uint32_t max_transfers = req->max_transfers; - uint32_t best_stop_index = HASHGRID_NONE; - uint8_t round = UINT8_MAX; - - /* range-check to keep search within states array */ - if (max_transfers >= RRRR_DEFAULT_MAX_ROUNDS) - max_transfers = RRRR_DEFAULT_MAX_ROUNDS - 1; - - #ifdef RRRR_FEATURE_LATLON - if ((req->arrive_by ? req->from == STOP_NONE : req->to == STOP_NONE)) { - hashgrid_result_t *hg_result; - uint32_t stop_index; - double distance; - rtime_t best_time = (rtime_t) (req->arrive_by ? 0 : UNREACHED); - - if (req->arrive_by) { - hg_result = &req->from_hg_result; - } else { - hg_result = &req->to_hg_result; +static void +reverse_request (router_t *router, router_request_t *req, router_request_t *new_req, uint8_t round, rtime_t best_time) { + uint64_t offset_state = ((uint64_t) round) * router->tdata->n_stop_points; + spidx_t sp_index; + rtime_t *round_best_time = &router->states_time[offset_state]; + + street_network_t target = req->arrive_by ? req->entry : req->exit; + int32_t i_target = target.n_points; + street_network_t *best_target = new_req->arrive_by ? &new_req->entry : &new_req->exit; + best_target->n_points = 0; + + while (i_target){ + --i_target; + sp_index = target.stop_points[i_target]; + if (round_best_time[sp_index] != UNREACHED && + best_time == (req->arrive_by ? round_best_time[sp_index] - target.durations[i_target] + : round_best_time[sp_index] + target.durations[i_target])) { + best_target->stop_points[best_target->n_points] = sp_index; + best_target->durations[best_target->n_points] = target.durations[i_target]; + ++best_target->n_points; } + } - hashgrid_result_reset(hg_result); - - #ifdef RRRR_DEBUG - fprintf (stderr, "Reversal - Hashgrid results:\n"); - #endif + new_req->time_cutoff = new_req->time; + new_req->time = best_time; + new_req->max_transfers = round; + new_req->arrive_by = !(new_req->arrive_by); +} - stop_index = hashgrid_result_next_filtered(hg_result, &distance); +static bool +origin_equals(router_request_t *req, router_request_t *oreq){ + street_network_t *origin_l = req->arrive_by ? &req->exit : &req->entry; + street_network_t *origin_r = oreq->arrive_by ? &oreq->exit : &oreq->entry; + if (req->arrive_by != oreq->arrive_by && + origin_l->n_points != origin_r->n_points){ + printf("return f1\n"); + return false; + } + { + spidx_t i_origin_l = 0; + for (;i_origin_l < origin_l->n_points;i_origin_l++){ + if (street_network_duration(origin_l->stop_points[i_origin_l],origin_r) != origin_l->durations[i_origin_l]){ + return false; + } + } + } + return true; +} - while (stop_index != HASHGRID_NONE) { - rtime_t extra_walktime = 0; +/* Use the itineraries in the plan_t to build reversals for time-wait compression. + * Make reversal requests between the departure and the arrival of each itinerary. + * Filter out itineraries that depart more than a travel duration after the best_arrival. + */ +bool +router_request_reverse_plan(router_t *router, router_request_t *req, router_request_t *ret, uint8_t *ret_n, plan_t *plan, uint8_t i_rev) { + int16_t i_itin; + rtime_t last_arrival = 0; + rtime_t last_departure = 0; + + for (i_itin = (int16_t) (plan->n_itineraries-1);i_itin >= 0; --i_itin){ + bool add_request = true; + itinerary_t itin = plan->itineraries[i_itin]; + rtime_t duration = itin.legs[itin.n_legs-1].t1-itin.legs[0].t0; + if (itin.n_legs == 1){ + continue; + } + if (req->arrive_by ? last_departure && itin.legs[itin.n_legs-1].t1 < last_departure - duration: + last_arrival && itin.legs[0].t0 > last_arrival + duration){ + continue; + } + last_departure = MAX(last_departure,itin.legs[0].t0); + last_arrival = MAX(last_arrival,itin.legs[itin.n_legs-1].t1); + ret[*ret_n] = *req; + reverse_request(router,req,&ret[*ret_n], (uint8_t) (itin.n_rides-1), + req->arrive_by ? itin.legs[0].t0 : itin.legs[itin.n_legs-1].t1); + ret[*ret_n].time_cutoff = req->arrive_by ? itin.legs[itin.n_legs-1].t1 : itin.legs[0].t0; + + { + int16_t i_req = 0; + for (;i_req < *ret_n; ++i_req){ + router_request_t *oreq = &ret[i_req]; + if (oreq->arrive_by == ret[*ret_n].arrive_by && + oreq->time == ret[*ret_n].time && + origin_equals(&ret[*ret_n], oreq)){ + if (i_req >= i_rev){ + /* Equivalent request-parameters found in the subset that still has to be processed. + * Increase max_transfers and time_cutoff if necessary*/ + add_request = false; + oreq->max_transfers = MAX(oreq->max_transfers,req[*ret_n].max_transfers); + oreq->time_cutoff = oreq->arrive_by ? MIN(oreq->time_cutoff,req[*ret_n].time_cutoff) : + MAX(oreq->time_cutoff,req[*ret_n].time_cutoff); + break; + }else{ + /* Equivalent request-parameters found in the subset that was already processed: + * Only add the request if the the other's request was too narrow with regard to transfers + * and/or time to include the itinerary found in this new request. + */ + add_request = !(oreq->max_transfers >= req[*ret_n].max_transfers && + req->arrive_by ? oreq->time_cutoff <= req[*ret_n].time_cutoff : + oreq->time_cutoff >= req[*ret_n].time_cutoff); + if (!add_request) break; + } + } + } + } - if (router->best_time[stop_index] != UNREACHED) { - extra_walktime = SEC_TO_RTIME((uint32_t)((distance * RRRR_WALK_COMP) / req->walk_speed)); + if (add_request) (*ret_n)++; + } + return (*ret_n > 0); +} - if (req->arrive_by) { - router->best_time[stop_index] -= extra_walktime; - if (router->best_time[stop_index] > best_time) { - best_stop_index = stop_index; - best_time = router->best_time[stop_index]; - } - } else { - router->best_time[stop_index] += extra_walktime; - if (router->best_time[stop_index] < best_time) { - best_stop_index = stop_index; - best_time = router->best_time[stop_index]; +bool +router_request_reverse_all(router_t *router, router_request_t *req, router_request_t *ret, uint8_t *ret_n, uint8_t i_rev) { + rtime_t best_time; + int8_t round; + + assert (req->max_transfers <= RRRR_DEFAULT_MAX_ROUNDS); + + round = (int8_t) req->max_transfers; + + do { + if (best_time_by_round(router, req, (uint8_t) round, &best_time)) { + bool add_request = true; + ret[*ret_n] = *req; + reverse_request(router,req,&ret[*ret_n], (uint8_t) round, best_time); + { + int16_t i_req = 0; + for (;i_req < *ret_n; ++i_req){ + router_request_t *oreq = &ret[i_req]; + if (oreq->arrive_by == ret[*ret_n].arrive_by && + oreq->time == ret[*ret_n].time && + origin_equals(&ret[*ret_n], oreq)){ + if (i_req >= i_rev){ + /* Equivalent request-parameters found in the subset that still has to be processed. + * Increase max_transfers and time_cutoff if necessary*/ + add_request = false; + oreq->max_transfers = MAX(oreq->max_transfers,req[*ret_n].max_transfers); + oreq->time_cutoff = oreq->arrive_by ? MIN(oreq->time_cutoff,req[*ret_n].time_cutoff) : + MAX(oreq->time_cutoff,req[*ret_n].time_cutoff); + break; + }else{ + /* Equivalent request-parameters found in the subset that was already processed: + * Only add the request if the the other's request was too narrow with regard to transfers + * and/or time to include the itinerary found in this new request. + */ + add_request = !(oreq->max_transfers >= req[*ret_n].max_transfers && + req->arrive_by ? oreq->time_cutoff <= req[*ret_n].time_cutoff : + oreq->time_cutoff >= req[*ret_n].time_cutoff); + if (!add_request) break; + } } } - } - #ifdef RRRR_DEBUG - fprintf (stderr, "%d %s %s (%.0fm) %d %d\n", stop_index, - tdata_stop_id_for_index(router->tdata, stop_index), - tdata_stop_name_for_index(router->tdata, stop_index), - distance, router->best_time[stop_index], extra_walktime); - #endif - - /* get the next potential start stop */ - stop_index = hashgrid_result_next_filtered(hg_result, &distance); + if (add_request) (*ret_n)++; } + round--; + } while (round >= 0); - if (req->arrive_by) { - req->from = (spidx_t) best_stop_index; - } else { - req->to = (spidx_t) best_stop_index; - } + return (*ret_n > 0); +} - /* TODO: Ideally we should implement a o_transfers option here to find the stop that requires the - * least transfers and is the best with respect to arrival time. This might be a different stop - * than the stop that is the best with most transfers. - */ +/* Reverse the direction of the search leaving most request parameters + * unchanged but applying time and transfer cutoffs based on an existing + * result for the same request. Returns a boolean value indicating whether + * the request was successfully reversed. + */ +bool +router_request_reverse(router_t *router, router_request_t *req) { + int16_t max_transfers = req->max_transfers; + uint8_t round = UINT8_MAX; + rtime_t best_time; - } else - #endif - { - best_stop_index = (req->arrive_by ? req->from : req->to); - } + /* range-check to keep search within states array */ + if (max_transfers >= RRRR_DEFAULT_MAX_ROUNDS) + max_transfers = RRRR_DEFAULT_MAX_ROUNDS - 1; - { - /* find the solution with the most transfers and the earliest arrival */ - uint8_t r; - for (r = 0; r <= max_transfers; ++r) { - if (router->states_walk_time[r * router->tdata->n_stops + best_stop_index] != UNREACHED) { - round = r; - /* Instead of the earliest arrival (most transfers) - * use the solution with the least transfers. - */ - if (req->optimise == o_transfers) break; - } + for (; max_transfers >= 0; --max_transfers) { + if (best_time_by_round(router, req, (uint8_t) max_transfers, &best_time)){ + round = (uint8_t) max_transfers; + break; } } - /* In the case that no solution was found, the request will remain unchanged. */ + /* In the case that no solution was found, + * the request will remain unchanged. + */ if (round == UINT8_MAX) return false; req->time_cutoff = req->time; - req->time = router->states_walk_time[round * router->tdata->n_stops + - best_stop_index]; + req->time = best_time; #if 0 fprintf (stderr, "State present at round %d \n", round); - router_state_dump (router, round * router->tdata->n_stops + stop); + router_state_dump (router, round * router->tdata->n_stop_points + sp_index); #endif req->max_transfers = round; req->arrive_by = !(req->arrive_by); #ifdef RRRR_DEBUG router_request_dump(req, router->tdata); range_check(req, router->tdata); - /* TODO: range-check the resulting request here? */ #endif return true; - - /* Eigenlijk zou in de counter clockwise stap een walkleg niet naar de - * target moeten gaan, maar naar de de fictieve arrival / departure halte. - * Zou mooi zijn om een punt te introduceren die dat faciliteert, dan zou - * je op dat punt een apply_hashgrid kunnen doen, ipv apply_transfers. - */ } -time_t req_to_date (router_request_t *req, tdata_t *tdata, struct tm *tm_out) { - time_t seconds; - uint32_t day_mask = req->day_mask; - uint8_t cal_day = 0; - - while (day_mask >>= 1) cal_day++; - seconds = tdata->calendar_start_time + (cal_day * SEC_IN_ONE_DAY); - rrrr_localtime_r(&seconds, tm_out); - - return seconds; -} - -time_t req_to_epoch (router_request_t *req, tdata_t *tdata, struct tm *tm_out) { - time_t seconds; - uint32_t day_mask = req->day_mask; - uint8_t cal_day = 0; - - while (day_mask >>= 1) cal_day++; - - seconds = tdata->calendar_start_time + - (cal_day * SEC_IN_ONE_DAY) + - RTIME_TO_SEC(req->time - RTIME_ONE_DAY); - rrrr_localtime_r(&seconds, tm_out); - - return seconds; -} - -/* Check the given request against the characteristics of the router that will be used. - * Indexes larger than array lengths for the given router, signed values less than zero, etc. - * can and will cause segfaults and present security risks. +/* Check the given request against the characteristics of the router that will + * be used. Indexes larger than array lengths for the given router, signed + * values less than zero, etc. can and will cause segfaults and present + * security risks. * - * We could also infer departure stop etc. from start vehicle_journey here, "missing start point" and reversal problems. + * We could also infer departure stop_point etc. from start vehicle_journey + * here, "missing start point" and reversal problems. */ -bool range_check(router_request_t *req, tdata_t *tdata) { +bool +range_check(router_request_t *req, tdata_t *tdata) { return !(req->walk_speed < 0.1 || - req->from >= tdata->n_stops || - req->to >= tdata->n_stops - ); + req->from_stop_point >= tdata->n_stop_points || + req->to_stop_point >= tdata->n_stop_points || + req->from_stop_area >= tdata->n_stop_areas || + req->to_stop_area >= tdata->n_stop_areas + ); } /* router_request_dump prints the current request structure to the screen */ - -void router_request_dump(router_request_t *req, tdata_t *tdata) { - const char *from_stop_id = tdata_stop_name_for_index(tdata, req->from); - const char *to_stop_id = tdata_stop_name_for_index(tdata, req->to); +void +router_request_dump(router_request_t *req, tdata_t *tdata) { + const char *from_sa_id = tdata_stop_area_name_for_index(tdata, req->from_stop_area); + const char *to_sa_id = tdata_stop_area_name_for_index(tdata, req->to_stop_area); + const char *from_sp_id = tdata_stop_point_name_for_index(tdata, req->from_stop_point); + const char *to_sp_id = tdata_stop_point_name_for_index(tdata, req->to_stop_point); char time[32], time_cutoff[32], date[11]; struct tm ltm; @@ -370,8 +474,12 @@ void router_request_dump(router_request_t *req, tdata_t *tdata) { btimetext(req->time, time); btimetext(req->time_cutoff, time_cutoff); printf("-- Router Request --\n" - "from: %s [%d]\n" - "to: %s [%d]\n" + "from_stop_area: %s [%d]\n" + "from_stop_point: %s [%d]\n" + "from_latlon: %f,%f\n" + "to_stop_area: %s [%d]\n" + "to_stop_point: %s [%d]\n" + "to_latlon: %f,%f\n" "date: %s\n" "time: %s [%d]\n" "speed: %f m/sec\n" @@ -379,7 +487,13 @@ void router_request_dump(router_request_t *req, tdata_t *tdata) { "max xfers: %d\n" "max time: %s\n" "mode: ", - from_stop_id, req->from, to_stop_id, req->to, date, time, + from_sa_id, req->from_stop_area, + from_sp_id, req->from_stop_point, + req->from_latlon.lat, req->from_latlon.lon, + to_sa_id, req->to_stop_area, + to_sp_id, req->to_stop_point, + req->to_latlon.lat,req->to_latlon.lon, + date, time, req->time, req->walk_speed, (req->arrive_by ? "true" : "false"), req->max_transfers, time_cutoff); @@ -398,3 +512,83 @@ void router_request_dump(router_request_t *req, tdata_t *tdata) { printf("\b\n"); } } + +#ifdef RRRR_DEV +void +router_request_dump_exits_and_entries(router_request_t *req, tdata_t *tdata) { + spidx_t i; + printf("Entries: \n"); + for (i = 0; i < req->entry.n_points; i++) { + spidx_t sp_index = req->entry.stop_points[i]; + printf("O %d %s %s, %d seconds\n",sp_index, + tdata_stop_point_id_for_index(tdata, sp_index), + tdata_stop_point_name_for_index(tdata,sp_index), + RTIME_TO_SEC(req->entry.durations[i]) + ); + } + printf("\nExits: \n"); + for (i = 0; i < req->exit.n_points; i++) { + spidx_t sp_index = req->exit.stop_points[i]; + printf("E %d %s %s, %d seconds\n",sp_index, + tdata_stop_point_id_for_index(tdata, sp_index), + tdata_stop_point_name_for_index(tdata,sp_index), + RTIME_TO_SEC(req->exit.durations[i]) + ); + } + printf("\n"); +} +#endif + +static void +mark_stop_area_in_street_network(spidx_t sa_index, rtime_t duration, tdata_t *tdata, street_network_t *sn) { + spidx_t sp_idx = (spidx_t) tdata->n_stop_points; + do { + sp_idx--; + if (tdata->stop_area_for_stop_point[sp_idx] == sa_index) { + street_network_mark_duration_to_stop_point(sn, sp_idx, duration); + } + } while (sp_idx); +} + +bool +router_request_search_street_network (router_request_t *req, tdata_t *tdata) { + if (req->from_stop_area != STOP_NONE) { + latlon_t *latlon; + latlon = tdata_stop_area_coord_for_index (tdata, req->from_stop_area); + street_network_stoppoint_durations (latlon, req->walk_speed, req->walk_max_distance, tdata, &req->entry); + mark_stop_area_in_street_network (req->from_stop_area, 0, tdata, &req->entry); + } else if (req->from_stop_point != STOP_NONE) { + latlon_t *latlon; + latlon = tdata_stop_point_coord_for_index (tdata, req->from_stop_point); + street_network_stoppoint_durations(latlon, req->walk_speed, req->walk_max_distance, tdata, &req->entry); + street_network_mark_duration_to_stop_point (&req->entry, req->from_stop_point, 0); + } else if (req->from_latlon.lat != 0.0 && req->from_latlon.lon != 0.0) { + street_network_stoppoint_durations (&req->from_latlon, req->walk_speed, req->walk_max_distance, tdata, &req->entry); + } else if (req->onboard_journey_pattern == JP_NONE) { + fprintf(stderr, "No coord for entry\n"); + return false; + } + + if (req->to_stop_area != STOP_NONE) { + latlon_t *latlon; + latlon = tdata_stop_area_coord_for_index (tdata, req->to_stop_area); + street_network_stoppoint_durations (latlon, req->walk_speed, req->walk_max_distance, tdata, &req->exit); + mark_stop_area_in_street_network (req->to_stop_area, 0, tdata, &req->exit); + } else if (req->to_stop_point != STOP_NONE) { + latlon_t *latlon; + latlon = tdata_stop_point_coord_for_index (tdata, req->to_stop_point); + street_network_stoppoint_durations (latlon, req->walk_speed, req->walk_max_distance, tdata, &req->exit); + street_network_mark_duration_to_stop_point (&req->exit, req->to_stop_point, 0); + } else if (req->to_latlon.lat != 0.0 && req->to_latlon.lon != 0.0) { + street_network_stoppoint_durations (&req->to_latlon, req->walk_speed, req->walk_max_distance, tdata, &req->exit); + } else { + fprintf(stderr, "No coord for exit\n"); + return false; + } + #ifdef RRRR_DEV + router_request_dump_exits_and_entries (req, tdata); + fprintf (stderr, "%d entries, %d exits\n", req->entry.n_points, req->exit.n_points); + #endif + return true; +} + diff --git a/router_request.h b/router_request.h index a0a3630..c10069e 100644 --- a/router_request.h +++ b/router_request.h @@ -1,3 +1,8 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and at + * https://github.com/bliksemlabs/rrrr/ + */ + #ifndef _ROUTER_REQUEST_H #define _ROUTER_REQUEST_H @@ -5,18 +10,23 @@ #include "router.h" #include "util.h" #include "config.h" - -time_t req_to_date (router_request_t *req, tdata_t *tdata, struct tm *tm_out); -time_t req_to_epoch (router_request_t *req, tdata_t *tdata, struct tm *tm_out); +#include "router_result.h" void router_request_initialize(router_request_t *req); void router_request_from_epoch(router_request_t *req, tdata_t *tdata, time_t epochtime); void router_request_randomize (router_request_t *req, tdata_t *tdata); -void router_request_next(router_request_t *req, rtime_t inc); bool router_request_reverse(router_t *router, router_request_t *req); +bool router_request_reverse_all(router_t *router, router_request_t *req, router_request_t *ret, uint8_t *ret_n, uint8_t i_rev); +bool router_request_search_street_network (router_request_t *req, tdata_t *tdata); + +/* Use the itineraries in the plan_t to build reversals for time-wait compression. + * Make reversal requests between the departure and the arrival of each itinerary. + * Filter out itineraries that depart more than a travel duration after the best_arrival. + */ +bool router_request_reverse_plan(router_t *router, router_request_t *req, router_request_t *ret, uint8_t *ret_n, plan_t *plan, uint8_t i_rev); time_t router_request_to_date (router_request_t *req, tdata_t *tdata, struct tm *tm_out); time_t router_request_to_epoch (router_request_t *req, tdata_t *tdata, struct tm *tm_out); bool range_check(router_request_t *req, tdata_t *router); void router_request_dump(router_request_t *req, tdata_t *tdata); - +void router_request_dump_exits_and_entries(router_request_t *req, tdata_t *tdata); #endif diff --git a/router_result.c b/router_result.c index 44e0f2a..2149f83 100644 --- a/router_result.c +++ b/router_result.c @@ -1,25 +1,101 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and at + * https://github.com/bliksemlabs/rrrr/ + */ + #include "rrrr_types.h" #include "router_result.h" +#include "street_network.h" +#include "plan.h" #include -#include +#include -/* Reverse the times and stops in a leg. Used for creating arrive-by itineraries. */ -static void leg_swap (leg_t *leg) { +/* Reverse the times and stops in a leg. + * Used for creating arrive-by itineraries. + */ +static void leg_swap(leg_t *leg) { struct leg temp = *leg; - leg->s0 = temp.s1; - leg->s1 = temp.s0; + leg->sp_from = temp.sp_to; + leg->sp_to = temp.sp_from; leg->t0 = temp.t1; leg->t1 = temp.t0; +#ifdef RRRR_FEATURE_REALTIME_EXPANDED + leg->jpp0 = temp.jpp1; + leg->jpp1 = temp.jpp0; + leg->d0 = temp.d0; + leg->d1 = temp.d1; +#endif } -/* Checks charateristics that should be the same for all vj plans produced by this router: - All stops should chain, all times should be increasing, all waits should be at the ends of walk legs, etc. - Returns true if any of the checks fail, false if no problems are detected. */ -static bool check_plan_invariants (plan_t *plan) { - uint8_t i_itinerary; - bool fail = false; +#ifdef RRRR_FEATURE_REALTIME_EXPANDED + +static void leg_add_ride_delay(leg_t *leg, router_t *router, uint64_t i_ride) { + journey_pattern_t *jp; + vehicle_journey_t *vj; + vjidx_t vj_index; + + jp = router->tdata->journey_patterns + router->states_back_journey_pattern[i_ride]; + vj_index = jp->vj_index + router->states_back_vehicle_journey[i_ride]; + vj = router->tdata->vjs + vj_index; + + if (router->tdata->vj_stoptimes[vj_index] && + router->tdata->stop_times[vj->stop_times_offset + router->states_journey_pattern_point[i_ride]].arrival != UNREACHED) { + + leg->d0 = (int16_t) (RTIME_TO_SEC_SIGNED(router->tdata->vj_stoptimes[vj_index][router->states_back_journey_pattern_point[i_ride]].departure) - RTIME_TO_SEC_SIGNED(router->tdata->stop_times[vj->stop_times_offset + router->states_back_journey_pattern_point[i_ride]].departure + vj->begin_time)); + leg->d1 = (int16_t) (RTIME_TO_SEC_SIGNED(router->tdata->vj_stoptimes[vj_index][router->states_journey_pattern_point[i_ride]].arrival) - RTIME_TO_SEC_SIGNED(router->tdata->stop_times[vj->stop_times_offset + router->states_journey_pattern_point[i_ride]].arrival + vj->begin_time)); + } else { + leg->d0 = 0; + leg->d1 = 0; + } + +} + +#endif + +static void leg_add_ride(itinerary_t *itin, leg_t *leg, router_t *router, router_request_t *req, + uint64_t i_state, spidx_t rid_from_stoppoint) { + uint64_t i_ride = i_state + rid_from_stoppoint; + leg->sp_from = router->states_ride_from[i_ride]; + leg->sp_to = rid_from_stoppoint; + leg->t0 = router->states_board_time[i_ride]; + leg->t1 = router->states_time[i_ride]; + leg->journey_pattern = router->states_back_journey_pattern[i_ride]; + leg->vj = router->states_back_vehicle_journey[i_ride]; +#ifdef RRRR_FEATURE_REALTIME_EXPANDED + leg->jpp0 = router->states_back_journey_pattern_point[i_ride]; + leg->jpp1 = router->states_journey_pattern_point[i_ride]; + { /* Infer the serviceday using the time */ + int8_t i_serviceday = 0; + leg->cal_day = 0; + for (; i_serviceday < router->n_servicedays; ++i_serviceday){ + if (leg->t0 == router->servicedays[i_serviceday].midnight + + tdata_stoptime_for_index(router->tdata, leg->journey_pattern, leg->jpp0, leg->vj, req->arrive_by)){ + calendar_t day_mask = router->servicedays[i_serviceday].mask; + while (day_mask >>= 1) { + leg->cal_day++; + } + break; + } + } + } + leg_add_ride_delay(leg, router, i_ride); +#endif + if (req->arrive_by) leg_swap(leg); + ++itin->n_legs; +} + +/* Checks charateristics that should be the same for all vj plans produced + * by this router: + * All stops should chain, all times should be increasing, all waits + * should be at the ends of walk legs, etc. + * Returns true if any of the checks fail, false if no problems are detected. + */ +static bool check_plan_invariants(plan_t *plan) { itinerary_t *prev_itin = NULL; rtime_t prev_target_time = UNREACHED; + uint8_t i_itinerary; + bool fail = false; + /* Loop over all itineraries in this plan. */ for (i_itinerary = 0; i_itinerary < plan->n_itineraries; ++i_itinerary) { itinerary_t *itin = plan->itineraries + i_itinerary; @@ -30,7 +106,9 @@ static bool check_plan_invariants (plan_t *plan) { /* Itinarary has at least one leg. Grab its first and last leg. */ leg_t *leg0 = itin->legs; leg_t *legN = itin->legs + (itin->n_legs - 1); - /* Itineraries should be Pareto-optimal. Increase in number of rides implies improving arrival time. */ + /* Itineraries should be Pareto-optimal. + * Increase in number of rides implies improving arrival time. + */ rtime_t target_time = plan->req.arrive_by ? leg0->t0 : legN->t1; if (prev_itin != NULL) { if (itin->n_legs <= prev_itin->n_legs) { @@ -44,16 +122,20 @@ static bool check_plan_invariants (plan_t *plan) { } prev_target_time = target_time; prev_itin = itin; - /* Check that itinerary does indeed connect the places in the request. */ - if (leg0->s0 != plan->req.from) { + /* Check that itinerary does indeed connect the places + * in the request. + */ + if (leg0->sp_from != plan->req.from_stop_point) { fprintf(stderr, "itinerary does not begin at from location.\n"); fail = true; } - if (legN->s1 != plan->req.to) { + if (legN->sp_to != plan->req.to_stop_point) { fprintf(stderr, "itinerary does not end at to location.\n"); fail = true; } - /* Check that the itinerary respects the depart after or arrive-by criterion */ + /* Check that the itinerary respects the depart after or + * arrive-by criterion. + */ /* finish when rtimes are in requests if (plan->req.arrive_by) { if (itin->legs[itin->n_legs - 1].s1 > plan->req.time)... @@ -74,10 +156,10 @@ static bool check_plan_invariants (plan_t *plan) { for (i_leg = 0; i_leg < itin->n_legs; ++i_leg) { leg_t *leg = itin->legs + i_leg; if (i_leg % 2 == 0) { - if (leg->journey_pattern != WALK) fprintf(stderr, "even numbered leg %d has journey_pattern %d not WALK.\n", i_leg, leg->journey_pattern); + if (leg->journey_pattern < WALK) fprintf(stderr, "even numbered leg %d has journey_pattern %d not WALK.\n", i_leg, leg->journey_pattern); fail = true; } else { - if (leg->journey_pattern == WALK) fprintf(stderr, "odd numbered leg %d has journey_pattern WALK.\n", i_leg); + if (leg->journey_pattern >= WALK) fprintf(stderr, "odd numbered leg %d has journey_pattern WALK.\n", i_leg); fail = true; } if (leg->t1 < leg->t0) { @@ -85,16 +167,16 @@ static bool check_plan_invariants (plan_t *plan) { fail = true; } if (i_leg > 0) { - if (leg->s0 != prev_leg->s1) { - fprintf(stderr, "legs do not chain: leg %d begins with stop %d, previous leg ends with stop %d.\n", i_leg, leg->s0, prev_leg->s1); + if (leg->sp_from != prev_leg->sp_to) { + fprintf(stderr, "legs do not chain: leg %d begins with stop_point %d, previous leg ends with stop_point %d.\n", i_leg, leg->sp_from, prev_leg->sp_to); fail = true; } - if (leg->journey_pattern == WALK && leg->t0 != prev_leg->t1) { + if (leg->journey_pattern >= WALK && leg->t0 != prev_leg->t1) { /* This will fail unless reversal is being performed */ #if 0 fprintf(stderr, "walk leg does not immediately follow ride: leg %d begins at time %d, previous leg ends at time %d.\n", l, leg->t0, prev_leg->t1); fail = true; -#endif + #endif } if (leg->t0 < prev_leg->t1) { fprintf(stderr, "itin %d: non-increasing times between legs %d and %d: %d, %d\n", @@ -109,283 +191,431 @@ static bool check_plan_invariants (plan_t *plan) { return fail; } -bool router_result_to_plan (struct plan *plan, router_t *router, router_request_t *req) { - itinerary_t *itin; - uint8_t i_transfer; - /* Router states are a 2D array of stride n_stops */ - /* router_state_t (*states)[n_stops] = (router_state_t(*)[]) router->states; */ - plan->n_itineraries = 0; - plan->req = *req; /* copy the request into the plan for use in rendering */ - itin = plan->itineraries; - /* Loop over the rounds to get ending states of itineraries using different numbers of vehicles */ - for (i_transfer = 0; i_transfer < RRRR_DEFAULT_MAX_ROUNDS; ++i_transfer) { - /* Work backward from the target to the origin */ - uint64_t i_state; - leg_t *l = itin->legs; /* the slot in which record a leg, reversing them for forward vehicle_journey's */ - uint32_t stop = router->target; /* Work backward from the target to the origin */ - int16_t j_transfer; /* signed int because we will be decreasing */ - - i_state = (i_transfer * router->tdata->n_stops) + stop; - - /* skip rounds that were not reached */ - if (router->states_walk_time[i_state] == UNREACHED) continue; - itin->n_rides = i_transfer + 1; - itin->n_legs = itin->n_rides * 2 + 1; /* always same number of legs for same number of transfers */ - if ( ! req->arrive_by) l += itin->n_legs - 1; - /* Follow the chain of states backward */ - for (j_transfer = i_transfer; j_transfer >= 0; --j_transfer) { - uint64_t i_walk, i_ride; - uint32_t walk_stop, ride_stop; - - i_state = (((uint8_t) j_transfer) * router->tdata->n_stops); - - if (stop > router->tdata->n_stops) { - fprintf (stderr, "ERROR: stopid %d out of range.\n", stop); - return false; - } +static void leg_add_target(itinerary_t *itin, leg_t *leg, router_t *router, router_request_t *req, + uint64_t i_state, street_network_t *target, int32_t i_target) { + spidx_t sp_index = target->stop_points[i_target]; + /* Target to first transit with streetnetwork phase */ + leg->sp_from = sp_index; + leg->sp_to = req->arrive_by ? req->from_stop_point : req->to_stop_point; + + /* Rendering the walk requires already having the ride arrival time */ + leg->t0 = router->states_time[i_state + sp_index]; + leg->t1 = req->arrive_by ? leg->t0 - target->durations[i_target] : + leg->t0 + target->durations[i_target]; + leg->journey_pattern = STREET; + leg->vj = STREET; + if (req->arrive_by) leg_swap(leg); + ++itin->n_legs; +} - /* Walk phase */ - i_walk = i_state + stop; - if (router->states_walk_time[i_walk] == UNREACHED) { - fprintf (stderr, "ERROR: stop %d was unreached by walking.\n", stop); - return false; - } - walk_stop = stop; - stop = router->states_walk_from[i_walk]; /* follow the chain of states backward */ +static void leg_add_origin(itinerary_t *itin, leg_t *leg, router_t *router, router_request_t *req, + uint64_t i_state, street_network_t *origin, int32_t i_origin, spidx_t board_sp) { + spidx_t sp_index = origin->stop_points[i_origin]; + /* Target to first transit with streetnetwork phase */ + leg->sp_from = req->arrive_by ? req->to_stop_point : req->from_stop_point; + leg->sp_to = sp_index; + + /* Rendering the walk requires already having the ride arrival time */ + leg->t1 = router->states_board_time[i_state + board_sp]; + leg->t0 = req->arrive_by ? leg->t1 + origin->durations[i_origin] : + leg->t1 - origin->durations[i_origin]; + leg->journey_pattern = STREET; + leg->vj = STREET; + if (req->arrive_by) leg_swap(leg); + ++itin->n_legs; +} - /* Ride phase */ - i_ride = i_state + stop; - if (router->states_time[i_ride] == UNREACHED) { - fprintf (stderr, "ERROR: stop %d was unreached by riding.\n", stop); - return false; - } - ride_stop = stop; - stop = router->states_ride_from[i_ride]; /* follow the chain of states backward */ - - /* Walk phase */ - l->s0 = router->states_walk_from[i_walk]; - l->s1 = walk_stop; - l->t0 = router->states_time[i_ride]; /* Rendering the walk requires already having the ride arrival time */ - l->t1 = router->states_walk_time[i_walk]; - l->journey_pattern = WALK; - l->vj = WALK; - - if (req->arrive_by) leg_swap (l); - l += (req->arrive_by ? 1 : -1); /* next leg */ - - /* Ride phase */ - l->s0 = router->states_ride_from[i_ride]; - l->s1 = ride_stop; - l->t0 = router->states_board_time[i_ride]; - l->t1 = router->states_time[i_ride]; - l->journey_pattern = router->states_back_journey_pattern[i_ride]; - l->vj = router->states_back_vehicle_journey[i_ride]; - - #ifdef RRRR_FEATURE_REALTIME_EXPANDED - { - journey_pattern_t *jp; - vehicle_journey_t *vj; - uint32_t vj_index; - jp = router->tdata->journey_patterns + router->states_back_journey_pattern[i_ride]; - vj_index = jp->vj_ids_offset + router->states_back_vehicle_journey[i_ride]; - vj = router->tdata->vjs + vj_index; +static void leg_add_direct(itinerary_t *itin, leg_t *leg, router_request_t *req, rtime_t duration) { + /* Target to first transit with streetnetwork phase */ + leg->sp_from = req->arrive_by ? req->to_stop_point : req->from_stop_point; + leg->sp_to = req->arrive_by ? req->from_stop_point : req->to_stop_point; - if (router->tdata->vj_stoptimes[vj_index] && - router->tdata->stop_times[vj->stop_times_offset + router->states_journey_pattern_point[i_ride]].arrival != UNREACHED) { + /* Rendering the walk requires already having the ride arrival time */ + leg->t0 = req->time; + leg->t1 = leg->t0 + duration; + leg->journey_pattern = STREET; + leg->vj = STREET; + if (req->arrive_by) leg_swap(leg); + ++itin->n_legs; +} - l->d0 = RTIME_TO_SEC_SIGNED(router->tdata->vj_stoptimes[vj_index][router->states_back_journey_pattern_point[i_ride]].departure) - RTIME_TO_SEC_SIGNED(router->tdata->stop_times[vj->stop_times_offset + router->states_back_journey_pattern_point[i_ride]].departure + vj->begin_time); - l->d1 = RTIME_TO_SEC_SIGNED(router->tdata->vj_stoptimes[vj_index][router->states_journey_pattern_point[i_ride]].arrival) - RTIME_TO_SEC_SIGNED(router->tdata->stop_times[vj->stop_times_offset + router->states_journey_pattern_point[i_ride]].arrival + vj->begin_time); - } else { - l->d0 = 0; - l->d1 = 0; - } - } - #endif +static void leg_add_vj_interline(itinerary_t *itin, leg_t *leg, router_t *router, router_request_t *req, + uint64_t i_state, spidx_t board_sp, spidx_t from_sp, spidx_t to_sp) { + leg->sp_from = from_sp; + leg->sp_to = to_sp; + + /* Rendering the walk requires already having the ride arrival time */ + leg->t0 = router->states_time[i_state + to_sp]; + leg->t1 = router->states_board_time[i_state + board_sp]; + leg->journey_pattern = STAY_ON; + leg->vj = STAY_ON; + if (req->arrive_by) leg_swap(leg); + ++itin->n_legs; +} + +static void leg_add_transfer(itinerary_t *itin, leg_t *leg, router_t *router, router_request_t *req, + uint64_t i_state, spidx_t walk_end_stop_point) { + /* Walk phase */ + leg->sp_from = router->states_walk_from[i_state + walk_end_stop_point]; + leg->sp_to = walk_end_stop_point; + /* Rendering the walk requires already having the ride arrival time */ + leg->t0 = router->states_time[i_state + leg->sp_from]; + leg->t1 = router->states_walk_time[i_state + walk_end_stop_point]; + leg->journey_pattern = WALK; + leg->vj = WALK; + if (req->arrive_by) leg_swap(leg); + ++itin->n_legs; +} - if (req->arrive_by) leg_swap (l); - l += (req->arrive_by ? 1 : -1); /* next leg */ +static void +leg_add_onboard(itinerary_t *itin, leg_t *leg, router_request_t *req){ + leg->sp_from = leg->sp_to = ONBOARD; + leg->t0 = leg->t1 = req->time; + leg->journey_pattern = leg->vj = STREET; + leg->sp_from = ONBOARD; + leg->t0 = req->time; + ++itin->n_legs; +} + +static void +reverse_legs(itinerary_t *itin){ + leg_t legs[MAX_LEGS]; + int32_t i_leg; + for (i_leg = 0; i_leg < itin->n_legs; ++i_leg){ + legs[i_leg] = itin->legs[itin->n_legs-i_leg-1]; + } + for (i_leg = 0; i_leg < itin->n_legs; ++i_leg){ + itin->legs[i_leg] = legs[i_leg]; + } +} +/* Set interline if applicable between legs if not stored within the state structure */ +static void render_interlines(router_t *router, itinerary_t *itin){ + int16_t i_leg = 0; + leg_t *last_vj_leg = NULL; + bool inInterline = false; + for (;i_leg < itin->n_legs;++i_leg){ + leg_t *leg = &itin->legs[i_leg]; + if (leg->journey_pattern == STAY_ON){ + inInterline = true; + continue; + }else if (leg->journey_pattern >= WALK){ + inInterline = false; + continue; } - if (req->onboard_journey_pattern_offset != NONE) { - if (!req->arrive_by) { - /* Results starting on board do not have an initial walk leg. */ - l->s0 = l->s1 = ONBOARD; - l->t0 = l->t1 = req->time; - l->journey_pattern = l->vj = WALK; - l += 1; /* move back to first transit leg */ - l->s0 = ONBOARD; - l->t0 = req->time; - } else { - #ifdef RRRR_DEBUG - fprintf(stderr, "We observed an onboard departure with an arrive by.\n"); - #endif - return false; + if (last_vj_leg && !inInterline){ + journey_pattern_t *jp = &router->tdata->journey_patterns[leg->journey_pattern]; + vehicle_journey_ref_t *interline = &router->tdata->vehicle_journey_transfers_forward[jp->vj_index + last_vj_leg->vj]; + if (interline->jp_index == leg->journey_pattern && interline->vj_offset == leg->vj){ + (&itin->legs[i_leg - 1])->journey_pattern = STAY_ON; + (&itin->legs[i_leg - 1])->t1 = leg->t0; + --itin->n_rides; } - } else { - /* The initial walk leg leading out of the search origin. This is inferred, not stored explicitly. */ - uint32_t origin_stop = (req->arrive_by ? req->to : req->from); - rtime_t duration; - - l->s0 = origin_stop; - l->s1 = stop; - /* It would also be possible to work from s1 to s0 and compress out the wait time. */ - l->t0 = router->states_time[origin_stop]; - duration = transfer_duration (router->tdata, req, l->s0, l->s1); - l->t1 = l->t0 + (req->arrive_by ? -duration : +duration); - l->journey_pattern = WALK; - l->vj = WALK; - if (req->arrive_by) leg_swap (l); } - /* Move to the next itinerary in the plan. */ - plan->n_itineraries += 1; - itin += 1; + last_vj_leg = leg; } - return check_plan_invariants (plan); } -static char * -plan_render_itinerary (struct itinerary *itin, tdata_t *tdata, char *b, char *b_end) { - leg_t *leg; - - b += sprintf (b, "\nITIN %d rides \n", itin->n_rides); - - /* Render the legs of this itinerary, which are in chronological order */ - for (leg = itin->legs; leg < itin->legs + itin->n_legs; ++leg) { - char ct0[16]; - char ct1[16]; - const char *agency_name, *short_name, *headsign, *productcategory, *leg_mode = NULL; - char *alert_msg = NULL; - const char *s0_id = tdata_stop_name_for_index(tdata, leg->s0); - const char *s1_id = tdata_stop_name_for_index(tdata, leg->s1); - float d0 = 0.0, d1 = 0.0; - - btimetext(leg->t0, ct0); - btimetext(leg->t1, ct1); - - /* d1 = 0.0; */ - - if (leg->journey_pattern == WALK) { - agency_name = ""; - short_name = "walk"; - headsign = "walk"; - productcategory = ""; - - /* Skip uninformative legs that just tell you to stay in the same place. if (leg->s0 == leg->s1) continue; */ - if (leg->s0 == ONBOARD) continue; - if (leg->s0 == leg->s1) leg_mode = "WAIT"; - else leg_mode = "WALK"; - } else { - agency_name = tdata_agency_name_for_journey_pattern(tdata, leg->journey_pattern); - short_name = tdata_line_code_for_journey_pattern(tdata, leg->journey_pattern); - headsign = tdata_headsign_for_journey_pattern(tdata, leg->journey_pattern); - productcategory = tdata_productcategory_for_journey_pattern(tdata, leg->journey_pattern); - #ifdef RRRR_FEATURE_REALTIME_EXPANDED - d0 = leg->d0 / 60.0f; - d1 = leg->d1 / 60.0f; - #endif - - if ((tdata->journey_patterns[leg->journey_pattern].attributes & m_tram) == m_tram) leg_mode = "TRAM"; else - if ((tdata->journey_patterns[leg->journey_pattern].attributes & m_subway) == m_subway) leg_mode = "SUBWAY"; else - if ((tdata->journey_patterns[leg->journey_pattern].attributes & m_rail) == m_rail) leg_mode = "RAIL"; else - if ((tdata->journey_patterns[leg->journey_pattern].attributes & m_bus) == m_bus) leg_mode = "BUS"; else - if ((tdata->journey_patterns[leg->journey_pattern].attributes & m_ferry) == m_ferry) leg_mode = "FERRY"; else - if ((tdata->journey_patterns[leg->journey_pattern].attributes & m_cablecar) == m_cablecar) leg_mode = "CABLE_CAR"; else - if ((tdata->journey_patterns[leg->journey_pattern].attributes & m_gondola) == m_gondola) leg_mode = "GONDOLA"; else - if ((tdata->journey_patterns[leg->journey_pattern].attributes & m_funicular) == m_funicular) leg_mode = "FUNICULAR"; else - leg_mode = "INVALID"; - - #ifdef RRRR_FEATURE_REALTIME_ALERTS - if (leg->journey_pattern != WALK && tdata->alerts) { - size_t i_entity; - for (i_entity = 0; i_entity < tdata->alerts->n_entity; ++i_entity) { - if (tdata->alerts->entity[i_entity] && - tdata->alerts->entity[i_entity]->alert) { - - TransitRealtime__Alert *alert = tdata->alerts->entity[i_entity]->alert; - size_t i_informed_entity; - - for (i_informed_entity = 0; i_informed_entity < alert->n_informed_entity; ++i_informed_entity) { - TransitRealtime__EntitySelector *informed_entity = alert->informed_entity[i_informed_entity]; - - if ( ( (!informed_entity->route_id) || ((uint32_t) *(informed_entity->route_id) == leg->journey_pattern) ) && - ( (!informed_entity->stop_id) || ((uint32_t) *(informed_entity->stop_id) == leg->s0) ) && - ( (!informed_entity->trip) || (!informed_entity->trip->trip_id) || ((uint32_t) *(informed_entity->trip->trip_id) == leg->vj) ) - /* TODO: need to have rtime_to_date for informed_entity->vj->start_date */ - /* TODO: need to have rtime_to_epoch for informed_entity->active_period */ - ) { - alert_msg = alert->header_text->translation[0]->text; - } - - if (alert_msg) break; - } - - /* TODO: theoretically we could have multiple alert messages */ - if (alert_msg) break; - } +static bool +render_itinerary(router_t *router, router_request_t *req, itinerary_t *itin, + uint8_t round, street_network_t *target, spidx_t i_target) { + jppidx_t jp_index; + jp_vjoffset_t vj_offset; + street_network_t *origin = req->arrive_by ? &req->exit : &req->entry; + spidx_t sp_index = target->stop_points[i_target]; + rtime_t duration_target = target->durations[i_target]; + spidx_t current_sp = sp_index; + uint64_t i_state = (((uint64_t) round) * router->tdata->n_stop_points); + + itinerary_init (itin); + + if (router->states_time[i_state + sp_index] == UNREACHED || + router->states_back_journey_pattern[i_state + sp_index] == JP_NONE) { + /* Render a itinerary that does not touch the transit network */ + leg_add_direct(itin, itin->legs + itin->n_legs, req, duration_target); + return round == 0; + } else { + /* Append the leg between the target and the first vehicle_journey in the itinerary*/ + leg_add_target(itin, itin->legs + itin->n_legs, router, req, i_state, target, i_target); + } + + /* Start with the first vehicle_journey and then navigate back through the states to an origin */ + jp_index = router->states_back_journey_pattern[i_state + sp_index]; + vj_offset = router->states_back_vehicle_journey[i_state + sp_index]; + ++itin->n_rides; + while (itin->n_legs < 100) { + spidx_t board_sp = current_sp; + rtime_t current_time; +#ifdef RRRR_INTERLINE_DEBUG + printf("JP is is for line %s\n", tdata_line_id_for_journey_pattern(router->tdata, jp_index)); + printf("JP_index %d, Destination %s\n", jp_index, tdata_headsign_for_journey_pattern(router->tdata, jp_index)); + printf("Trip_id %s\n", tdata_vehicle_journey_id_for_jp_vj_offset(router->tdata, jp_index, vj_offset)); + printf("Add_ride_new From Sp_index %d %s\n", sp_index, tdata_stop_point_name_for_index(router->tdata, current_sp)); +#endif + leg_add_ride(itin, itin->legs + itin->n_legs, router, req, i_state, current_sp); +#ifdef RRRR_INTERLINE_DEBUG + printf("Ride from %s [%d] to %s [%d]\n", + tdata_stop_point_name_for_index(router->tdata, + (itin->legs + itin->n_legs-1)->sp_from), + (itin->legs + itin->n_legs-1)->sp_from, + tdata_stop_point_name_for_index(router->tdata, + (itin->legs + itin->n_legs-1)->sp_to), + (itin->legs + itin->n_legs-1)->sp_to); +#endif + + current_time = router->states_board_time[i_state + current_sp]; + current_sp = router->states_ride_from[i_state + current_sp]; +#ifdef RRRR_INTERLINE_DEBUG + { + char time32[32]; + char time33[32]; + printf("Check interline at Sp_index %d %s current_time %s, states_walk_time %s, prev_round states_walk_time %s" + "\n", sp_index, tdata_stop_point_name_for_index(router->tdata, current_sp),btimetext(current_time,time32), + btimetext(router->states_walk_time[i_state + current_sp],time32),btimetext(router->states_walk_time[i_state + current_sp + router->tdata->n_stop_points],time33)); + + printf("current_time %d, states_walk_time %d\n", + current_time, + router->states_walk_time[i_state + current_sp]); + + printf("current_time %s, states_walk_time %s\n", + btimetext(current_time, time32), + btimetext(router->states_walk_time[i_state + current_sp], time33)); + + printf("current_sp %d, states_walk_from %d (%s) JP_back %d\n", + current_sp, + router->states_walk_from[i_state + current_sp], + tdata_stop_point_name_for_index(router->tdata, current_sp), + router->states_back_journey_pattern[i_state + current_sp]); + } +#endif + + + if (street_network_duration(current_sp, origin) != UNREACHED) { + /* Origin reached, completing the itinerary */ + int32_t i_origin = 0; + for (;i_origin < origin->n_points; i_origin++){ + if (origin->stop_points[i_origin] == current_sp) + break; + } + leg_add_origin(itin, itin->legs + itin->n_legs, router, req, + i_state, origin, i_origin, board_sp); + if (!req->arrive_by) reverse_legs(itin); + { + rtime_t duration_from_final_sp_to_target_on_sn = street_network_duration(board_sp, target); + rtime_t duration_from_first_sp_to_target_on_sp = street_network_duration(itin->legs[0].sp_to, target); + if (duration_from_final_sp_to_target_on_sn != UNREACHED && + duration_from_final_sp_to_target_on_sn > duration_from_first_sp_to_target_on_sp) { + /* This transit itinerary actually brings us further away from the target, + * thus ignore this journey and replace it by a direct itinerary on the street_network */ + itin->n_legs = 0; + leg_add_direct(itin, itin->legs + itin->n_legs, req, duration_target); + return round == 0; } } - #endif + return true; } + else if (req->onboard_journey_pattern != JP_NONE && + req->onboard_journey_pattern == jp_index && + req->onboard_journey_pattern_vjoffset == vj_offset){ + /* Change the start-position of the first transit leg to ONBOARD */ + (itin->legs + itin->n_legs-1)->sp_from = ONBOARD; + leg_add_onboard(itin, itin->legs + itin->n_legs, req); + if (!req->arrive_by) reverse_legs(itin); + render_interlines(router,itin); + return true; + } + /* Check whether we arrived on this stop_point before the time we could have walked there + * This implies a vehicle_journey interline */ + else if (round == 0 || + router->states_walk_time[i_state + current_sp - router->tdata->n_stop_points] == UNREACHED || + (req->arrive_by ? current_time > router->states_walk_time[i_state + current_sp - router->tdata->n_stop_points] : + current_time < router->states_walk_time[i_state + current_sp - router->tdata->n_stop_points])) { +#ifdef RRRR_INTERLINE_DEBUG + printf("current_sp %d, states_walk_from %d (%s) <%d>\n", + current_sp, + router->states_walk_from[i_state + current_sp], + tdata_stop_point_name_for_index(router->tdata, current_sp), + router->states_walk_from[i_state + current_sp] != current_sp + ); +#endif - /* TODO: we are able to calculate the maximum length required for each line - * therefore we could prevent a buffer overflow from happening. */ - b += sprintf (b, "%s %5d %3d %5d %5d %s %+3.1f %s %+3.1f ;%s;%s;%s;%s;%s;%s;%s\n", - leg_mode, leg->journey_pattern, leg->vj, leg->s0, leg->s1, ct0, d0, ct1, d1, agency_name, short_name, headsign, productcategory, s0_id, s1_id, - (alert_msg ? alert_msg : "")); - - /* EXAMPLE - polyline_for_leg (tdata, leg); - b += sprintf (b, "%s\n", polyline_result()); - */ - - if (b > b_end) { - fprintf (stderr, "buffer overflow\n"); - return b; - /* exit(2); */ + journey_pattern_t *jp = &router->tdata->journey_patterns[jp_index]; + vehicle_journey_ref_t *vj_interline = req->arrive_by ? + &router->tdata->vehicle_journey_transfers_forward[jp->vj_index + vj_offset] : + &router->tdata->vehicle_journey_transfers_backward[jp->vj_index + vj_offset]; + if (vj_interline->jp_index != JP_NONE) { + jppidx_t prev_jp_index = vj_interline->jp_index; + jp_vjoffset_t prev_vj_offset = vj_interline->vj_offset; + journey_pattern_t *prev_jp = &router->tdata->journey_patterns[prev_jp_index]; + spidx_t last_sp_of_prev_vj = req->arrive_by ? router->tdata->journey_pattern_points[prev_jp->journey_pattern_point_offset] : + router->tdata->journey_pattern_points[prev_jp->journey_pattern_point_offset + prev_jp->n_stops - 1]; + if (router->states_time[i_state + last_sp_of_prev_vj] != UNREACHED || + router->states_back_journey_pattern[i_state + last_sp_of_prev_vj] == prev_jp_index || + router->states_back_vehicle_journey[i_state + last_sp_of_prev_vj] == prev_vj_offset) { + leg_add_vj_interline(itin, itin->legs + itin->n_legs, router, req, i_state, board_sp, current_sp, last_sp_of_prev_vj); + current_sp = last_sp_of_prev_vj; + jp_index = prev_jp_index; + vj_offset = prev_vj_offset; + continue; + } + } + } + { + i_state -= router->tdata->n_stop_points; + leg_add_transfer(itin, itin->legs + itin->n_legs, router, req, i_state, current_sp); +#ifdef RRRR_INTERLINE_DEBUG + printf("Walk from %s [%d] to %s [%d]\n", + tdata_stop_point_name_for_index(router->tdata, + (itin->legs + itin->n_legs-1)->sp_from), + (itin->legs + itin->n_legs-1)->sp_from, + tdata_stop_point_name_for_index(router->tdata, + (itin->legs + itin->n_legs-1)->sp_to), + (itin->legs + itin->n_legs-1)->sp_to); +#endif + current_sp = router->states_walk_from[i_state + current_sp]; + ++itin->n_rides; + --round; + if (router->states_time[i_state + current_sp] == UNREACHED){ + fprintf(stderr,"Transfer to unreached location\n"); + return false; + } + if (street_network_duration(current_sp,target) < duration_target){ + /* This journey is sub-optimal as it passes a more optimal target */ + return false; + } + jp_index = router->states_back_journey_pattern[i_state + current_sp]; + vj_offset = router->states_back_vehicle_journey[i_state + current_sp]; } } - - return b; + fprintf(stderr, "Something went terribly wrong during rendering\n"); + return false; } -/* Write a plan structure out to a text buffer in tabular format. */ -static uint32_t -plan_render(plan_t *plan, tdata_t *tdata, router_request_t *req, char *buf, uint32_t buflen) { - char *b = buf; - char *b_end = buf + buflen; - - if ((req->optimise & o_all) == o_all) { - /* Iterate over itineraries in this plan, which are in increasing order of number of rides */ - itinerary_t *itin; - for (itin = plan->itineraries; itin < plan->itineraries + plan->n_itineraries; ++itin) { - b = plan_render_itinerary (itin, tdata, b, b_end); +/* Returns whether given round n, the given target (i_target) is the best target, or that a different target has a + * better time for the same vehicle journey. Optionally (optimizeOnLessStreet) it also checks whether is is the target + * with the least distance/duration on the street_network. + */ +static bool +best_target_for_jp_vj(router_t *router, router_request_t *req, uint64_t i_state, street_network_t *target, spidx_t i_target, bool optimizeOnLessStreet) { + spidx_t sp_index = target->stop_points[i_target]; + jpidx_t jp_index = router->states_back_journey_pattern[i_state + sp_index]; + jp_vjoffset_t vj_index = router->states_back_vehicle_journey[i_state + sp_index]; + + rtime_t best_sn_duration = target->durations[i_target]; + spidx_t target_least_sn_duration = i_target; + + rtime_t best_time = router->states_time[i_state + sp_index] + best_sn_duration; + spidx_t target_best = i_target; + int32_t i_otarget = target->n_points; + while (i_otarget) { + rtime_t duration; + --i_otarget; + sp_index = target->stop_points[i_otarget]; + duration = target->durations[i_otarget]; + if (i_target == i_otarget || router->states_time[i_state + sp_index] == UNREACHED || + router->states_back_journey_pattern[i_state + sp_index] != jp_index || + router->states_back_vehicle_journey[i_state + sp_index] != vj_index) { + continue; } - } else if (plan->n_itineraries > 0) { - if ((req->optimise & o_transfers) == o_transfers) { - /* only render the first itinerary, which has the least transfers */ - b = plan_render_itinerary (plan->itineraries, tdata, b, b_end); + if (optimizeOnLessStreet && target->durations[i_otarget] < best_sn_duration) { + best_sn_duration = target_least_sn_duration; + target_least_sn_duration = (spidx_t) i_otarget; } - if ((req->optimise & o_shortest) == o_shortest) { - /* only render the last itinerary, which has the most rides and is the shortest in time */ - b = plan_render_itinerary (&plan->itineraries[plan->n_itineraries - 1], tdata, b, b_end); + if (req->arrive_by ? router->states_time[i_state + sp_index] - duration > best_time : + router->states_time[i_state + sp_index] + duration < best_time) { + best_time = req->arrive_by ? router->states_time[i_state + sp_index] - duration : + router->states_time[i_state + sp_index] + duration; + target_best = (spidx_t) i_otarget; + } + } + return i_target == target_best || (optimizeOnLessStreet && i_target == target_least_sn_duration); +} + +/* Get the best end-time given street_netwerk and i_state */ +static rtime_t +best_time_in_round(router_t *router, router_request_t *req, uint64_t i_state, street_network_t *sn) { + int32_t i_target; + rtime_t best_time = (rtime_t) (req->arrive_by ? 0 : UNREACHED); + for (i_target = 0; i_target < sn->n_points; ++i_target) { + spidx_t sp_index = sn->stop_points[i_target]; + rtime_t duration = sn->durations[i_target]; + if (req->arrive_by ? (router->states_time[i_state + sp_index] != UNREACHED && + router->states_time[i_state + sp_index] - duration > best_time) : + router->states_time[i_state + sp_index] + duration < best_time) { + best_time = req->arrive_by ? router->states_time[i_state + sp_index] - duration : + router->states_time[i_state + sp_index] + duration; + } + } + return (rtime_t) (best_time == 0 ? UNREACHED : best_time); +} + +bool router_result_to_plan(plan_t *plan, router_t *router, router_request_t *req) { + itinerary_t *itin; + uint8_t i_transfer; + /* copy the request into the plan for use in rendering */ + plan->req = *req; + itin = &plan->itineraries[plan->n_itineraries]; + + /* Loop over the rounds to get ending states of itineraries + * using different numbers of vehicles + */ + for (i_transfer = 0; i_transfer < req->max_transfers+1; ++i_transfer) { + /* Work backward from the target to the origin */ + uint64_t i_state; + spidx_t i_target; + rtime_t best_time_round; + street_network_t *target = req->arrive_by ? &req->entry : &req->exit; + if (target->n_points == 0) { + printf("No targets\n"); + continue; } + i_state = (((uint64_t) i_transfer) * router->tdata->n_stop_points); + best_time_round = best_time_in_round(router, req, i_state, target); + if (best_time_round == UNREACHED) + continue; /* No targets reached with this number of transfers */ + + /* Scan targets for optimal itinaries with i_transfer transfers */ + for (i_target = 0; i_target < target->n_points; ++i_target) { + rtime_t duration = target->durations[i_target]; + spidx_t sp_index = target->stop_points[i_target]; + + /* Skip the targets which were not reached by a vhicle in the round or have worse times than the best_time */ + if (router->states_time[i_state + sp_index] == UNREACHED || + (req->arrive_by ? router->states_time[i_state + sp_index] - duration < best_time_round : + router->states_time[i_state + sp_index] + duration > best_time_round) || + !best_target_for_jp_vj(router, req, i_state, target, i_target, false)) { + continue; + } + + /* Work backward from the targets to the origin */ + if (render_itinerary(router, req, itin, i_transfer, target, i_target)) { + /* Move to the next itinerary in the plan. */ + plan->n_itineraries += 1; + itin += 1; + }else{ +#ifdef RRRR_INTERLINE_DEBUG + /*printf("Itin render fault\n");*/ +#endif + } + }; } - *b = '\0'; - return b - buf; + return check_plan_invariants(plan); } -/* - After routing, call to convert the router state into a readable list of itinerary legs. - Returns the number of bytes written to the buffer. -*/ -uint32_t router_result_dump(router_t *router, router_request_t *req, char *buf, uint32_t buflen) { +/* After routing, call to convert the router state into a readable list of + * itinerary legs. Returns the number of bytes written to the buffer. + */ +uint32_t +router_result_dump(router_t *router, router_request_t *req, + uint32_t(*render)(plan_t *plan, tdata_t *tdata, char *buf, uint32_t buflen), + char *buf, uint32_t buflen) { plan_t plan; - if (!router_result_to_plan (&plan, router, req)) { + plan.n_itineraries = 0; + + if (!router_result_to_plan(&plan, router, req)) { return 0; } - /* plan_render_json (&plan, router->tdata, req); */ - return plan_render (&plan, router->tdata, req, buf, buflen); + return render(&plan, router->tdata, buf, buflen); } - diff --git a/router_result.h b/router_result.h index e31aacb..80437cc 100644 --- a/router_result.h +++ b/router_result.h @@ -1,3 +1,8 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and at + * https://github.com/bliksemlabs/rrrr/ + */ + #ifndef _ROUTER_RESULT_H #define _ROUTER_RESULT_H @@ -5,21 +10,17 @@ #include "util.h" #include "rrrr_types.h" #include "router.h" +#include "plan.h" -/* A leg represents one ride or walking transfer. */ -typedef struct leg leg_t; -struct leg { - /* journey_pattern index */ - uint32_t journey_pattern; - - /* vj index */ - uint32_t vj; - - /* from stop index */ - spidx_t s0; +#if 0 +/* Structure to temporary store abstracted plans */ +typedef struct result result_t; +struct result { + /* from stop_point index */ + spidx_t sp_from; - /* to stop index */ - spidx_t s1; + /* to stop_point index */ + spidx_t sp_to; /* start time */ rtime_t t0; @@ -27,36 +28,19 @@ struct leg { /* end time */ rtime_t t1; - #ifdef RRRR_FEATURE_REALTIME - /* start delay */ - int16_t d0; - - /* end delay */ - int16_t d1; - #endif -}; - -/* An itinerary is a chain of legs leading from one place to another. */ -typedef struct itinerary itinerary_t; -struct itinerary { - uint32_t n_rides; - uint32_t n_legs; - leg_t legs[RRRR_DEFAULT_MAX_ROUNDS * 2 + 1]; -}; - + /* modes in trip */ + uint8_t mode; -/* A plan is several pareto-optimal itineraries connecting the same two stops. */ -typedef struct plan plan_t; -struct plan { - uint32_t n_itineraries; - itinerary_t itineraries[RRRR_DEFAULT_MAX_ROUNDS]; - router_request_t req; + /* transfers in trip */ + uint8_t n_transfers; }; +#endif - -bool router_result_to_plan (struct plan *plan, router_t *router, router_request_t *req); +bool router_result_to_plan (plan_t *plan, router_t *router, router_request_t *req); /* return num of chars written */ -uint32_t router_result_dump(router_t*, router_request_t*, char *buf, uint32_t buflen); +uint32_t router_result_dump(router_t *router, router_request_t *req, + uint32_t(*render)(plan_t *plan, tdata_t *tdata, char *buf, uint32_t buflen), + char *buf, uint32_t buflen); #endif diff --git a/rrrr_types.h b/rrrr_types.h index e23055b..fc87234 100644 --- a/rrrr_types.h +++ b/rrrr_types.h @@ -1,9 +1,13 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and at + * https://github.com/bliksemlabs/rrrr/ + */ + #ifndef _RRRR_TYPES_H #define _RRRR_TYPES_H #include "config.h" #include "hashgrid.h" - #include #include #include @@ -23,6 +27,20 @@ typedef uint16_t rtime_t; typedef uint16_t spidx_t; +typedef uint32_t vjidx_t; + +typedef uint16_t jpidx_t; + +typedef uint16_t jp_vjoffset_t; + +typedef uint16_t jppidx_t; + +typedef uint8_t opidx_t; + +typedef uint16_t lineidx_t; + +typedef uint16_t routeidx_t; + typedef uint32_t calendar_t; typedef struct service_day { @@ -38,13 +56,119 @@ struct list { uint32_t len; }; +typedef struct stop_point stop_point_t; +struct stop_point { + uint32_t journey_patterns_at_stop_point_offset; + uint32_t transfers_offset; + /* Transfer offset calculation by next item + * won't allow to extend the list nor to append + * to the end and fragment, adding a new stop + * once the transfers have to be extended is + * an option, but will not ensure the transfers + * are transitive. Low hanging fruit would be + * to store the number of transfers. + * + * By splitting up the struct a dynamic list of + * n_transfers can be computed. And in case of + * extending an object truly be updated. + * + * Alternatively references to the old object + * Should be updated. + */ +}; + +/* An individual JourneyPattern in the RAPTOR sense: + * A group of VehicleJourneys all share the same JourneyPattern. + */ +typedef struct journey_pattern journey_pattern_t; +struct journey_pattern { + uint32_t journey_pattern_point_offset; + vjidx_t vj_index; + jppidx_t n_stops; + jp_vjoffset_t n_vjs; + uint16_t attributes; + routeidx_t route_index; + rtime_t min_time; + rtime_t max_time; +}; + +typedef uint16_t vj_attribute_mask_t; typedef enum vehicle_journey_attributes { - vja_none = 0, - vja_accessible = 1, - vja_toilet = 2, - vja_wifi = 4 + vja_none = 0, + vja_wheelchair_accessible = 1, + vja_bike_accepted = 2, + vja_visual_announcement = 4, + vja_audible_announcement = 8, + vja_appropriate_escort = 16, + vja_appropriate_signage = 32, + vja_school_vehicle = 64, + vja_wifi = 128, + vja_toilet = 256, + vja_ondemand = 512 + /* 5 more attributes allowed */ } vehicle_journey_attributes_t; +/* An individual VehicleJourney, + * a materialized instance of a time demand type. */ +typedef struct vehicle_journey vehicle_journey_t; +struct vehicle_journey { + /* The offset of the first stoptime of the + * time demand type used by this vehicle_journey. + */ + uint32_t stop_times_offset; + + /* The absolute start time since at the + * departure of the first stop + */ + rtime_t begin_time; + + /* The vj_attributes + */ + vj_attribute_mask_t vj_attributes; +}; + +typedef struct vehicle_journey_ref vehicle_journey_ref_t; +struct vehicle_journey_ref { + jppidx_t jp_index; + jp_vjoffset_t vj_offset; +}; + +typedef struct stoptime stoptime_t; +struct stoptime { + rtime_t arrival; + rtime_t departure; +}; + +typedef enum stop_attribute { + /* the stop is accessible for a wheelchair */ + sa_wheelchair_boarding = 1, + + /* the stop is accessible for the visible impaired */ + sa_visual_accessible = 2, + + /* a shelter is available against rain */ + sa_shelter = 4, + + /* a bicycle can be parked */ + sa_bikeshed = 8, + + /* a bicycle may be rented */ + sa_bicyclerent = 16, + + /* a car can be parked */ + sa_parking = 32 +} stop_attribute_t; + +typedef enum journey_pattern_point_attribute { + /* the vehicle waits if it arrives early */ + rsa_waitingpoint = 1, + + /* a passenger can enter the vehicle at this stop */ + rsa_boarding = 2, + + /* a passenger can leave the vehicle at this stop */ + rsa_alighting = 4 +} journey_pattern_point_attribute_t; typedef enum optimise { /* output only shortest time */ @@ -59,6 +183,7 @@ typedef enum optimise { typedef enum tmode { + m_none = 0, m_tram = 1, m_subway = 2, m_rail = 4, @@ -70,49 +195,69 @@ typedef enum tmode { m_all = 255 } tmode_t; +typedef struct street_network street_network_t; +struct street_network { + spidx_t n_points; + /* Used to mark stop_point as po for the itinerary */ + spidx_t stop_points[RRRR_MAX_ENTRY_EXIT_POINTS]; + /* Used to mark duration from origin to stop_point */ + rtime_t durations[RRRR_MAX_ENTRY_EXIT_POINTS]; +}; + typedef struct router_request router_request_t; struct router_request { -#ifdef RRRR_FEATURE_LATLON + /* Requested stop_point as start-location */ + spidx_t from_stop_point; + + /* Requested stop_area as start-location */ + spidx_t from_stop_area; + /* actual origin in wgs84 presented to the planner */ latlon_t from_latlon; - hashgrid_result_t from_hg_result; + + /* Requested stop_area as end-location */ + spidx_t to_stop_area; + + /* (nearest) destination stop_point index from the users perspective */ + spidx_t to_stop_point; /* actual destination in wgs84 presented to the planner */ latlon_t to_latlon; - hashgrid_result_t to_hg_result; + + /* preferred transfer stop_point index from the users perspective */ + spidx_t via_stop_point; /* actual intermediate in wgs84 presented to the planner */ latlon_t via_latlon; - hashgrid_result_t via_hg_result; -#endif - /* (nearest) start stop index from the users perspective */ - spidx_t from; - - /* (nearest) destination stop index from the users perspective */ - spidx_t to; - - /* preferred transfer stop index from the users perspective */ - spidx_t via; /* onboard departure, journey_pattern index from the users perspective */ - uint32_t onboard_vj_journey_pattern; + jpidx_t onboard_journey_pattern; /* onboard departure, vehicle_journey offset within the journey_pattern */ - uint32_t onboard_journey_pattern_offset; + jp_vjoffset_t onboard_journey_pattern_vjoffset; /* TODO comment on banning */ #if RRRR_MAX_BANNED_JOURNEY_PATTERNS > 0 - uint32_t banned_journey_patterns[RRRR_MAX_BANNED_JOURNEY_PATTERNS]; + jpidx_t banned_journey_patterns[RRRR_MAX_BANNED_JOURNEY_PATTERNS]; #endif - #if RRRR_MAX_BANNED_STOPS > 0 - spidx_t banned_stops[RRRR_MAX_BANNED_STOPS]; + #if RRRR_MAX_BANNED_OPERATORS > 0 + opidx_t banned_operators[RRRR_MAX_BANNED_OPERATORS]; #endif - #if RRRR_MAX_BANNED_STOPS_HARD > 0 - spidx_t banned_stops_hard[RRRR_MAX_BANNED_STOPS_HARD]; + #if RRRR_MAX_BANNED_STOP_POINTS > 0 + spidx_t banned_stops[RRRR_MAX_BANNED_STOP_POINTS]; + #endif + #if RRRR_MAX_BANNED_STOP_POINTS_HARD > 0 + spidx_t banned_stop_points_hard[RRRR_MAX_BANNED_STOP_POINTS_HARD]; #endif #if RRRR_MAX_BANNED_VEHICLE_JOURNEYS > 0 - uint32_t banned_vjs_journey_pattern[RRRR_MAX_BANNED_VEHICLE_JOURNEYS]; - uint16_t banned_vjs_offset[RRRR_MAX_BANNED_VEHICLE_JOURNEYS]; + jpidx_t banned_vjs_journey_pattern[RRRR_MAX_BANNED_VEHICLE_JOURNEYS]; + jp_vjoffset_t banned_vjs_offset[RRRR_MAX_BANNED_VEHICLE_JOURNEYS]; + #endif + + /* select journey patterns from these operators only */ + #if RRRR_MAX_FILTERED_OPERATORS > 0 + opidx_t n_operators; + opidx_t operators[RRRR_MAX_FILTERED_OPERATORS]; #endif /* bit for the day on which we are searching, relative to the timetable calendar */ @@ -130,11 +275,6 @@ struct router_request { /* the maximum distance the hashgrid will search through for alternative stops */ uint16_t walk_max_distance; -#ifdef FEATURE_AGENCY_FILTER - /* Filter the journey_patterns by the operating agency */ - uint16_t agency; -#endif - /* the largest number of transfers to allow in the result */ uint8_t max_transfers; @@ -145,17 +285,20 @@ struct router_request { uint8_t walk_slack; /* select the required vehicle_journey attributes by a bitfield */ - uint8_t vj_attributes; + vj_attribute_mask_t vj_attributes; /* TODO comment on banning */ #if RRRR_MAX_BANNED_JOURNEY_PATTERNS > 0 uint8_t n_banned_journey_patterns; #endif - #if RRRR_MAX_BANNED_STOPS > 0 + #if RRRR_MAX_BANNED_OPERATORS > 0 + uint8_t n_banned_operators; + #endif + #if RRRR_MAX_BANNED_STOP_POINTS > 0 uint8_t n_banned_stops; #endif - #if RRRR_MAX_BANNED_STOPS_HARD > 0 - uint8_t n_banned_stops_hard; + #if RRRR_MAX_BANNED_STOP_POINTS_HARD > 0 + uint8_t n_banned_stop_points_hard; #endif #if RRRR_MAX_BANNED_VEHICLE_JOURNEYS > 0 uint8_t n_banned_vjs; @@ -175,8 +318,16 @@ struct router_request { /* whether to show the intermediate stops in the output */ bool intermediatestops; + + /* Mark durations to various stop-points from the request start-position of the itinerary */ + street_network_t entry; + /* Mark durations from various stop-points to the request end-position of the itinerary */ + street_network_t exit; }; +#ifndef restrict +#define restrict __restrict +#endif #ifndef _LP64 #define ZU "%u" @@ -185,6 +336,7 @@ struct router_request { #endif #define SEC_TO_RTIME(x) (rtime_t) ((x) >> 2) +#define SIGNED_SEC_TO_RTIME(x) ((x) >> 2) #define RTIME_TO_SEC(x) (((uint32_t)x) << 2) #define RTIME_TO_SEC_SIGNED(x) ((x) << 2) @@ -198,9 +350,14 @@ struct router_request { #define RTIME_THREE_DAYS (SEC_TO_RTIME(SEC_IN_THREE_DAYS)) #define UNREACHED UINT16_MAX -#define NONE (UINT16_MAX) -#define WALK (UINT16_MAX - 1) - +#define STREET (UINT16_MAX - 1) +#define STAY_ON (UINT16_MAX - 2) +#define WALK (UINT16_MAX - 3) + +#define OP_NONE ((opidx_t) -1) +#define JP_NONE ((jpidx_t) -1) +#define JPP_NONE ((jppidx_t) -1) +#define VJ_NONE ((jp_vjoffset_t) -1) #define STOP_NONE ((spidx_t) -1) #define ONBOARD ((spidx_t) -2) diff --git a/rrtimetable/rrtimetable/exporter/helper.py b/rrtimetable/rrtimetable/exporter/helper.py deleted file mode 100644 index 5259297..0000000 --- a/rrtimetable/rrtimetable/exporter/helper.py +++ /dev/null @@ -1,3 +0,0 @@ -import os -parentdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -os.sys.path.insert(0,parentdir) diff --git a/rrtimetable/rrtimetable/exporter/timetable3.py b/rrtimetable/rrtimetable/exporter/timetable3.py deleted file mode 100644 index c524fe9..0000000 --- a/rrtimetable/rrtimetable/exporter/timetable3.py +++ /dev/null @@ -1,520 +0,0 @@ -import helper -from utils import * -import operator -import sys - -NUMBER_OF_DAYS = 32 - -class Index(): - def __init__(self): - self.operators = [] - self.idx_for_operator_uri = {} - self.lines = [] - self.idx_for_line_uri = {} - self.routes = [] - self.idx_for_route_uri = {} - self.journey_patterns = [] - self.idx_for_journey_pattern_uri = {} - self.stop_points = [] - self.idx_for_stop_point_uri = {} - self.stop_areas = [] - self.idx_for_stop_area_uri = {} - self.validity_pattern_for_journey_pattern_uri = {} - self.timedemandgroups = [] - self.idx_for_timedemandgroup_uri = {} - self.journey_patterns_at_stop_point = {} - self.vehicle_journeys_in_journey_pattern = {} - self.connections_from_stop_point = {} - self.connections_point_to_point = {} - - self.loc_for_headsign = {} - self.headsigns = [] - self.headsign_length = 0 - - self.idx_for_productcategory = {} - self.productcategories = [] - - self.idx_for_linecode = {} - self.linecodes = [] - - def put_headsign(self,headsign): - if headsign in self.loc_for_headsign: - return self.loc_for_headsign[headsign] - self.loc_for_headsign[headsign] = self.headsign_length - self.headsign_length += (len(headsign) + 1) - self.headsigns.append(headsign) - return self.loc_for_headsign[headsign] - - def put_productcategory(self,productcategory): - if productcategory in self.idx_for_productcategory: - return self.idx_for_productcategory[productcategory] - self.idx_for_productcategory[productcategory] = len(self.idx_for_productcategory) - self.productcategories.append(productcategory) - return self.idx_for_productcategory[productcategory] - - def put_linecode(self,linecode): - if linecode in self.idx_for_linecode: - return self.idx_for_linecode[linecode] - self.idx_for_linecode[linecode] = len(self.idx_for_linecode) - self.linecodes.append(linecode) - return self.idx_for_linecode[linecode] - -def make_idx(tdata): - index = Index() - for vj in sorted(tdata.vehicle_journeys.values(), key= lambda vj: (vj.route.line.operator.uri,vj.route.line.uri,vj.route.uri,vj.departure_time)): - if len(vj.validity_pattern) == 0 or min(vj.validity_pattern) >= NUMBER_OF_DAYS: - continue - if vj.journey_pattern.uri not in index.validity_pattern_for_journey_pattern_uri: - index.validity_pattern_for_journey_pattern_uri[vj.journey_pattern.uri] = set([]) - index.validity_pattern_for_journey_pattern_uri[vj.journey_pattern.uri].update(vj.validity_pattern) - - if vj.journey_pattern.route.line.operator.uri not in index.idx_for_operator_uri: - index.idx_for_operator_uri[vj.journey_pattern.route.line.operator.uri] = len(index.idx_for_operator_uri) - index.operators.append(vj.journey_pattern.route.line.operator) - - if vj.journey_pattern.route.line.uri not in index.idx_for_line_uri: - index.idx_for_line_uri[vj.journey_pattern.route.line.uri] = len(index.idx_for_line_uri) - index.lines.append(vj.journey_pattern.route.line) - - if vj.journey_pattern.route.uri not in index.idx_for_route_uri: - index.idx_for_route_uri[vj.journey_pattern.route.uri] = len(index.idx_for_route_uri) - index.routes.append(vj.journey_pattern.route) - - if vj.journey_pattern.uri not in index.idx_for_journey_pattern_uri: - index.idx_for_journey_pattern_uri[vj.journey_pattern.uri] = len(index.idx_for_journey_pattern_uri) - index.journey_patterns.append(vj.journey_pattern) - - if vj.journey_pattern.uri not in index.vehicle_journeys_in_journey_pattern: - index.vehicle_journeys_in_journey_pattern[vj.journey_pattern.uri] = [] - index.vehicle_journeys_in_journey_pattern[vj.journey_pattern.uri].append(vj) - - if vj.timedemandgroup.uri not in index.idx_for_timedemandgroup_uri: - index.idx_for_timedemandgroup_uri[vj.timedemandgroup.uri] = len(index.idx_for_timedemandgroup_uri) - index.timedemandgroups.append(vj.timedemandgroup) - for jpp in vj.journey_pattern.points: - if jpp.stop_point.uri not in index.idx_for_stop_point_uri: - index.idx_for_stop_point_uri[jpp.stop_point.uri] = len(index.stop_points) - index.stop_points.append(jpp.stop_point) - if jpp.stop_point.uri not in index.journey_patterns_at_stop_point: - index.journey_patterns_at_stop_point[jpp.stop_point.uri] = set([]) - index.journey_patterns_at_stop_point[jpp.stop_point.uri].add(vj.journey_pattern.uri) - if jpp.stop_point.stop_area.uri not in index.idx_for_stop_area_uri: - index.idx_for_stop_point_uri[jpp.stop_point.stop_area.uri] = len(index.stop_areas) - index.stop_areas.append(jpp.stop_point.stop_area) - - for conn in tdata.connections.values(): - if conn.from_stop_point.uri not in index.idx_for_stop_point_uri or conn.to_stop_point.uri not in index.idx_for_stop_point_uri: - continue #connection to or from unknown stop_point - if conn.from_stop_point.uri not in index.connections_from_stop_point: - index.connections_from_stop_point[conn.from_stop_point.uri] = [] - index.connections_from_stop_point[conn.from_stop_point.uri].append(conn) - if len(index.journey_patterns) == 0: - print "No valid journey_patterns found to export to this timetable. Exiting..." - sys.exit(1) - print '--------------------------' - return index - -def write_stop_point_idx(out,index,stop_uri): - if len(index.stop_points) <= 65535: - writeshort(out,index.idx_for_stop_point_uri[stop_uri]) - else: - writeint(out,index.idx_for_stop_point_uri[stop_uri]) - -def export_sp_coords(tdata,index,out): - index.loc_stop_coords = out.tell() - for sp in index.stop_points: - write2floats(out,sp.latitude or 0.0, sp.longitude or 0.0) - -def export_journey_pattern_point_stop(tdata,index,out): - write_text_comment(out,"JOURNEY_PATTERN_POINT STOP") - index.loc_journey_pattern_points = tell(out) - index.offset_jpp = [] - offset = 0 - index.n_jpp = 0 - for jp in index.journey_patterns: - index.offset_jpp.append(offset) - for jpp in jp.points: - index.n_jpp += 1 - write_stop_point_idx(out,index,jpp.stop_point.uri) - offset += 1 - -def export_journey_pattern_point_attributes(tdata,index,out): - write_text_comment(out,"STOPS ATTRIBUTES BY JOURNEY_PATTERN") - index.loc_journey_pattern_point_attributes = tell(out) - index.offset_jpp_attributes = [] - offset = 0 - for jp in index.journey_patterns: - index.offset_jpp_attributes.append(offset) - for jpp in jp.points: - attr = 0 - if jpp.timingpoint: - attr |= 1 - if jpp.forboarding: - attr |= 2 - if jpp.foralighting: - attr |= 4 - writebyte(out,attr) - offset += 1 - -timedemandgroup_t = Struct('HH') -def export_timedemandgroups(tdata,index,out): - write_text_comment(out,"TIMEDEMANDGROUPS") - index.loc_timedemandgroups = tell(out) - index.offset_for_timedemandgroup_uri = {} - tp_offset = 0 - for tp in index.timedemandgroups: - index.offset_for_timedemandgroup_uri[tp.uri] = tp_offset - for tpp in tp.points: - out.write(timedemandgroup_t.pack(tpp.drivetime >> 2, tpp.totaldrivetime >> 2)) - tp_offset += 1 - index.n_tpp = tp_offset - -def export_vj_in_jp(tdata,index,out): - write_text_comment(out,"VEHICLE JOURNEYS IN JOURNEY_PATTERN") - index.loc_vehicle_journeys = tell(out) - tioffset = 0 - index.vj_ids_offsets = [] - vj_t = Struct('IHH') - for jp in index.journey_patterns: - index.vj_ids_offsets.append(tioffset) - for vj in index.vehicle_journeys_in_journey_pattern[jp.uri]: - vj_attr = 0 - out.write(vj_t.pack(index.offset_for_timedemandgroup_uri[vj.timedemandgroup.uri], vj.departure_time >> 2, vj_attr)) - tioffset += 1 - -def export_jpp_at_sp(tdata,index,out): - write_text_comment(out,"JOURNEY_PATTERNS AT STOP") - index.loc_jp_at_sp = tell(out) - index.jpp_at_sp_offsets = [] - n_offset = 0 - for sp in index.stop_points: - jp_uris = index.journey_patterns_at_stop_point[sp.uri] - index.jpp_at_sp_offsets.append(n_offset) - for jp_uri in jp_uris: - writeint(out,index.idx_for_journey_pattern_uri[jp_uri]) - n_offset += 1 - index.jpp_at_sp_offsets.append(n_offset) #sentinel - index.n_jpp_at_sp = n_offset - -def export_transfers(tdata,index,out): - print "saving transfer stops (footpaths)" - write_text_comment(out,"TRANSFER TARGET STOPS") - index.loc_transfer_target_stop_points = tell(out) - - index.transfers_offsets = [] - offset = 0 - transfertimes = [] - for sp in index.stop_points: - index.transfers_offsets.append(offset) - if sp.uri not in index.connections_from_stop_point: - continue - for conn in index.connections_from_stop_point[sp.uri]: - if (int(conn.min_transfer_time) >> 2) > 255: - continue - write_stop_point_idx(out,index,conn.to_stop_point.uri) - transfertimes.append(conn.min_transfer_time) - offset += 1 - assert len(transfertimes) == offset - index.transfers_offsets.append(offset) #sentinel - index.n_connections = offset - - print "saving transfer times (footpaths)" - write_text_comment(out,"TRANSFER TIMES") - index.loc_transfer_dist_meters = tell(out) - - for transfer_time in transfertimes: - writebyte(out,(int(transfer_time) >> 2)) - -def export_stop_indices(tdata,index,out): - print "saving stop indexes" - write_text_comment(out,"STOP STRUCTS") - index.loc_stops = tell(out) - struct_2i = Struct('II') - print len(index.jpp_at_sp_offsets),len(index.transfers_offsets) - assert len(index.jpp_at_sp_offsets) == len(index.transfers_offsets) - for stop in zip (index.jpp_at_sp_offsets, index.transfers_offsets) : - out.write(struct_2i.pack(*stop)); - -def export_stop_point_attributes(tdata,index,out): - print "saving stop attributes" - write_text_comment(out,"STOP Attributes") - index.loc_stop_point_attributes = tell(out) - for sp in index.stop_points: - attr = 0 - writebyte(out,attr) - -def export_jp_structs(tdata,index,out): - print "saving route indexes" - write_text_comment(out,"ROUTE STRUCTS") - index.loc_journey_patterns = tell(out) - route_t = Struct('3I8H') - jpp_offsets = index.offset_jpp - trip_ids_offsets = index.vj_ids_offsets - jp_attributes = [] - - nroutes = len(index.journey_patterns) - - jp_n_jpp = [] - jp_n_vj = [] - - index.idx_for_operator = {} - index.jp_operators = [] - operator_offsets = [] - - linecode_offsets = [] - productcategory_offsets = [] - headsign_offsets=[] - jp_min_time = [] - jp_max_time = [] - for jp in index.journey_patterns: - jp_n_jpp.append(len(jp.points)) - jp_n_vj.append(len(index.vehicle_journeys_in_journey_pattern[jp.uri])) - jp_min_time.append(min([vj.departure_time for vj in index.vehicle_journeys_in_journey_pattern[jp.uri]]) >> 2) - jp_max_time.append(max([vj.departure_time+vj.timedemandgroup.points[-1].totaldrivetime for vj in index.vehicle_journeys_in_journey_pattern[jp.uri]]) >> 2) - - productcategory_offsets.append(index.put_productcategory(jp.productcategory or '')) - headsign_offsets.append(index.put_headsign(jp.headsign or '')) - linecode_offsets.append(index.put_linecode(jp.route.line.code or '')) - - operator = jp.route.line.operator.uri - if operator not in index.idx_for_operator: - index.idx_for_operator[operator] = len(index.idx_for_operator) - index.jp_operators.append(operator) - operator_offsets.append(index.idx_for_operator[operator]) - jp_attributes.append(1 << jp.route.route_type) - jp_t_fields = [jpp_offsets, trip_ids_offsets,headsign_offsets, jp_n_jpp, jp_n_vj,jp_attributes,operator_offsets,linecode_offsets,productcategory_offsets,jp_min_time, jp_max_time] - for l in jp_t_fields : - # the extra last route is a sentinel so we can derive list lengths for the last true route. - assert len(l) == nroutes - for route in zip (*jp_t_fields) : - # print route - out.write(route_t.pack(*route)); - out.write(route_t.pack(jpp_offsets[-1]+1,0,0,0,0,0,0,0,0,0, 0)) #Sentinel - -def validity_mask(days): - mask = 0 - for day in days: - if day < NUMBER_OF_DAYS: - mask |= 1 << day - return mask - -def export_vj_validities(tdata,index,out): - print "writing bitfields indicating which days each trip is active" - # note that bitfields are ordered identically to the trip_ids table, and offsets into that table can be reused - write_text_comment(out,"VJ ACTIVE BITFIELDS") - index.loc_vj_active = tell(out) - - for jp in index.journey_patterns: - for vj in index.vehicle_journeys_in_journey_pattern[jp.uri]: - writeint(out,validity_mask(vj.validity_pattern)) - -def export_jp_validities(tdata,index,out): - print "writing bitfields indicating which days each trip is active" - # note that bitfields are ordered identically to the trip_ids table, and offsets into that table can be reused - write_text_comment(out,"JP ACTIVE BITFIELDS") - index.loc_jp_active = tell(out) - n_zeros = 0 - for jp in index.journey_patterns: - writeint(out,validity_mask(index.validity_pattern_for_journey_pattern_uri[jp.uri])) - -def export_platform_codes(tdata,index,out): - print "writing out platformcodes for stops" - write_text_comment(out,"PLATFORM CODES") - index.loc_platformcodes = write_string_table(out,[sp.platformcode or '' for sp in index.stop_points]) - -def export_stop_point_names(tdata,index,out): - - nameloc_for_name = {} - index.sp_nameloc_for_idx = [] - index.sp_namesize = 0 - idx = 0 - for sp in index.stop_points: - name = sp.name or '' - if name in nameloc_for_name: - nameloc = nameloc_for_name[name] - else: - nameloc = index.sp_namesize - nameloc_for_name[name] = nameloc - index.sp_namesize += 1 + len(name) - index.sp_nameloc_for_idx.append(nameloc) - idx += 1 - nstops = idx - assert len(index.sp_nameloc_for_idx) == idx - - print "writing out stop names to string table" - write_text_comment(out,"STOP NAME") - stop_names = sorted(nameloc_for_name.iteritems(), key=operator.itemgetter(1)) - index.loc_stop_names = tell(out) - for stop_name,nameloc in stop_names: - assert nameloc == out.tell() - index.loc_stop_names - out.write(stop_name+'\0') - - print "writing out locations for stopnames" - write_text_comment(out,"STOP NAME LOCATIONS") - index.loc_stop_nameidx = tell(out) - for nameloc in index.sp_nameloc_for_idx: - writeint(out,nameloc) - writeint(out,0) - -def export_operators(tdata,index,out): - print "writing out agencies to string table" - write_text_comment(out,"OPERATOR IDS") - uris = index.jp_operators - index.n_operators = len(uris) - index.loc_operator_ids = write_string_table(out,[index.operators[index.idx_for_operator_uri[uri]].uri for uri in uris]) - write_text_comment(out,"OPERATOR NAMES") - index.loc_operator_names = write_string_table(out,[index.operators[index.idx_for_operator_uri[uri]].name or '' for uri in uris]) - write_text_comment(out,"OPERATOR URLS") - index.loc_operator_urls = write_string_table(out,[index.operators[index.idx_for_operator_uri[uri]].url or '' for uri in uris]) - -def export_headsigns(tdata,index,out): - print "writing out headsigns to string table" - write_text_comment(out,"HEADSIGNS") - sorted_headsigns = sorted(index.loc_for_headsign.iteritems(), key=operator.itemgetter(1)) - index.loc_headsign = tell(out) - written_length = 0 - for headsign in index.headsigns: - out.write(headsign+'\0') - written_length += len(headsign) + 1 - assert written_length == index.headsign_length - - -def export_linecodes(tdata,index,out): - write_text_comment(out,"LINE CODES") - index.loc_line_codes = write_string_table(out,index.linecodes) - -def export_productcategories(tdata,index,out): - write_text_comment(out,"PRODUCT CATEGORIES") - index.loc_productcategories = write_string_table(out,index.productcategories) - -def export_line_uris(tdata,index,out): - # maybe no need to store route IDs: report trip ids and look them up when reconstructing the response - print "writing line ids to string table" - write_text_comment(out,"LINE IDS") - index.loc_line_uris = write_string_table(out,[jp.route.line.uri for jp in index.journey_patterns]) - -def export_sp_uris(tdata,index,out): - print "writing out sorted stop ids to string table" - # stopid index was several times bigger than the string table. it's probably better to just store fixed-width ids. - write_text_comment(out,"STOP IDS") - index.loc_stop_point_uris = write_string_table(out,[sp.uri for sp in index.stop_points]) - -def export_vj_uris(tdata,index,out): - all_vj_ids = [] - for jp in index.journey_patterns: - for vj in index.vehicle_journeys_in_journey_pattern[jp.uri]: - all_vj_ids.append(vj.uri) - index.n_vj = len(all_vj_ids) - print "writing trip ids to string table" - # note that trip_ids are ordered by departure time within trip bundles (routes), which are themselves in arbitrary order. - write_text_comment(out,"VJ IDS") - index.loc_vj_uris = write_string_table(out,all_vj_ids) - index.n_vj = len(all_vj_ids) - -def write_header (out,index) : - """ Write out a file header containing offsets to the beginning of each subsection. - Must match struct transit_data_header in transitdata.c """ - out.seek(0) - htext = "TTABLEV3" - - - packed = struct_header.pack(htext, - index.calendar_start_time, - index.dst_mask, - index.n_stops, # n_stops - index.n_stops, # n_stop_attributes - index.n_stops, # n_stop_coords - index.n_jp, # n_routes - index.n_jpp, # n_route_stops - index.n_jpp, # n_route_stop_attributes - index.n_tpp, # n_stop_times - index.n_vj, # n_vjs - index.n_jpp_at_sp, # n_stop_routes - index.n_connections, #n_transfer_target_stop - index.n_connections, #n_transfer_dist_meters - index.n_vj, #n_trip_active - index.n_jp, # n_route_active - index.n_stops, # n_platformcodes - index.sp_namesize, # n_stop_names (length of the object) - len(index.sp_nameloc_for_idx) + 1, # n_stop_nameidx - index.n_operators, # n_agency_ids - index.n_operators, # n_agency_names - index.n_operators, # n_agency_urls - index.headsign_length, # n_headsigns (length of the object) - len(index.idx_for_linecode), # n_route_shortnames - len(index.idx_for_productcategory), # n_productcategories - len(index.journey_patterns), # n_route_ids - index.n_stops, # n_stop_ids - index.n_vj, # n_trip_ids - - index.loc_stops, - index.loc_stop_point_attributes, - index.loc_stop_coords, - index.loc_journey_patterns, - index.loc_journey_pattern_points, - index.loc_journey_pattern_point_attributes, - index.loc_timedemandgroups, - index.loc_vehicle_journeys, - index.loc_jp_at_sp, - index.loc_transfer_target_stop_points, - index.loc_transfer_dist_meters, - index.loc_vj_active, - index.loc_jp_active, - index.loc_platformcodes, - index.loc_stop_names, - index.loc_stop_nameidx, - index.loc_operator_ids, - index.loc_operator_names, - index.loc_operator_urls, - index.loc_headsign, - index.loc_line_codes, - index.loc_productcategories, - index.loc_line_uris, - index.loc_stop_point_uris, - index.loc_vj_uris, - ) - print - out.write(packed) - - - -struct_header = Struct('8sQ51I') - -def export(tdata): - index = make_idx(tdata) - index.dst_mask = 0 - index.calendar_start_time = time.mktime((tdata.validfrom).timetuple()) - index.n_stops = len(index.stop_points) - index.n_jp = len(index.journey_patterns) - out = open('timetable.dat','wb') - out.seek(struct_header.size) - - export_sp_coords(tdata,index,out) - export_journey_pattern_point_stop(tdata,index,out) - export_journey_pattern_point_attributes(tdata,index,out) - export_timedemandgroups(tdata,index,out) - export_vj_in_jp(tdata,index,out) - export_jpp_at_sp(tdata,index,out) - export_transfers(tdata,index,out) - export_stop_indices(tdata,index,out) - export_stop_point_attributes(tdata,index,out) - export_jp_structs(tdata,index,out) - export_vj_validities(tdata,index,out) - export_jp_validities(tdata,index,out) - export_platform_codes(tdata,index,out) - export_stop_point_names(tdata,index,out) - export_operators(tdata,index,out) - export_headsigns(tdata,index,out) - export_linecodes(tdata,index,out) - export_productcategories(tdata,index,out) - export_line_uris(tdata,index,out) - export_sp_uris(tdata,index,out) - export_vj_uris(tdata,index,out) - print "reached end of timetable file" - write_text_comment(out,"END TTABLEV2") - index.loc_eof = tell(out) - print "rewinding and writing header... ", - write_header(out,index) - - out.flush() - out.close() diff --git a/rrtimetable/rrtimetable/exporter/timetable4.py b/rrtimetable/rrtimetable/exporter/timetable4.py new file mode 100644 index 0000000..caaefee --- /dev/null +++ b/rrtimetable/rrtimetable/exporter/timetable4.py @@ -0,0 +1,698 @@ +from utils import * +import sys +import datetime + +# Notes: + +# UTC and timezones +# +# We normalize all times to UTC, to properly route through DST-changes and multiple timezones. +# However since we are using unsigned integers, we cannot allow negative times. +# To avoid that we take the highest UTC offset of all vehicle_journey's in the timetable and use that as a global offset. +# All vehicle_journey's have the number of 15-minutes, offset from that global utc offset. +# We picked a 15 minute resolution for these offsets to allow them to store in int8_t + +NUMBER_OF_DAYS = 32 +MIN_WAITTIME = 2 * 60 #2 minutes ( in seconds) +MAX_INTERLINE_WAITTIME = 5 *60 #5 minutes ( in seconds) +JP_NONE = 65535 +VJ_NONE = 65535 + +class Index(): + def __init__(self): + self.operators = [] + self.idx_for_operator_uri = {} + self.lines = [] + self.idx_for_line_uri = {} + self.routes = [] + self.idx_for_route_uri = {} + self.journey_patterns = [] + self.idx_for_journey_pattern_uri = {} + self.stop_points = [] + self.idx_for_stop_point_uri = {} + self.stop_areas = [] + self.idx_for_stop_area_uri = {} + self.validity_pattern_for_journey_pattern_uri = {} + self.timedemandgroups = [] + self.idx_for_timedemandgroup_uri = {} + self.commercial_modes = [] + self.idx_for_commercial_mode_uri = {} + self.physical_modes = [] + self.idx_for_physical_mode_uri = {} + + self.journey_patterns_at_stop_point = {} + self.vehicle_journeys_in_journey_pattern = {} + self.connections_from_stop_point = {} + self.connections_point_to_point = {} + self.blocks = {} + self.vj_interline_clockwise = {} + self.vj_interline_counterclockwise = {} + + self.loc_for_string = {} + self.strings = [] + self.string_length = 0 + + def put_string(self,string): + if string in self.loc_for_string: + return self.loc_for_string[string] + self.loc_for_string[string] = self.string_length + self.string_length += (len(string) + 1) + self.strings.append(string) + return self.loc_for_string[string] + +def make_idx(tdata): + index = Index() + index.global_utc_offset = -999999 + for vj in sorted(tdata.vehicle_journeys_utc.values(), key= lambda vj: (vj.route.line.operator.uri,vj.route.line.uri,vj.route.uri,vj.departure_time)): + if len(vj.validity_pattern) == 0 or min(vj.validity_pattern) >= NUMBER_OF_DAYS: + continue + if vj.utc_offset > index.global_utc_offset: + index.global_utc_offset = vj.utc_offset + if vj.journey_pattern.uri not in index.validity_pattern_for_journey_pattern_uri: + index.validity_pattern_for_journey_pattern_uri[vj.journey_pattern.uri] = set([]) + index.validity_pattern_for_journey_pattern_uri[vj.journey_pattern.uri].update(vj.validity_pattern) + if vj.blockref is not None: + if (vj.utc_offset,vj.blockref) not in index.blocks: + index.blocks[(vj.utc_offset,vj.blockref)] = [] + index.blocks[(vj.utc_offset,vj.blockref)].append(vj) + + if vj.journey_pattern.route.line.operator.uri not in index.idx_for_operator_uri: + index.idx_for_operator_uri[vj.journey_pattern.route.line.operator.uri] = len(index.idx_for_operator_uri) + index.operators.append(vj.journey_pattern.route.line.operator) + + if vj.journey_pattern.route.line.uri not in index.idx_for_line_uri: + index.idx_for_line_uri[vj.journey_pattern.route.line.uri] = len(index.idx_for_line_uri) + index.lines.append(vj.journey_pattern.route.line) + line = vj.journey_pattern.route.line + if line.physical_mode.uri not in index.idx_for_physical_mode_uri: + index.idx_for_physical_mode_uri[line.physical_mode.uri] = len(index.idx_for_physical_mode_uri) + index.physical_modes.append(line.physical_mode) + + if vj.journey_pattern.route.uri not in index.idx_for_route_uri: + index.idx_for_route_uri[vj.journey_pattern.route.uri] = len(index.idx_for_route_uri) + index.routes.append(vj.journey_pattern.route) + + if vj.journey_pattern.uri not in index.idx_for_journey_pattern_uri: + index.idx_for_journey_pattern_uri[vj.journey_pattern.uri] = len(index.idx_for_journey_pattern_uri) + index.journey_patterns.append(vj.journey_pattern) + vj._jp_idx = index.idx_for_journey_pattern_uri[vj.journey_pattern.uri] + + if vj.journey_pattern.uri not in index.vehicle_journeys_in_journey_pattern: + index.vehicle_journeys_in_journey_pattern[vj.journey_pattern.uri] = [] + vj._jpvjoffset = len(index.vehicle_journeys_in_journey_pattern[vj.journey_pattern.uri]) + index.vehicle_journeys_in_journey_pattern[vj.journey_pattern.uri].append(vj) + + if vj.journey_pattern.commercial_mode.uri not in index.idx_for_commercial_mode_uri: + index.idx_for_commercial_mode_uri[vj.journey_pattern.commercial_mode.uri] = len(index.idx_for_commercial_mode_uri) + index.commercial_modes.append(vj.journey_pattern.commercial_mode) + + if vj.timedemandgroup.uri not in index.idx_for_timedemandgroup_uri: + index.idx_for_timedemandgroup_uri[vj.timedemandgroup.uri] = len(index.idx_for_timedemandgroup_uri) + index.timedemandgroups.append(vj.timedemandgroup) + for jpp in vj.journey_pattern.points: + if jpp.stop_point.uri not in index.idx_for_stop_point_uri: + index.idx_for_stop_point_uri[jpp.stop_point.uri] = len(index.stop_points) + index.stop_points.append(jpp.stop_point) + if jpp.stop_point.uri not in index.journey_patterns_at_stop_point: + index.journey_patterns_at_stop_point[jpp.stop_point.uri] = set([]) + index.journey_patterns_at_stop_point[jpp.stop_point.uri].add(vj.journey_pattern.uri) + if jpp.stop_point.stop_area.uri not in index.idx_for_stop_area_uri: + index.idx_for_stop_area_uri[jpp.stop_point.stop_area.uri] = len(index.stop_areas) + index.stop_areas.append(jpp.stop_point.stop_area) + + for conn in tdata.connections.values(): + if conn.from_stop_point.uri not in index.idx_for_stop_point_uri or conn.to_stop_point.uri not in index.idx_for_stop_point_uri: + continue #connection to or from unknown stop_point + if conn.from_stop_point.uri not in index.connections_from_stop_point: + index.connections_from_stop_point[conn.from_stop_point.uri] = [] + index.connections_from_stop_point[conn.from_stop_point.uri].append(conn) + + for key,vjs in index.blocks.items(): + if len(vjs) == 1: + continue + vjs = sorted(vjs, key= lambda vj: vj.departure_time) + for i in range(len(vjs)-1): + from_vj = vjs[i] + to_vj = vjs[i+1] + if (from_vj.departure_time+from_vj.timedemandgroup.points[-1].totaldrivetime) > to_vj.departure_time: + print 'Ignoring VJ interline from %s to %s, timetravel!' % (from_vj.uri,to_vj.uri) + continue + waittime = to_vj.departure_time - (from_vj.departure_time+from_vj.timedemandgroup.points[-1].totaldrivetime) + if waittime > MAX_INTERLINE_WAITTIME: + print 'Ignoring VJ interline from %s to %s, wait-time between VJs is %d seconds!' % (from_vj.uri,to_vj.uri,waittime) + continue + if (from_vj.utc_offset,from_vj.uri) in index.vj_interline_clockwise: + raise Exception("Vehicle_journey's can only point to ONE other VJ") + index.vj_interline_clockwise[(from_vj.utc_offset,from_vj.uri)] = (to_vj._jp_idx,to_vj._jpvjoffset) + if (to_vj.utc_offset,to_vj.uri) in index.vj_interline_counterclockwise: + raise Exception("Vehicle_journey's can only point to ONE other VJ counterclockwise") + index.vj_interline_counterclockwise[(to_vj.utc_offset,to_vj.uri)] = (from_vj._jp_idx,from_vj._jpvjoffset) + + if len(index.journey_patterns) == 0: + print "No valid journey_patterns found to export to this timetable. Exiting..." + sys.exit(1) + print '--------------------------' + return index + +def write_stop_point_idx(out,index,stop_point_uri): + if len(index.stop_points) <= 65535: + writeshort(out,index.idx_for_stop_point_uri[stop_point_uri]) + else: + writeint(out,index.idx_for_stop_point_uri[stop_point_uri]) + +def write_stop_area_idx(out,index,stop_area_uri): + if len(index.stop_points) <= 65535: + writeshort(out,index.idx_for_stop_area_uri[stop_area_uri]) + else: + writeint(out,index.idx_for_stop_area_uri[stop_area_uri]) + +def write_list_of_strings(out, index, list_of_strings): + loc = tell(out) + for x in list_of_strings: + writeint(out,index.put_string(x or '')) + return loc + +def export_sp_coords(index, out): + write_text_comment(out,"STOP POINT COORDS") + index.loc_stop_point_coords = out.tell() + for sp in index.stop_points: + write2floats(out,sp.latitude or 0.0, sp.longitude or 0.0) + +def export_sa_coords(index, out): + write_text_comment(out,"STOP AREA COORDS") + index.loc_stop_area_coords = out.tell() + for sa in index.stop_areas: + write2floats(out,sa.latitude or 0.0, sa.longitude or 0.0) + +def export_journey_pattern_point_stop(index, out): + write_text_comment(out,"JOURNEY_PATTERN_POINT STOP") + index.loc_journey_pattern_points = tell(out) + index.offset_jpp = [] + offset = 0 + index.n_jpp = 0 + for jp in index.journey_patterns: + index.offset_jpp.append(offset) + for jpp in jp.points: + index.n_jpp += 1 + write_stop_point_idx(out,index,jpp.stop_point.uri) + offset += 1 + +def export_journey_pattern_point_headsigns(index, out): + write_text_comment(out,"JOURNEY_PATTERN_POINT HEADSIGN") + index.loc_journey_pattern_point_headsigns = tell(out) + index.offset_jpp = [] + offset = 0 + for jp in index.journey_patterns: + index.offset_jpp.append(offset) + for jpp in jp.points: + writeint(out,index.put_string(jpp.headsign or jp.headsign or '')) + offset += 1 + +def export_journey_pattern_point_attributes(index, out): + write_text_comment(out,"STOPS ATTRIBUTES BY JOURNEY_PATTERN") + index.loc_journey_pattern_point_attributes = tell(out) + index.offset_jpp_attributes = [] + offset = 0 + for jp in index.journey_patterns: + index.offset_jpp_attributes.append(offset) + for jpp in jp.points: + attr = 0 + if jpp.timingpoint: + attr |= 1 + if jpp.forboarding: + attr |= 2 + if jpp.foralighting: + attr |= 4 + writebyte(out,attr) + offset += 1 + +timedemandgroup_t = Struct('HH') +def export_timedemandgroups(index, out): + write_text_comment(out,"TIMEDEMANDGROUPS") + index.loc_timedemandgroups = tell(out) + index.offset_for_timedemandgroup_uri = {} + tp_offset = 0 + for tp in index.timedemandgroups: + index.offset_for_timedemandgroup_uri[tp.uri] = tp_offset + for tpp in tp.points: + out.write(timedemandgroup_t.pack(tpp.drivetime >> 2, tpp.totaldrivetime >> 2)) + tp_offset += 1 + index.n_tpp = tp_offset + +def export_vj_in_jp(index, out): + write_text_comment(out,"VEHICLE JOURNEYS IN JOURNEY_PATTERN") + index.loc_vehicle_journeys = tell(out) + tioffset = 0 + index.vj_ids_offsets = [] + vj_t = Struct('IHH') + for jp in index.journey_patterns: + index.vj_ids_offsets.append(tioffset) + for vj in index.vehicle_journeys_in_journey_pattern[jp.uri]: + vj_attr = 0 + assert (tioffset - index.vj_ids_offsets[vj._jp_idx]) == vj._jpvjoffset + out.write(vj_t.pack(index.offset_for_timedemandgroup_uri[vj.timedemandgroup.uri], (vj.departure_time+index.global_utc_offset) >> 2, vj_attr)) + tioffset += 1 + +def export_jpp_at_sp(index, out): + write_text_comment(out,"JOURNEY_PATTERNS AT STOP") + index.loc_jp_at_sp = tell(out) + index.jpp_at_sp_offsets = [] + n_offset = 0 + for sp in index.stop_points: + jp_uris = index.journey_patterns_at_stop_point[sp.uri] + index.jpp_at_sp_offsets.append(n_offset) + for jp_uri in set(jp_uris): + writeshort(out,index.idx_for_journey_pattern_uri[jp_uri]) + n_offset += 1 + index.jpp_at_sp_offsets.append(n_offset) #sentinel + index.n_jpp_at_sp = n_offset + +def export_sa_for_sp(index, out): + write_text_comment(out,"STOP_POINT -> STOP_AREA") + index.loc_sa_for_sp = tell(out) + for sp in index.stop_points: + write_stop_area_idx(out,index,sp.stop_area.uri) + + +vjref_t = Struct('HH') +def export_vj_interlines(tdata,index,out): + index.loc_vj_interline_backward = tell(out) + n_vjtransfers = 0 + for jp in index.journey_patterns: + for vj in index.vehicle_journeys_in_journey_pattern[jp.uri]: + if (vj.utc_offset,vj.uri) in index.vj_interline_counterclockwise: + out.write(vjref_t.pack(*index.vj_interline_counterclockwise[(vj.utc_offset,vj.uri)])) + else: + out.write(vjref_t.pack(JP_NONE,VJ_NONE)) + n_vjtransfers += 1 + index.loc_vj_interline_forward = tell(out) + n_vjtransfers_forward = 0 + for jp in index.journey_patterns: + for vj in index.vehicle_journeys_in_journey_pattern[jp.uri]: + if (vj.utc_offset,vj.uri) in index.vj_interline_clockwise: + out.write(vjref_t.pack(*index.vj_interline_clockwise[(vj.utc_offset,vj.uri)])) + else: + out.write(vjref_t.pack(JP_NONE,VJ_NONE)) + n_vjtransfers_forward += 1 + assert n_vjtransfers == n_vjtransfers_forward + +def export_transfers(tdata,index,out): + print "saving transfer stops (footpaths)" + write_text_comment(out,"TRANSFER TARGET STOPS") + index.loc_transfer_target_stop_points = tell(out) + + index.transfers_offsets = [] + offset = 0 + transfertimes = [] + stop_point_waittimes = {} + for sp in index.stop_points: + index.transfers_offsets.append(offset) + if sp.uri not in index.connections_from_stop_point: + continue + for conn in index.connections_from_stop_point[sp.uri]: + if conn.from_stop_point.uri == conn.to_stop_point.uri: + stop_point_waittimes[conn.from_stop_point.uri] = conn.min_transfer_time + continue + write_stop_point_idx(out,index,conn.to_stop_point.uri) + transfertimes.append(conn.min_transfer_time) + offset += 1 + assert len(transfertimes) == offset + index.transfers_offsets.append(offset) #sentinel + index.n_connections = offset + + print "saving transfer times (footpaths)" + write_text_comment(out,"TRANSFER TIMES") + index.loc_transfer_dist_meters = tell(out) + + for transfer_time in transfertimes: + writeshort(out,(int(transfer_time) >> 2)) + + index.loc_stop_point_waittime = tell(out) + for sp in index.stop_points: + if sp.uri in stop_point_waittimes: + writeshort(out,(int(stop_point_waittimes[sp.uri]) >> 2)) + else: + writeshort(out,(int(MIN_WAITTIME) >> 2)) + +def export_stop_indices(index, out): + print "saving stop indexes" + write_text_comment(out,"STOP STRUCTS") + index.loc_stop_points = tell(out) + struct_2i = Struct('II') + print len(index.jpp_at_sp_offsets),len(index.transfers_offsets) + assert len(index.jpp_at_sp_offsets) == len(index.transfers_offsets) + for stop in zip (index.jpp_at_sp_offsets, index.transfers_offsets) : + out.write(struct_2i.pack(*stop)); + +def export_stop_point_attributes(index, out): + print "saving stop attributes" + write_text_comment(out,"STOP Attributes") + index.loc_stop_point_attributes = tell(out) + for sp in index.stop_points: + attr = 0 + writebyte(out,attr) + +def export_jp_structs(index, out): + print "saving route indexes" + write_text_comment(out,"ROUTE STRUCTS") + index.loc_journey_patterns = tell(out) + route_t = Struct('2I6H') + jpp_offsets = index.offset_jpp + trip_ids_offsets = index.vj_ids_offsets + jp_attributes = [] + + nroutes = len(index.journey_patterns) + + jp_n_jpp = [] + jp_n_vj = [] + routeidx_offsets = [] + jp_min_time = [] + jp_max_time = [] + for jp in index.journey_patterns: + jp_n_jpp.append(len(jp.points)) + jp_n_vj.append(len(index.vehicle_journeys_in_journey_pattern[jp.uri])) + jp_min_time.append(min([vj.departure_time+index.global_utc_offset for vj in index.vehicle_journeys_in_journey_pattern[jp.uri]]) >> 2) + jp_max_time.append(max([vj.departure_time+vj.timedemandgroup.points[-1].totaldrivetime+index.global_utc_offset + for vj in index.vehicle_journeys_in_journey_pattern[jp.uri]]) >> 2) + routeidx_offsets.append(index.idx_for_route_uri[jp.route.uri]) + + jp_attributes.append(1 << jp.route.route_type) + jp_t_fields = [jpp_offsets, trip_ids_offsets,jp_n_jpp, jp_n_vj,jp_attributes,routeidx_offsets,jp_min_time, jp_max_time] + for l in jp_t_fields : + # the extra last route is a sentinel so we can derive list lengths for the last true route. + assert len(l) == nroutes + for route in zip (*jp_t_fields) : + # print route + out.write(route_t.pack(*route)); + out.write(route_t.pack(jpp_offsets[-1]+1,0,0,0,0,0,0,0)) #Sentinel + +def validity_mask(days): + mask = 0 + for day in days: + if day < NUMBER_OF_DAYS: + mask |= 1 << day + return mask + +def export_vj_validities(index, out): + print "writing bitfields indicating which days each trip is active" + # note that bitfields are ordered identically to the trip_ids table, and offsets into that table can be reused + write_text_comment(out,"VJ ACTIVE BITFIELDS") + index.loc_vj_active = tell(out) + + for jp in index.journey_patterns: + for vj in index.vehicle_journeys_in_journey_pattern[jp.uri]: + writeint(out,validity_mask(vj.validity_pattern)) + +def export_jp_validities(index, out): + print "writing bitfields indicating which days each trip is active" + # note that bitfields are ordered identically to the trip_ids table, and offsets into that table can be reused + write_text_comment(out,"JP ACTIVE BITFIELDS") + index.loc_jp_active = tell(out) + n_zeros = 0 + for jp in index.journey_patterns: + writeint(out,validity_mask(index.validity_pattern_for_journey_pattern_uri[jp.uri])) + +def export_platform_codes(index, out): + print "writing out platformcodes for stops" + write_text_comment(out,"PLATFORM CODES") + index.loc_platformcodes = write_list_of_strings(out,index,[sp.platformcode or '' for sp in index.stop_points]) + +def export_stop_pointnames(tdata,index,out): + print "writing out locations for stopnames" + write_text_comment(out,"STOP POINT NAMES") + index.loc_stop_nameidx = write_list_of_strings(out,index,[sp.name or '' for sp in index.stop_points]) + +def export_stop_areanames(index, out): + print "writing out locations for stopareas" + write_text_comment(out,"STOP AREA NAMES") + index.loc_stop_areaidx = write_list_of_strings(out,index,[sa.name or '' for sa in index.stop_areas]) + +def export_operators(index, out): + print "writing out opreators to string pool" + write_text_comment(out,"OPERATOR IDS") + index.loc_operator_ids = write_list_of_strings(out,index,[op.uri or '' for op in index.operators]) + write_text_comment(out,"OPERATOR NAMES") + index.loc_operator_names = write_list_of_strings(out,index,[op.name or '' for op in index.operators]) + write_text_comment(out,"OPERATOR URLS") + index.loc_operator_urls = write_list_of_strings(out,index,[op.url or '' for op in index.operators]) + +def export_commercialmodes(index, out): + print "writing out commercial_mode to string table" + write_text_comment(out,"CCMODE IDS") + index.loc_commercialmode_ids = write_list_of_strings(out,index,[cc.uri or '' for cc in index.commercial_modes]) + write_text_comment(out,"CCMODE NAMES") + index.loc_commercialmode_names = write_list_of_strings(out,index,[cc.name or '' for cc in index.commercial_modes]) + index.loc_commercial_mode_for_jp = tell(out) + for jp in index.journey_patterns: + writeshort(out,index.idx_for_commercial_mode_uri[jp.commercial_mode.uri]) + +def export_physicalmodes(index, out): + print "writing out commercial_mode to string table" + write_text_comment(out,"CCMODE IDS") + index.loc_physicalmode_ids = write_list_of_strings(out,index,[cc.uri or '' for cc in index.physical_modes]) + write_text_comment(out,"CCMODE NAMES") + index.loc_physicalmode_names = write_list_of_strings(out,index,[cc.name or '' for cc in index.physical_modes]) + index.loc_physical_mode_for_line = tell(out) + for l in index.lines: + writeshort(out,index.idx_for_physical_mode_uri[l.physical_mode.uri]) + +def export_stringpool(index, out): + print "writing out stringpool" + write_text_comment(out,"STRINGPOOL") + index.loc_stringpool = tell(out) + written_length = 0 + for string in index.strings: + out.write(string+'\0') + written_length += len(string) + 1 + assert written_length == index.string_length + +def export_linecodes(index, out): + write_text_comment(out,"LINE CODES") + index.loc_line_codes = write_list_of_strings(out,index,[line.code or '' for line in index.lines]) + +def export_linecolors(index, out): + write_text_comment(out,"LINE COLOR") + index.loc_line_color = write_list_of_strings(out,index,[line.color or '' for line in index.lines]) + write_text_comment(out,"LINE COLOR_TEXT") + index.loc_line_color_text = write_list_of_strings(out,index,[line.color_text or '' for line in index.lines]) + +def export_linenames(index, out): + write_text_comment(out,"LINE NAMES") + index.loc_line_names = write_list_of_strings(out,index,[line.name or '' for line in index.lines]) + +def export_line_uris(index, out): + print "writing line ids to string table" + write_text_comment(out,"LINE IDS") + index.loc_line_uris = write_list_of_strings(out,index,[line.uri for line in index.lines]) + +def export_sp_uris(index, out): + print "writing out sorted stop_point ids to string point list" + # stopid index was several times bigger than the string table. it's probably better to just store fixed-width ids. + write_text_comment(out,"STOP_POINT IDS") + index.loc_stop_point_uris = write_list_of_strings(out,index,[sp.uri for sp in index.stop_points]) + +def export_sa_uris(index, out): + print "writing out sorted stop_area ids to string point list" + write_text_comment(out,"STOP_AREA IDS") + index.loc_stop_area_uris = write_list_of_strings(out,index,[sa.uri for sa in index.stop_areas]) + +def export_sa_timezones(index, out): + write_text_comment(out,"STOP_AREA TIMEZONES") + index.loc_stop_area_timezones = write_list_of_strings(out,index,[sa.timezone for sa in index.stop_areas]) + +def export_vj_time_offsets(index, out): + print 'Timetable offset from UTC'+str(index.global_utc_offset) + index.loc_vj_time_offsets = tell(out) + for jp in index.journey_patterns: + for vj in index.vehicle_journeys_in_journey_pattern[jp.uri]: + writesignedbyte(out,(index.global_utc_offset-vj.utc_offset)/60/15) # n * 15 minutes + +def export_vj_uris(index, out): + all_vj_ids = [] + for jp in index.journey_patterns: + for vj in index.vehicle_journeys_in_journey_pattern[jp.uri]: + if vj.realtime_uri: + all_vj_ids.append(vj.realtime_uri) + else: + all_vj_ids.append(vj.uri) + + index.n_vj = len(all_vj_ids) + print "writing trip ids to string table" + # note that trip_ids are ordered by departure time within trip bundles (routes), which are themselves in arbitrary order. + write_text_comment(out,"VJ IDS") + index.loc_vj_uris = write_list_of_strings(out,index,all_vj_ids) + index.n_vj = len(all_vj_ids) + +def export_routes(index, out): + index.loc_line_for_route = tell(out) + for r in index.routes: + writeshort(out,index.idx_for_line_uri[r.line.uri]) + +def export_lines(index, out): + index.loc_operator_for_line = tell(out) + for l in index.lines: + writebyte(out,index.idx_for_operator_uri[l.operator.uri]) + +def write_header (out,index) : + """ Write out a file header containing offsets to the beginning of each subsection. + Must match struct transit_data_header in transitdata.c + :param out: output filepointer + :param index: Index of the datastructure exported + """ + out.seek(0) + htext = "TTABLEV4" + + packed = struct_header.pack(htext, + index.calendar_start_time, + index.timezone, + index.global_utc_offset, + index.n_days, # n_days + index.n_stops, # n_stops + len(index.stop_areas), #n_stop_areas + index.n_stops, # n_stop_attributes + index.n_stops, # n_stop_point_coords + len(index.stop_areas), # n_stop_area_coords + len(index.stop_points), # n_sa_for_ap + index.n_jp, # n_routes + index.n_jpp, # n_jpp + index.n_jpp, # n_jpp_attributes + index.n_jpp, # n_jpp_headsigns + index.n_tpp, # n_stop_times + index.n_vj, # n_vjs + index.n_jpp_at_sp, # n_stop_routes + index.n_connections, #n_transfer_target_stop + index.n_connections, #n_transfer_dist_meters + index.n_vj, #n_trip_active + index.n_jp, # n_jp_active + index.n_stops, # n_platformcodes + len(index.stop_points), # n_stop_nameidx + len(index.stop_areas), # n_stop_nameidx + len(index.operators), # n_operator_id + len(index.operators), # n_operator_names + len(index.operators), # n_operator_urls + len(index.commercial_modes), # n_commercialmode_id + len(index.commercial_modes), # n_commercialmode_names + len(index.physical_modes), # n_physicalmode_id + len(index.physical_modes), # n_physicalmode_names + index.string_length, # n_string_pool (length of the object) + len(index.lines), # n_line_codes + len(index.lines), # n_line_ids + len(index.lines), # n_line_colors + len(index.lines), # n_line_colors_text + len(index.lines), # n_line_names + len(index.stop_points), # n_stop_point_ids + len(index.stop_areas), # n_stop_area_ids + len(index.stop_areas), # n_stop_area_timezones + index.n_vj, # n_vj_time_offsets + index.n_vj, # n_vj_ids + len(index.routes), #n_line_for_route + len(index.lines), #n_operator_for_line + len(index.journey_patterns), #n_commerical_mode_for_jp + len(index.lines), #n_commerical_mode_for_line + index.n_vj, #n_vj_interline_backward + index.n_vj, #n_vj_interline_foward + len(index.stop_points), #n_stop_point_waittime + + index.loc_stop_points, + index.loc_stop_point_attributes, + index.loc_stop_point_coords, + index.loc_journey_patterns, + index.loc_journey_pattern_points, + index.loc_journey_pattern_point_attributes, + index.loc_journey_pattern_point_headsigns, + index.loc_timedemandgroups, + index.loc_vehicle_journeys, + index.loc_jp_at_sp, + index.loc_transfer_target_stop_points, + index.loc_transfer_dist_meters, + index.loc_stop_point_waittime, + index.loc_vj_interline_backward, + index.loc_vj_interline_forward, + index.loc_vj_active, + index.loc_jp_active, + index.loc_platformcodes, + index.loc_stop_nameidx, + index.loc_stop_areaidx, + index.loc_line_for_route, + index.loc_operator_for_line, + index.loc_operator_ids, + index.loc_operator_names, + index.loc_operator_urls, + index.loc_commercialmode_ids, + index.loc_commercialmode_names, + index.loc_commercial_mode_for_jp, + index.loc_physicalmode_ids, + index.loc_physicalmode_names, + index.loc_physical_mode_for_line, + index.loc_stringpool, + index.loc_line_codes, + index.loc_line_names, + index.loc_line_uris, + index.loc_line_color, + index.loc_line_color_text, + index.loc_stop_point_uris, + index.loc_stop_area_uris, + index.loc_stop_area_timezones, + index.loc_vj_time_offsets, + index.loc_vj_uris, + index.loc_stop_area_coords, + index.loc_sa_for_sp, + ) + out.write(packed) + +struct_header = Struct('8sQi91I') + +def export(tdata): + index = make_idx(tdata) + index.n_days = NUMBER_OF_DAYS + index.calendar_start_time = (tdata.validfrom.toordinal() - datetime.date(1970, 1, 1).toordinal()) * 24*60*60 + print index.calendar_start_time + index.n_stops = len(index.stop_points) + index.n_jp = len(index.journey_patterns) + index.timezone = index.put_string(tdata.timezone) + out = open('timetable4.dat','wb') + out.seek(struct_header.size) + + export_sp_coords(index, out) + export_journey_pattern_point_stop(index, out) + export_journey_pattern_point_attributes(index, out) + export_timedemandgroups(index, out) + export_vj_in_jp(index, out) + export_jpp_at_sp(index, out) + export_transfers(tdata,index,out) + export_vj_interlines(tdata,index,out) + export_stop_indices(index, out) + export_stop_point_attributes(index, out) + export_jp_structs(index, out) + export_vj_validities(index, out) + export_jp_validities(index, out) + export_platform_codes(index, out) + export_sa_coords(index, out) + export_sa_for_sp(index, out) + export_stop_pointnames(tdata,index,out) + export_stop_areanames(index, out) + export_operators(index, out) + export_commercialmodes(index, out) + export_physicalmodes(index, out) + export_routes(index, out) + export_lines(index, out) + export_linecodes(index, out) + export_linenames(index, out) + export_linecolors(index, out) + export_journey_pattern_point_headsigns(index, out) + export_line_uris(index, out) + export_sp_uris(index, out) + export_sa_uris(index, out) + export_sa_timezones(index, out) + export_vj_time_offsets(index, out) + export_vj_uris(index, out) + export_stringpool(index, out) + print "reached end of timetable file" + write_text_comment(out,"END TTABLEV4") + index.loc_eof = tell(out) + print "rewinding and writing header... ", + write_header(out,index) + + print ('Number of vehicle_journeys: ',index.n_vj) + out.flush() + out.close() diff --git a/rrtimetable/rrtimetable/exporter/utils.py b/rrtimetable/rrtimetable/exporter/utils.py index e677170..4a4f129 100644 --- a/rrtimetable/rrtimetable/exporter/utils.py +++ b/rrtimetable/rrtimetable/exporter/utils.py @@ -1,55 +1,93 @@ -import sys, struct, time from struct import Struct ############################ - -#pack arrival and departure into 4 bytes w/ offset? - # C structs, must match those defined in .h files. struct_1I = Struct('I') # a single UNSIGNED int def writeint(out,x) : - out.write(struct_1I.pack(x)); + """Write x as packed binary data to file + :param out: output file-pointer + :param x: unsigned int (0 <= x <= 4294967295) + """ + out.write(struct_1I.pack(x)) struct_1H = Struct('H') # a single UNSIGNED short def writeshort(out,x) : - out.write(struct_1H.pack(x)); + """Write x as packed binary data to file + :param out: output file-pointer + :param x: unsigned short (0 <= x <= 65535) + """ + out.write(struct_1H.pack(x)) struct_1B = Struct('B') # a single UNSIGNED byte def writebyte(out,x) : - out.write(struct_1B.pack(x)); + """Write x as packed binary data to file + :param out: output file-pointer + :param x: unsigned byte (0 <= x <= 255) + """ + out.write(struct_1B.pack(x)) + +struct_1b = Struct('b') # a single SIGNED byte +def writesignedbyte(out,x) : + """Write x as packed binary data to file + :param out: output file-pointer + :param x: signed byte (-128 <= x <= 127) + """ + out.write(struct_1b.pack(x)) struct_2H = Struct('HH') # a two UNSIGNED shorts def write_2ushort(out,x, y) : - out.write(struct_2H.pack(x, y)); + """Write coordinate (x,y) as packed binary data to file + :param out: output file-pointer + :param x: short with x-coordinate + :param y: short with y-coordinate + """ + out.write(struct_2H.pack(x, y)) -struct_2f = Struct('2f') # 2 floats +struct_2f = Struct("2f") # 2 floats def write2floats(out,x, y) : - out.write(struct_2f.pack(x, y)); + """Write coordinate (x,y) as packed binary data to file + :param out: output file-pointer + :param x: float with x-coordinate + :param y: float with y-coordinate + """ + out.write(struct_2f.pack(x, y)) -def align(out,width=4) : - """ Align output file to a [width]-byte boundary. """ +def align(out, width=4): + """Align output file to a [width]-byte boundary. + :param width: size of the boundary + :param out: output file-pointer + """ pos = out.tell() n_padding_bytes = (width - (pos % width)) % width out.write('%' * n_padding_bytes) + def tell(out) : - """ Display the current output file position in a human-readable format, then return that position in bytes. """ + """ Display the current output file position in a human-readable format, then return that position in bytes. + :param out: output filepointer + :return: current output file position + """ pos = out.tell() if pos > 1024 * 1024 : text = '%0.2f MB' % (pos / 1024.0 / 1024.0) else : text = '%0.2f kB' % (pos / 1024.0) - print " at position %d in output [%s]" % (pos, text) + print (" at position %d in output [%s]" % (pos, text)) return pos -def write_text_comment(out,string) : - """ Write a text block to the file, just to help indentify segment boundaries, and align. """ + +def write_text_comment(out,string): + """ Write a text block to the file, just to help indentify segment boundaries, and align. + :param out: output filepointer + :param string: text comment + """ string = '|| {:s} ||'.format(string) out.write(string) align(out) -def write_string_table(out,strings) : + +def write_string_table(out,strings): """ Write a table of fixed-width, null-terminated strings to the output file. The argument is a list of Python strings. The output string table begins with an integer indicating the width of each entry in bytes (including the null terminator). @@ -57,11 +95,13 @@ def write_string_table(out,strings) : This data structure can provide a mapping from integer IDs to strings and vice versa: If the strings are sorted, a binary search can be used for string --> ID lookups in logarithmic time. Note: Later we could use fixed width non-null-terminated string: printf("%.*s", length, string); or fwrite(); + :param out: output filepointer + :param strings: list of strings """ # sort a copy of the string list # strings = list(strings) # strings.sort() - width = 0; + width = 0 for s in strings : if len(s) > width : width = len(s) @@ -73,4 +113,3 @@ def write_string_table(out,strings) : padding = '\0' * (width - len(s)) out.write(padding) return loc - diff --git a/rrtimetable/rrtimetable/fusio_dbexport.py b/rrtimetable/rrtimetable/fusio_dbexport.py index 7debb94..f228cb7 100644 --- a/rrtimetable/rrtimetable/fusio_dbexport.py +++ b/rrtimetable/rrtimetable/fusio_dbexport.py @@ -1,6 +1,6 @@ import psycopg2 from model.transit import * -from exporter.timetable3 import export +import exporter.timetable4 def parse_gtfs_time(timestr): return (lambda x:int(x[0])*3600+int(x[1])*60+int(x[2]))(timestr.split(":")) #oh yes I did @@ -9,38 +9,48 @@ def convert(dbname): conn = psycopg2.connect("dbname='ridprod'") cur = conn.cursor() + cur.execute("SELECT DISTINCT agency_timezone FROM fusio.agency"); + feed_timezone = cur.fetchone()[0] cur.execute("SELECT MIN(date)::date FROM fusio.calendar_dates") - tdata = Timetable(cur.fetchone()[0]) + tdata = Timetable(cur.fetchone()[0],feed_timezone) - cur.execute("SELECT stop_id,stop_name,stop_lat,stop_lon FROM fusio.stops WHERE location_type = 1") - for stop_id,stop_name,stop_lat,stop_lon in cur.fetchall(): - StopArea(tdata,stop_id,name=stop_name,latitude=stop_lat,longitude=stop_lon) - cur.execute("SELECT stop_id,stop_name,stop_lat,stop_lon,parent_station,platform_code FROM fusio.stops WHERE coalesce(location_type,0) = 0") - for stop_id,stop_name,stop_lat,stop_lon,parent_station,platform_code in cur.fetchall(): + cur.execute("select physical_mode_id,physical_mode_name from fusio.physical_modes") + for id,name in cur.fetchall(): + PhysicalMode(tdata,id,name=name) + + cur.execute("select commercial_mode_id,commercial_mode_name from fusio.commercial_modes") + for id,name in cur.fetchall(): + CommercialMode(tdata,id,name=name) + + cur.execute("SELECT stop_id,stop_name,stop_lat,stop_lon,stop_timezone FROM fusio.stops WHERE location_type = 1") + for stop_id,stop_name,stop_lat,stop_lon,stop_timezone in cur.fetchall(): + StopArea(tdata,stop_id,stop_timezone or feed_timezone,name=stop_name,latitude=stop_lat,longitude=stop_lon) + cur.execute("SELECT stop_id,stop_name,stop_lat,stop_lon,stop_timezone,parent_station,platform_code FROM fusio.stops WHERE coalesce(location_type,0) = 0") + for stop_id,stop_name,stop_lat,stop_lon,stop_timezone,parent_station,platform_code in cur.fetchall(): stop_area_uri = parent_station try: StopPoint(tdata,stop_id,stop_area_uri,name=stop_name,latitude=stop_lat,longitude=stop_lon,platformcode=platform_code) except: stop_area_uri = 'StopArea:ZZ:'+stop_id - StopArea(tdata,stop_area_uri,name=stop_name,latitude=stop_lat,longitude=stop_lon) + StopArea(tdata,stop_area_uri,stop_timezone or feed_timezone,name=stop_name,latitude=stop_lat,longitude=stop_lon) StopPoint(tdata,stop_id,stop_area_uri,name=stop_name,latitude=stop_lat,longitude=stop_lon,platformcode=platform_code) cur.execute("SELECT from_stop_id,to_stop_id,min_transfer_time,transfer_type FROM fusio.transfers") for from_stop_id,to_stop_id,min_transfer_time,transfer_type in cur.fetchall(): - if from_stop_id == to_stop_id: - continue - if (int(min_transfer_time) >> 2) > 255: - min_transfer_time = 255 try: Connection(tdata,from_stop_id,to_stop_id,min_transfer_time,type=transfer_type) Connection(tdata,to_stop_id,from_stop_id,min_transfer_time,type=transfer_type) except: pass - cur.execute("SELECT agency_id,agency_name,agency_url FROM fusio.agency") - for agency_id,agency_name,agency_url in cur.fetchall(): - Operator(tdata,agency_id,name=agency_name,url=agency_url) - cur.execute("SELECT line_id,line_name,line_code,network_id FROM fusio.lines") - for line_id,line_name,line_code,network_id in cur.fetchall(): - Line(tdata,line_id,network_id,name=line_name,code=line_code) + cur.execute("SELECT agency_id,agency_name,agency_url,agency_timezone FROM fusio.agency") + for agency_id,agency_name,agency_url,agency_timezone in cur.fetchall(): + Operator(tdata,agency_id,agency_timezone,name=agency_name,url=agency_url) + cur.execute(""" +SELECT DISTINCT ON (line_id) +line_id,line_name,line_code,network_id,physical_mode_id,line_color +FROM fusio.lines JOIN fusio.routes USING (line_id) JOIN fusio.trips USING (route_id) +""") + for line_id,line_name,line_code,network_id,physical_mode_id,line_color in cur.fetchall(): + Line(tdata,line_id,network_id,physical_mode_id,name=line_name,code=line_code,color=line_color) cur.execute("SELECT route_id,line_id,route_type FROM fusio.routes") for route_id,line_id,route_type in cur.fetchall(): Route(tdata,route_id,line_id,route_type=route_type) @@ -51,24 +61,27 @@ def convert(dbname): calendars[service_id] = validdates trip_id = None + cur.close() + cur = conn.cursor('trips') cur.execute(""" -SELECT trip_id,service_id,route_id,trip_headsign,stop_sequence,stop_id,arrival_time,departure_time,pickup_type,drop_off_type -FROM fusio.trips JOIN fusio.stop_times USING (trip_id) +SELECT trip_id,service_id,route_id,trip_headsign,stop_sequence,stop_id,arrival_time,departure_time,pickup_type,drop_off_type,stop_headsign,commercial_mode_id, block_id +FROM fusio.trips JOIN fusio.stop_times USING (trip_id) JOIN fusio.routes USING (route_id) JOIN fusio.lines USING (line_id) ORDER BY trip_id,stop_sequence """) vj = None last_trip_id = None - for trip_id,service_id,route_id,trip_headsign,stop_sequence,stop_id,arrival_time,departure_time,pickup_type,drop_off_type in cur.fetchall(): + for trip_id,service_id,route_id,trip_headsign,stop_sequence,stop_id,arrival_time,departure_time,pickup_type,drop_off_type,stop_headsign,ccmode_id,block_id in cur: if trip_id != last_trip_id: if vj is not None: vj.finish() last_trip_id = trip_id - vj = VehicleJourney(tdata,trip_id,route_id,headsign=trip_headsign) + vj = VehicleJourney(tdata,trip_id,route_id,ccmode_id,headsign=trip_headsign,blockref=block_id) for date in calendars[service_id]: vj.setIsValidOn(date) - vj.add_stop(stop_id,parse_gtfs_time(arrival_time),parse_gtfs_time(departure_time),forboarding=(pickup_type != 1),foralighting=(drop_off_type != 1)) + vj.add_stop(stop_id,parse_gtfs_time(arrival_time),parse_gtfs_time(departure_time),forboarding=(pickup_type != 1),foralighting=(drop_off_type != 1),headsign=stop_headsign) if vj is not None: vj.finish() return tdata tdata = convert('ridprod') export(tdata) +exporter.timetable4.export(tdata) diff --git a/rrtimetable/rrtimetable/gtfs2rrrr.py b/rrtimetable/rrtimetable/gtfs2rrrr.py index ac7c589..a24a1d6 100644 --- a/rrtimetable/rrtimetable/gtfs2rrrr.py +++ b/rrtimetable/rrtimetable/gtfs2rrrr.py @@ -1,34 +1,69 @@ from model.transit import * from gtfsdb import GTFSDatabase import sys -from exporter.timetable3 import export +import exporter.timetable4 from datetime import timedelta, date MAX_DAYS = 32 -def convert(gtfsdb): - - from_date,to_date = gtfsdb.date_range() - tdata = Timetable(from_date) - print "Timetable valid from "+str(from_date) - - for stop_id,stop_name,stop_lat,stop_lon in gtfsdb.stop_areas(): - StopArea(tdata,stop_id,name=stop_name,latitude=stop_lat,longitude=stop_lon) - - for stop_id,stop_name,stop_lat,stop_lon,parent_station,platform_code in gtfsdb.stop_points(): +def put_gtfs_modes(tdata): + PhysicalMode(tdata,'0',name='Tram') + PhysicalMode(tdata,'1',name='Metro') + PhysicalMode(tdata,'2',name='Rail') + PhysicalMode(tdata,'3',name='Bus') + PhysicalMode(tdata,'4',name='Ferry') + PhysicalMode(tdata,'5',name='Cable car') + PhysicalMode(tdata,'6',name='Gondola') + PhysicalMode(tdata,'7',name='Funicular') + + CommercialMode(tdata,'0',name='Tram') + CommercialMode(tdata,'1',name='Metro') + CommercialMode(tdata,'2',name='Rail') + CommercialMode(tdata,'3',name='Bus') + CommercialMode(tdata,'4',name='Ferry') + CommercialMode(tdata,'5',name='Cable car') + CommercialMode(tdata,'6',name='Gondola') + CommercialMode(tdata,'7',name='Funicular') + +def determine_timezone(gtfsdb): + timezones = set([]) + for agency_id,agency_name,agency_url,agency_timezone in gtfsdb.agencies(): + if agency_timezone is not None: + timezones.add(agency_timezone) + + if len(timezones) == 0: + raise Exception("Agency has required field agency_timezone not filled in") + elif len(timezones) > 1: + raise Exception("Feed has multiple timezones, RRRR currently only supports one") #But should be trivial to change + else: + return list(timezones)[0] + +def convert(gtfsdb, from_date=None): + if from_date == None: + from_date, _ = gtfsdb.date_range() + + feed_timezone = determine_timezone(gtfsdb) + tdata = Timetable(from_date,feed_timezone) + print "Timetable valid from %s to %s, timezone: %s" % (from_date, from_date + datetime.timedelta(days=MAX_DAYS),feed_timezone) + + put_gtfs_modes(tdata) + + for agency_id,agency_name,agency_url,agency_timezone in gtfsdb.agencies(): + Operator(tdata,agency_id,agency_timezone,name=agency_name,url=agency_url) + + for stop_id,stop_name,stop_lat,stop_lon,stop_timezone in gtfsdb.stop_areas(): + StopArea(tdata,stop_id,stop_timezone or feed_timezone,name=stop_name,latitude=stop_lat,longitude=stop_lon) + + for stop_id,stop_name,stop_lat,stop_lon,stop_timezone,parent_station,platform_code in gtfsdb.stop_points(): stop_area_uri = parent_station try: StopPoint(tdata,stop_id,stop_area_uri,name=stop_name,latitude=stop_lat,longitude=stop_lon,platformcode=platform_code) except: stop_area_uri = 'StopArea:ZZ:'+stop_id - StopArea(tdata,stop_area_uri,name=stop_name,latitude=stop_lat,longitude=stop_lon) + StopArea(tdata,stop_area_uri,stop_timezone or feed_timezone,name=stop_name,latitude=stop_lat,longitude=stop_lon) StopPoint(tdata,stop_id,stop_area_uri,name=stop_name,latitude=stop_lat,longitude=stop_lon,platformcode=platform_code) for from_stop_id,to_stop_id,min_transfer_time,transfer_type in gtfsdb.transfers(): - if from_stop_id == to_stop_id: - continue - if (int(min_transfer_time) >> 2) > 255: - min_transfer_time = 255 try: Connection(tdata,from_stop_id,to_stop_id,min_transfer_time,type=transfer_type) Connection(tdata,to_stop_id,from_stop_id,min_transfer_time,type=transfer_type) @@ -43,14 +78,18 @@ def convert(gtfsdb): except: pass - for agency_id,agency_name,agency_url in gtfsdb.agencies(): - Operator(tdata,agency_id,name=agency_name,url=agency_url) + for sp in tdata.stop_points.values(): + min_transfer_time = 120 #Create loop-transfers + try: + Connection(tdata,sp.uri,sp.uri,min_transfer_time,type=transfer_type) + except: + pass - for line_id,line_name,line_code,agency_id in gtfsdb.lines(): + for line_id,line_name,line_code,agency_id,route_type,route_color,route_text_color in gtfsdb.lines(): if agency_id is None: if len(index.operators) == 1: agency_id = list(index.operators.keys())[0] - Line(tdata,line_id,agency_id,name=line_name,code=line_code) + Line(tdata,line_id,agency_id,str(route_type),name=line_name,code=line_code,color=route_color,color_text=route_text_color) for route_id,line_id,route_type in gtfsdb.routes(): Route(tdata,route_id,line_id,route_type=route_type) @@ -63,10 +102,10 @@ def convert(gtfsdb): if sid not in calendars: calendars[sid] = [] calendars[sid].append(date) - + vj = None last_trip_id = None - for trip_id,service_id,route_id,trip_headsign,stop_sequence,stop_id,arrival_time,departure_time,pickup_type,drop_off_type in gtfsdb.stop_times(): + for trip_id,realtime_trip_id,service_id,route_id,trip_headsign,stop_sequence,stop_id,arrival_time,departure_time,pickup_type,drop_off_type,stop_headsign,route_type,block_id in gtfsdb.stop_times(): if trip_id != last_trip_id: if vj is not None: vj.finish() @@ -74,10 +113,10 @@ def convert(gtfsdb): if service_id not in calendars: continue last_trip_id = trip_id - vj = VehicleJourney(tdata,trip_id,route_id,headsign=trip_headsign) + vj = VehicleJourney(tdata,trip_id,route_id,str(route_type),headsign=trip_headsign,blockref=block_id,realtime_uri=realtime_trip_id) for date in calendars[service_id]: vj.setIsValidOn(date) - vj.add_stop(stop_id,arrival_time,departure_time,forboarding=(pickup_type != 1),foralighting=(drop_off_type != 1)) + vj.add_stop(stop_id,arrival_time,departure_time,forboarding=(pickup_type != 1),foralighting=(drop_off_type != 1),headsign=stop_headsign) if vj is not None: vj.finish() return tdata @@ -87,23 +126,29 @@ def convert(gtfsdb): def main(): parser = OptionParser() parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="make a bunch of noise" ) + parser.add_option("-d", "--from-date", action="store", type="string", dest="from_date", default=None, help="Explicitly set the first valid date of the timetable. Format: YYYY-MM-DD" ) (options, args) = parser.parse_args() + if options.from_date: + from_date = datetime.datetime.strptime(options.from_date, '%Y-%m-%d').date() + else: + from_date = None + if len(args) < 1: print("Loads a GTFS file and generate a timetable suitable for RRRR.\nusage: gtfs2rrrr.py ") exit() - + gtfsdb_filename = args[0]+'.gtfsdb' gtfs_filename = args[0] - + gtfsdb = GTFSDatabase( gtfsdb_filename, overwrite=True ) gtfsdb.load_gtfs( gtfs_filename, None, reporter=sys.stdout, verbose=options.verbose ) - tdata = convert(gtfsdb) + tdata = convert(gtfsdb, from_date) if len(tdata.journey_patterns) == 0 or len(tdata.vehicle_journeys) == 0: print "No valid trips in this GTFS file!" sys.exit(1) - export(tdata) + exporter.timetable4.export(tdata) -if __name__=='__main__': +if __name__=='__main__': main() diff --git a/rrtimetable/rrtimetable/gtfsdb.py b/rrtimetable/rrtimetable/gtfsdb.py index 8e0c710..4745fec 100644 --- a/rrtimetable/rrtimetable/gtfsdb.py +++ b/rrtimetable/rrtimetable/gtfsdb.py @@ -12,7 +12,7 @@ # # Unless otherwise noted, code included with Graphserver is covered under the BSD license -# Copyright (c) 2007, 2008, 2009, 2010, Graphserver contributors +# Copyright (c) 2007, 2008, 2009, 2010, Graphserver contributors # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -46,8 +46,8 @@ def withProgress(seq, modValue=100): c = -1 for c, v in enumerate(seq): - if (c+1) % modValue == 0: - sys.stdout.write("%s\r" % (c+1)) + if (c+1) % modValue == 0: + sys.stdout.write("%s\r" % (c+1)) sys.stdout.flush() yield v @@ -56,11 +56,11 @@ def withProgress(seq, modValue=100): class UTF8TextFile(object): def __init__(self, fp): self.fp = fp - + def next(self): nextline = self.fp.next() return nextline.encode( "UTF-8" ) - + def __iter__(self): return self @@ -70,10 +70,10 @@ def between(n, a, b): def cons(ary): for i in range(len(ary)-1): yield (ary[i], ary[i+1]) - + def parse_gtfs_time(timestr): return (lambda x:int(x[0])*3600+int(x[1])*60+int(x[2]))(timestr.split(":")) #oh yes I did - + def parse_gtfs_date(datestr): return (int(datestr[0:4]), int(datestr[4:6]), int(datestr[6:8])) @@ -83,9 +83,9 @@ def create_table(cc, gtfs_basename, header): cc.execute("create table %s (%s)"%(gtfs_basename,",".join(sqlite_field_definitions))) def load_gtfs_table_to_sqlite(fp, gtfs_basename, cc, header=None, verbose=False): - """header is iterable of (fieldname, fieldtype, processing_function). For example, (("stop_sequence", "INTEGER", int),). + """header is iterable of (fieldname, fieldtype, processing_function). For example, (("stop_sequence", "INTEGER", int),). "TEXT" is default fieldtype. Default processing_function is lambda x:x""" - + ur = UTF8TextFile( fp ) rd = csv.reader( ur ) @@ -93,9 +93,9 @@ def load_gtfs_table_to_sqlite(fp, gtfs_basename, cc, header=None, verbose=False) gtfs_header = [x.strip() for x in rd.next()] print(gtfs_header) - + gtfs_field_indices = dict(zip(gtfs_header, range(len(gtfs_header)))) - + field_name_locations = [gtfs_field_indices[field_name] if field_name in gtfs_field_indices else None for field_name, field_type, field_converter in header] field_converters = [field_definition[2] for field_definition in header] field_operator = list(zip(field_name_locations, field_converters)) @@ -103,26 +103,30 @@ def load_gtfs_table_to_sqlite(fp, gtfs_basename, cc, header=None, verbose=False) # populate stoptimes table insert_template = 'insert into %s (%s) values (%s)'%(gtfs_basename,",".join([x[0] for x in header]), ",".join(["?"]*len(header))) print( insert_template ) - for i, line in withProgress(enumerate(rd), 5000): - # carry on quietly if there's a blank line in the csv - if line == []: - continue - - _line = [] - for i, converter in field_operator: - if i end_date: return [] - + # Use the day-of-week name to query for all service periods that run on that day dow_name = self.DOW_INDEX[sample_date.weekday()] service_periods = list( self.get_cursor().execute( "SELECT service_id, start_date, end_date FROM calendar WHERE %s=1"%dow_name ) ) - + # Exclude service periods whose range does not include this sample_date service_periods = [x for x in service_periods if (int(x[1]) <= datetimeint and int(x[2]) >= datetimeint)] - + # Cut service periods down to service IDs sids = set( [x[0] for x in service_periods] ) - + # For each exception on the given sample_date, add or remove service_id to the accumulating list - + for exception_sid, exception_type in self.get_cursor().execute( "select service_id, exception_type from calendar_dates WHERE date = ?", (datetimestr,) ): if exception_type == 1: sids.add( exception_sid ) elif exception_type == 2: if exception_sid in sids: sids.remove( exception_sid ) - + return list(sids) @@ -409,14 +413,14 @@ def main_compile_gtfsdb(): if len(args) < 2: print("Loads a GTFS file into an SQLite database, enabling more sophisticated queries.\nusage: gtfsdb.py ") exit() - + gtfsdb_filename = args[1] gtfs_filename = args[0] - + gtfsdb = GTFSDatabase( gtfsdb_filename, overwrite=True ) gtfsdb.load_gtfs( gtfs_filename, options.tables, reporter=sys.stdout, verbose=options.verbose ) print "Done loading GTFS into database. Don't forget to add transfers to the database if needed!" -if __name__=='__main__': +if __name__=='__main__': main_compile_gtfsdb() diff --git a/rrtimetable/rrtimetable/model/transit.py b/rrtimetable/rrtimetable/model/transit.py index 8cd33c1..6c963d5 100644 --- a/rrtimetable/rrtimetable/model/transit.py +++ b/rrtimetable/rrtimetable/model/transit.py @@ -1,9 +1,15 @@ import datetime +import dateutil.tz +import utils +from copy import copy class Timetable: - def __init__(self,validfrom): + def __init__(self,validfrom,timezone): if not isinstance(validfrom, datetime.date): raise TypeError('validfrom must be a datetime.date, not a %s' % type(validfrom)) + if not utils.validate_timezone(timezone): + raise Exception("Invalid timezone "+str(timezone)) + self.timezone = timezone self.validfrom = validfrom self.stop_areas = {} self.stop_points = {} @@ -11,32 +17,75 @@ def __init__(self,validfrom): self.routes = {} self.lines = {} self.vehicle_journeys = {} + self.vehicle_journeys_utc = {} self.connections = {} self.journey_patterns = {} self.signature_to_jp = {} self.timedemandgroups = {} self.signature_to_timedemandgroup = {} + self.physical_modes = {} + self.commercial_modes = {} class StopArea: - def __init__(self,timetable,uri,name=None,latitude=None,longitude=None): + + def __init__(self,timetable,uri,timezone,name=None,latitude=None,longitude=None): self.type = 'stop_area' self.uri = uri if uri in timetable.stop_areas: - raise ValueError('Violation of unique StopArea key') + raise ValueError('Violation of unique StopArea key') timetable.stop_areas[uri] = self + if not utils.validate_timezone(timezone): + raise Exception("Invalid timezone") + self.timezone = timezone self.name = name self.latitude = latitude self.longitude = longitude +class CommercialMode: + def __init__(self,timetable,uri,name=None): + self.type = 'commercial_mode' + self.uri = uri + if uri in timetable.commercial_modes: + raise ValueError('Violation of unique CommercialMode key') + timetable.commercial_modes[uri] = self + self.name = name + + def __hash__(self): + return hash(freeze(self.__dict__)) + + def __eq__(self, other): + if isinstance(other, self.__class__): + return self.__dict__ == other.__dict__ + else: + return False + +class PhysicalMode: + def __init__(self,timetable,uri,name=None): + self.type = 'physical_mode' + self.uri = uri + if uri in timetable.physical_modes: + raise ValueError('Violation of unique PhysicalMode key') + timetable.physical_modes[uri] = self + self.name = name + + def __hash__(self): + return hash(freeze(self.__dict__)) + + def __eq__(self, other): + if isinstance(other, self.__class__): + return self.__dict__ == other.__dict__ + else: + return False + class StopPoint: def __init__(self,timetable,uri,stop_area_uri,name=None,platformcode=None,latitude=None,longitude=None): self.type = 'stop_point' self.uri = uri if stop_area_uri not in timetable.stop_areas: - raise ValueError('Violation of foreign key, stop_area not found') + raise ValueError('Violation of foreign key, stop_area not found') self.stop_area = timetable.stop_areas[stop_area_uri] if uri in timetable.stop_points: - raise ValueError('Violation of unique StopPoint key') + raise ValueError('Violation of unique StopPoint key') timetable.stop_points[uri] = self self.name = name self.platformcode = platformcode @@ -47,9 +96,9 @@ class Connection: def __init__(self,timetable,from_stop_point_uri,to_stop_point_uri,min_transfer_time,type=None,): self.type = 'connection' if from_stop_point_uri not in timetable.stop_points: - raise ValueError('Violation of foreign key, from_stop_point not found') + raise ValueError('Violation of foreign key, from_stop_point not found') if to_stop_point_uri not in timetable.stop_points: - raise ValueError('Violation of foreign key, to_stop_point not found') + raise ValueError('Violation of foreign key, to_stop_point not found') if (from_stop_point_uri,to_stop_point_uri) in timetable.connections: raise ValueError('Violation of unique key, unique connections between stop_points required') self.from_stop_point = timetable.stop_points[from_stop_point_uri] @@ -59,27 +108,35 @@ def __init__(self,timetable,from_stop_point_uri,to_stop_point_uri,min_transfer_t timetable.connections[(from_stop_point_uri,to_stop_point_uri)] = self class Operator: - def __init__(self,timetable,uri,name=None,url=None): + def __init__(self,timetable,uri,timezone,name=None,url=None): self.type = 'operator' self.uri = uri if uri in timetable.operators: - raise ValueError('Violation of unique Operator key') + raise ValueError('Violation of unique Operator key') timetable.operators[uri] = self + if not utils.validate_timezone(timezone): + raise Exception("Invalid timezone") + self.timezone = timezone self.name = name self.url = url class Line: - def __init__(self,timetable,uri,operator_uri,name=None,code=None): + def __init__(self,timetable,uri,operator_uri,physical_mode_uri,name=None,code=None,color=None,color_text=None): self.type = 'line' self.uri = uri if operator_uri not in timetable.operators: raise ValueError('Violation of foreign key, operator not found') self.operator = timetable.operators[operator_uri] + if physical_mode_uri not in timetable.physical_modes: + raise ValueError('Violation of foreign key, physical_mode not found') + self.physical_mode = timetable.physical_modes[physical_mode_uri] if uri in timetable.lines: - raise ValueError('Violation of unique Line key') + raise ValueError('Violation of unique Line key') timetable.lines[uri] = self self.name = name self.code = code + self.color = color + self.color_text = color_text class Route: def __init__(self,timetable,uri,line_uri,direction=None,route_type=None): @@ -91,7 +148,7 @@ def __init__(self,timetable,uri,line_uri,direction=None,route_type=None): self.line = timetable.lines[line_uri] self.route_type = route_type if uri in timetable.routes: - raise ValueError('Violation of unique Route key') + raise ValueError('Violation of unique Route key') timetable.routes[uri] = self if direction is not None: self.direction = direction @@ -104,7 +161,7 @@ def freeze(o): return o class JourneyPatternPoint: - def __init__(self,timetable,stop_point_uri,forboarding=True,foralighting=True,timingpoint=False,destination=None): + def __init__(self,timetable,stop_point_uri,forboarding=True,foralighting=True,timingpoint=False,headsign=None): if stop_point_uri not in timetable.stop_points: raise ValueError('Violation of foreign key, StopPoint not found') self.type = 'journey_pattern_point' @@ -112,7 +169,7 @@ def __init__(self,timetable,stop_point_uri,forboarding=True,foralighting=True,ti self.forboarding = forboarding self.foralighting = foralighting self.timingpoint = timingpoint - self.destination = destination + self.headsign = headsign def __hash__(self): return hash(freeze(self.__dict__)) @@ -125,6 +182,10 @@ def __eq__(self, other): class TimeDemandGroupPoint: def __init__(self,drivetime,totaldrivetime): + """ + :param drivetime: drive-time since start of trip + :param totaldrivetime: drive-time combined with dwell time at this stoppoint + """ self.type = 'timedemandgrouppoint' self.drivetime = drivetime self.totaldrivetime = totaldrivetime @@ -145,14 +206,14 @@ def __init__(self,timetable,uri,points): self.points = points class JourneyPattern: - def __init__(self,timetable,uri,route_uri,points,headsign=None,productcategory=None): + def __init__(self,timetable,uri,route_uri,points,headsign=None,commercial_mode=None): if route_uri not in timetable.routes: raise ValueError('Violation of foreign key, route not found') self.route = timetable.routes[route_uri] self.type = 'journey_pattern' self.uri = uri self.points = points - self.productcategory = productcategory + self.commercial_mode = commercial_mode self.headsign = headsign def __hash__(self): @@ -165,18 +226,22 @@ def __eq__(self, other): return False class VehicleJourney: - def __init__(self,timetable,uri,route_uri,headsign=None,productcategory=None): + def __init__(self,timetable,uri,route_uri,commercial_mode_uri,headsign=None,blockref=None,realtime_uri=None): self.timetable = timetable self.type = 'vehicle_journey' self.uri = uri - self.productcategory = productcategory + self.realtime_uri = realtime_uri self.headsign = headsign self.validity_pattern = set([]) if uri in timetable.vehicle_journeys: - raise ValueError('Violation of unique VehicleJourney key') + raise ValueError('Violation of unique VehicleJourney key') timetable.vehicle_journeys[uri] = self if route_uri not in timetable.routes: raise ValueError('Violation of foreign key, route not found') + if commercial_mode_uri not in timetable.commercial_modes: + raise ValueError('Violation of foreign key, commercial_mode not found') + self.blockref = blockref + self.commercial_mode = timetable.commercial_modes[commercial_mode_uri] self.route = timetable.routes[route_uri] self.points = [] self.timedemandgroup = [] @@ -190,7 +255,7 @@ def setIsValidOn(self,validdate): self.validity_pattern.add((validdate-self.timetable.validfrom).days) return True - def add_stop(self,stop_point_uri,arrival_time,departure_time,forboarding=True,foralighting=True,timingpoint=False,destination=None): + def add_stop(self,stop_point_uri,arrival_time,departure_time,forboarding=True,foralighting=True,timingpoint=False,headsign=None): if self.__isfinished__: raise AttributeError('VehicleJourney was previously completed') if stop_point_uri not in self.timetable.stop_points: @@ -208,9 +273,11 @@ def add_stop(self,stop_point_uri,arrival_time,departure_time,forboarding=True,fo if drivetime < self.timedemandgroup[-1].totaldrivetime: raise ValueError('Timetravel from latest stop') self.timedemandgroup.append(TimeDemandGroupPoint(drivetime,totaldrivetime)) - self.points.append(JourneyPatternPoint(self.timetable,stop_point_uri,forboarding,foralighting,timingpoint,destination)) + self.points.append(JourneyPatternPoint(self.timetable,stop_point_uri,forboarding,foralighting,timingpoint,headsign=(headsign or self.headsign))) - def finish(self): + def finish(self,utc_offset_calculation=utils.utc_time_gtfs): + if self.__isfinished__: + raise AttributeError('VehicleJourney was previously completed') self.__isfinished__ = True if len(self.points) != len(self.timedemandgroup): raise ValueError('Length of timedemandgroup is not equal with journeypattern') @@ -219,11 +286,11 @@ def finish(self): self.timedemandgroup = tuple(self.timedemandgroup) self.points[-1].forboarding = False #Do not allow boarding at final stop self.points = tuple(self.points) - pattern_signature = (self.route.uri,self.productcategory or '',self.headsign or '',self.points) + pattern_signature = (self.route.uri,self.commercial_mode,self.headsign or '',self.points) if pattern_signature in self.timetable.signature_to_jp: self.journey_pattern = self.timetable.signature_to_jp[pattern_signature] else: - jp = JourneyPattern(self.timetable,str(len(self.timetable.signature_to_jp)),self.route.uri,self.points,self.headsign,self.productcategory) + jp = JourneyPattern(self.timetable,str(len(self.timetable.signature_to_jp)),self.route.uri,self.points,self.headsign,self.commercial_mode) self.timetable.signature_to_jp[pattern_signature] = jp self.timetable.journey_patterns[jp.uri] = jp self.journey_pattern = jp @@ -236,3 +303,15 @@ def finish(self): self.timetable.signature_to_timedemandgroup[self.timedemandgroup] = timegroup self.timetable.timedemandgroups[timegroup.uri] = timegroup self.timedemandgroup = timegroup + + #Build UTC VehicleJourneys + for validdate in [self.timetable.validfrom + datetime.timedelta(days=validdate) for validdate in self.validity_pattern]: + utc_offset,utc_deptime = utc_offset_calculation(validdate,self.departure_time,self.route.line.operator.timezone) + utc_key = (self.uri,utc_offset) + if utc_key not in self.timetable.vehicle_journeys_utc: + self.timetable.vehicle_journeys_utc[utc_key] = copy(self) + self.timetable.vehicle_journeys_utc[utc_key].validity_pattern = set([]) + self.timetable.vehicle_journeys_utc[utc_key].departure_time = utc_deptime + self.timetable.vehicle_journeys_utc[utc_key].utc_offset = utc_offset + utc_vj = self.timetable.vehicle_journeys_utc[utc_key] + utc_vj.validity_pattern.add((validdate-self.timetable.validfrom).days) diff --git a/rrtimetable/rrtimetable/model/utils.py b/rrtimetable/rrtimetable/model/utils.py new file mode 100644 index 0000000..7640815 --- /dev/null +++ b/rrtimetable/rrtimetable/model/utils.py @@ -0,0 +1,17 @@ +import datetime +import dateutil +import pytz + +def validate_timezone(timezone): + return timezone is not None and dateutil.tz.gettz(timezone) is not None + +timezone_cache = {} +resp_cache = {} + +def utc_time_gtfs(validdate,time,timezone_id): + if timezone_id not in timezone_cache: + timezone_cache[timezone_id] = pytz.timezone(timezone_id) + timezone = timezone_cache[timezone_id] + dt = datetime.datetime.combine(validdate,datetime.time(12,0,0)) + utc_offset = timezone.utcoffset(dt).seconds + return (utc_offset,time-utc_offset) diff --git a/rrtimetable/rrtimetable/tests/exportv3_tests.py b/rrtimetable/rrtimetable/tests/exportv4_tests.py similarity index 74% rename from rrtimetable/rrtimetable/tests/exportv3_tests.py rename to rrtimetable/rrtimetable/tests/exportv4_tests.py index ff861fc..71ff22f 100644 --- a/rrtimetable/rrtimetable/tests/exportv3_tests.py +++ b/rrtimetable/rrtimetable/tests/exportv4_tests.py @@ -1,16 +1,15 @@ import unittest -import helper from model.transit import * -from exporter.timetable3 import export +from exporter.timetable4 import export import datetime class TestSequenceFunctions(unittest.TestCase): def test_integration(self): - tdata = Timetable(datetime.date(2014,1,1)) - sa = StopArea(tdata,'SA1',name='SA1') - sa = StopArea(tdata,'SA2',name='SA2') - sa = StopArea(tdata,'SA3',name='SA3') + tdata = Timetable(datetime.date(2014,1,1),'Europe/Amsterdam') + sa = StopArea(tdata,'SA1','Europe/Amsterdam',name='SA1') + sa = StopArea(tdata,'SA2','Europe/Amsterdam',name='SA2') + sa = StopArea(tdata,'SA3','Europe/Amsterdam',name='SA3') sp = StopPoint(tdata,'SP1','SA1',name='SP1') sp = StopPoint(tdata,'SP2','SA2',name='SP2') sp = StopPoint(tdata,'SP3','SA3',name='SP3') @@ -18,10 +17,12 @@ def test_integration(self): conn = Connection(tdata,'SP2','SP1',120,type=2) conn = Connection(tdata,'SP3','SP2',120,type=2) conn = Connection(tdata,'SP2','SP3',120,type=2) - op = Operator(tdata,'OP1',name='Operator',url='http://www.example.com') - l = Line(tdata,'L1','OP1',name='Testline',code='T') - r = Route(tdata,'R1','L1',direction=1) - vj = VehicleJourney(tdata,'VJ1','R1') + op = Operator(tdata,'OP1',name='Operator',url='http://www.example.com',timezone='Europe/Amsterdam') + buspm = PhysicalMode(tdata,'3',name='Bus') + buscc = CommercialMode(tdata,'3',name='Bus') + l = Line(tdata,'L1','OP1','3',name='Testline',code='T') + r = Route(tdata,'R1','L1',direction=1,route_type=3) + vj = VehicleJourney(tdata,'VJ1','R1','3') vj.setIsValidOn(datetime.date(2014,1,1)) vj.setIsValidOn(datetime.date(2014,1,2)) vj.setIsValidOn(datetime.date(2014,1,3)) @@ -30,7 +31,7 @@ def test_integration(self): vj.add_stop('SP3',2400,2400,forboarding=True,foralighting=True,timingpoint=True) vj.finish() - vj = VehicleJourney(tdata,'VJ2','R1') + vj = VehicleJourney(tdata,'VJ2','R1','3') vj.setIsValidOn(datetime.date(2014,1,1)) vj.setIsValidOn(datetime.date(2014,1,2)) vj.setIsValidOn(datetime.date(2014,1,3)) @@ -39,7 +40,7 @@ def test_integration(self): vj.add_stop('SP3',2500,2500,forboarding=True,foralighting=True,timingpoint=True) vj.finish() - vj = VehicleJourney(tdata,'VJ3','R1') + vj = VehicleJourney(tdata,'VJ3','R1','3') vj.setIsValidOn(datetime.date(2014,1,1)) vj.setIsValidOn(datetime.date(2014,1,2)) vj.setIsValidOn(datetime.date(2014,1,3)) @@ -48,7 +49,7 @@ def test_integration(self): vj.add_stop('SP3',2500,2500,forboarding=True,foralighting=True,timingpoint=True) vj.finish() - vj = VehicleJourney(tdata,'VJ4','R1') + vj = VehicleJourney(tdata,'VJ4','R1','3') vj.setIsValidOn(datetime.date(2014,1,1)) vj.setIsValidOn(datetime.date(2014,1,2)) vj.setIsValidOn(datetime.date(2014,1,3)) diff --git a/rrtimetable/rrtimetable/tests/helper.py b/rrtimetable/rrtimetable/tests/helper.py deleted file mode 100644 index 5259297..0000000 --- a/rrtimetable/rrtimetable/tests/helper.py +++ /dev/null @@ -1,3 +0,0 @@ -import os -parentdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -os.sys.path.insert(0,parentdir) diff --git a/rrtimetable/rrtimetable/tests/model_tests.py b/rrtimetable/rrtimetable/tests/model_tests.py index 2dbdf90..b72ec62 100644 --- a/rrtimetable/rrtimetable/tests/model_tests.py +++ b/rrtimetable/rrtimetable/tests/model_tests.py @@ -1,43 +1,44 @@ import unittest -import helper from model.transit import * import datetime class TestSequenceFunctions(unittest.TestCase): def test_equal(self): - tdata = Timetable(datetime.date(2014,1,1)) - sa = StopArea(tdata,'SA1',name='SA1') - sa = StopArea(tdata,'SA2',name='SA2') - sa = StopArea(tdata,'SA3',name='SA3') + tdata = Timetable(datetime.date(2014,1,1),'Europe/Amsterdam') + sa = StopArea(tdata,'SA1','Europe/Amsterdam',name='SA1') + sa = StopArea(tdata,'SA2','Europe/Amsterdam',name='SA2') + sa = StopArea(tdata,'SA3','Europe/Amsterdam',name='SA3') sp = StopPoint(tdata,'SP1','SA1',name='SP1') sp = StopPoint(tdata,'SP2','SA2',name='SP1') sp = StopPoint(tdata,'SP3','SA3',name='SP1') - + jpp1 = JourneyPatternPoint(tdata,'SP1',forboarding=True,foralighting=True,timingpoint=False) jpp2 = JourneyPatternPoint(tdata,'SP1',forboarding=True,foralighting=True,timingpoint=False) self.assertEquals(jpp1,jpp2) def test_primary_key_constraints(self): - tdata = Timetable(datetime.date(2014,1,1)) - sa = StopArea(tdata,'SA1') + tdata = Timetable(datetime.date(2014,1,1),'Europe/Amsterdam') + pm = PhysicalMode(tdata,'BUS',name='Bus') + cm = CommercialMode(tdata,'BUS',name='Bus') + sa = StopArea(tdata,'SA1','Europe/Amsterdam') sa.name = 'SA1' - self.assertRaises(ValueError, StopArea,tdata,'SA1') + self.assertRaises(ValueError, StopArea,tdata,'SA1','Europe/Amsterdam') self.assertEquals(tdata.stop_areas['SA1'].name,'SA1') sp = StopPoint(tdata,'SP1','SA1') sp.name = 'SP1' self.assertRaises(ValueError, StopPoint,tdata,'SP1','SA1') self.assertEquals(tdata.stop_points['SP1'].name,'SP1') - - op = Operator(tdata,'OP1') + + op = Operator(tdata,'OP1','Europe/Amsterdam') op.name = 'OP1' - self.assertRaises(ValueError, Operator,tdata,'OP1') + self.assertRaises(ValueError, Operator,tdata,'OP1','Europe/Amsterdam') self.assertEquals(tdata.operators['OP1'].name,'OP1') - l = Line(tdata,'L1','OP1') + l = Line(tdata,'L1','OP1','BUS') l.name = 'L1' - self.assertRaises(ValueError, Line,tdata,'L1','OP1') + self.assertRaises(ValueError, Line,tdata,'L1','OP1','BUS') self.assertEquals(tdata.lines['L1'].name,'L1') r = Route(tdata,'R1','L1') @@ -45,47 +46,51 @@ def test_primary_key_constraints(self): self.assertRaises(ValueError, Route,tdata,'R1','L1') self.assertEquals(tdata.routes['R1'].name,'R1') - vj = VehicleJourney(tdata,'VJ1','R1') - self.assertRaises(ValueError, VehicleJourney,tdata,'VJ1','R1') + vj = VehicleJourney(tdata,'VJ1','R1','BUS') + self.assertRaises(ValueError, VehicleJourney,tdata,'VJ1','R1','BUS') self.assertEquals(tdata.vehicle_journeys['VJ1'].route.line.operator.name,'OP1') def test_foreign_key_constraints(self): - tdata = Timetable(datetime.date(2014,1,1)) - sa = StopArea(tdata,'SA1') + tdata = Timetable(datetime.date(2014,1,1),'Europe/Amsterdam') + pm = PhysicalMode(tdata,'BUS',name='Bus') + cm = CommercialMode(tdata,'BUS',name='Bus') + sa = StopArea(tdata,'SA1','Europe/Amsterdam') sa.name = 'SA1' sp = StopPoint(tdata,'SP1','SA1') self.assertRaises(ValueError, StopPoint,tdata,'SP1','SA2') self.assertEquals(tdata.stop_points['SP1'].stop_area.name,'SA1') self.assertEquals(sp.stop_area,sa) - op = Operator(tdata,'OP1') + op = Operator(tdata,'OP1','Europe/Amsterdam') op.name = 'OP1' - l = Line(tdata,'L1','OP1') - self.assertRaises(ValueError, Line,tdata,'L3','OP2') + l = Line(tdata,'L1','OP1','BUS') + self.assertRaises(ValueError, Line,tdata,'L3','OP2','BUS') self.assertEquals(tdata.lines['L1'].operator.name,'OP1') r = Route(tdata,'R1','L1') self.assertRaises(ValueError, Route,tdata,'R1','L1') self.assertEquals(tdata.routes['R1'].line.operator.name,'OP1') - vj = VehicleJourney(tdata,'VJ1','R1') + vj = VehicleJourney(tdata,'VJ1','R1','BUS') self.assertRaises(ValueError, Route,tdata,'R1','L1') self.assertEquals(tdata.vehicle_journeys['VJ1'].route.line.operator.name,'OP1') def test_integration(self): - tdata = Timetable(datetime.date(2014,1,1)) - sa = StopArea(tdata,'SA1',name='SA1') - sa = StopArea(tdata,'SA2',name='SA2') - sa = StopArea(tdata,'SA3',name='SA3') + tdata = Timetable(datetime.date(2014,1,1),'Europe/Amsterdam') + pm = PhysicalMode(tdata,'BUS',name='Bus') + cm = CommercialMode(tdata,'BUS',name='Bus') + sa = StopArea(tdata,'SA1','Europe/Amsterdam',name='SA1') + sa = StopArea(tdata,'SA2','Europe/Amsterdam',name='SA2') + sa = StopArea(tdata,'SA3','Europe/Amsterdam',name='SA3') sp = StopPoint(tdata,'SP1','SA1',name='SP1') sp = StopPoint(tdata,'SP2','SA2',name='SP1') sp = StopPoint(tdata,'SP3','SA3',name='SP1') conn = Connection(tdata,'SP1','SP2',120,type=2) conn = Connection(tdata,'SP2','SP1',120,type=2) - op = Operator(tdata,'OP1',name='Operator',url='http://www.example.com') - l = Line(tdata,'L1','OP1',name='Testline',code='T') + op = Operator(tdata,'OP1','Europe/Amsterdam',name='Operator',url='http://www.example.com') + l = Line(tdata,'L1','OP1','BUS',name='Testline',code='T') r = Route(tdata,'R1','L1',direction=1) - vj = VehicleJourney(tdata,'VJ1','R1') + vj = VehicleJourney(tdata,'VJ1','R1','BUS') self.assertRaises(ValueError, vj.add_stop,'SP1',-10,-10) #Expect exception on negative times vj.add_stop('SP1',900,900,forboarding=True,foralighting=True,timingpoint=True) self.assertRaises(ValueError, vj.add_stop,'SP2',600,600) #Expect exception on timetravel @@ -94,7 +99,7 @@ def test_integration(self): vj.add_stop('SP3',2400,2400,forboarding=True,foralighting=True,timingpoint=True) vj.finish() - vj = VehicleJourney(tdata,'VJ2','R1') + vj = VehicleJourney(tdata,'VJ2','R1','BUS') vj.setIsValidOn(datetime.date(2014,1,1)) vj.setIsValidOn(datetime.date(2014,1,2)) vj.setIsValidOn(datetime.date(2014,1,3)) @@ -109,7 +114,7 @@ def test_integration(self): self.assertEquals(1,len(tdata.timedemandgroups)) self.assertRaises(AttributeError,vj.add_stop,'SP3',2400,2400) #Expect that we can no longer modify journeys after finishing - vj = VehicleJourney(tdata,'VJ3','R1') + vj = VehicleJourney(tdata,'VJ3','R1','BUS') vj.add_stop('SP1',1000,1000,forboarding=True,foralighting=True,timingpoint=True) vj.add_stop('SP2',1900,2000,forboarding=True,foralighting=True,timingpoint=True) vj.add_stop('SP3',2500,2500,forboarding=True,foralighting=True,timingpoint=True) @@ -117,11 +122,90 @@ def test_integration(self): self.assertEquals(1,len(tdata.journey_patterns)) self.assertEquals(2,len(tdata.timedemandgroups)) - vj = VehicleJourney(tdata,'VJ4','R1') + vj = VehicleJourney(tdata,'VJ4','R1','BUS') vj.add_stop('SP1',1000,1000,forboarding=True,foralighting=True,timingpoint=True) vj.add_stop('SP2',1900,2000,forboarding=True,foralighting=True,timingpoint=True) vj.finish() self.assertEquals(2,len(tdata.journey_patterns)) self.assertEquals(3,len(tdata.timedemandgroups)) + + def test_utc_dst_off_to_on(self): + tdata = Timetable(datetime.date(2015,3,28),'Europe/Amsterdam') + pm = PhysicalMode(tdata,'BUS',name='Bus') + cm = CommercialMode(tdata,'BUS',name='Bus') + sa = StopArea(tdata,'SA1','Europe/Amsterdam',name='SA1') + sa = StopArea(tdata,'SA2','Europe/Amsterdam',name='SA2') + sa = StopArea(tdata,'SA3','Europe/Amsterdam',name='SA3') + sp = StopPoint(tdata,'SP1','SA1',name='SP1') + sp = StopPoint(tdata,'SP2','SA2',name='SP1') + sp = StopPoint(tdata,'SP3','SA3',name='SP1') + conn = Connection(tdata,'SP1','SP2',120,type=2) + conn = Connection(tdata,'SP2','SP1',120,type=2) + op = Operator(tdata,'OP1','Europe/Amsterdam',name='Operator',url='http://www.example.com') + l = Line(tdata,'L1','OP1','BUS',name='Testline',code='T') + r = Route(tdata,'R1','L1',direction=1) + vj = VehicleJourney(tdata,'VJ1','R1','BUS') + vj.setIsValidOn(datetime.date(2015,3,28)) + vj.setIsValidOn(datetime.date(2015,3,29)) + vj.add_stop('SP1',900,900,forboarding=True,foralighting=True,timingpoint=True) + vj.add_stop('SP2',1800,1860,forboarding=True,foralighting=True,timingpoint=True) + vj.add_stop('SP3',2400,2400,forboarding=True,foralighting=True,timingpoint=True) + vj.finish() + + #Check split on utc offset + self.assertEquals(1,len(tdata.vehicle_journeys)) + self.assertEquals(2,len(tdata.vehicle_journeys_utc)) + self.assertEquals(3600,tdata.vehicle_journeys_utc[('VJ1',3600)].utc_offset) + self.assertEquals(7200,tdata.vehicle_journeys_utc[('VJ1',7200)].utc_offset) + + #Correct departuretimes + self.assertEquals(900,tdata.vehicle_journeys['VJ1'].departure_time) + self.assertEquals(-2700,tdata.vehicle_journeys_utc[('VJ1',3600)].departure_time) + self.assertEquals(-6300,tdata.vehicle_journeys_utc[('VJ1',7200)].departure_time) + + #Correct validity_patterns + self.assertEquals(set([0,1]),tdata.vehicle_journeys['VJ1'].validity_pattern) + self.assertEquals(set([0]),tdata.vehicle_journeys_utc[('VJ1',3600)].validity_pattern) + self.assertEquals(set([1]),tdata.vehicle_journeys_utc[('VJ1',7200)].validity_pattern) + + def test_utc_dst_on_to_off(self): + tdata = Timetable(datetime.date(2015,10,24),'Europe/Amsterdam') + pm = PhysicalMode(tdata,'BUS',name='Bus') + cm = CommercialMode(tdata,'BUS',name='Bus') + sa = StopArea(tdata,'SA1','Europe/Amsterdam',name='SA1') + sa = StopArea(tdata,'SA2','Europe/Amsterdam',name='SA2') + sa = StopArea(tdata,'SA3','Europe/Amsterdam',name='SA3') + sp = StopPoint(tdata,'SP1','SA1',name='SP1') + sp = StopPoint(tdata,'SP2','SA2',name='SP1') + sp = StopPoint(tdata,'SP3','SA3',name='SP1') + conn = Connection(tdata,'SP1','SP2',120,type=2) + conn = Connection(tdata,'SP2','SP1',120,type=2) + op = Operator(tdata,'OP1','Europe/Amsterdam',name='Operator',url='http://www.example.com') + l = Line(tdata,'L1','OP1','BUS',name='Testline',code='T') + r = Route(tdata,'R1','L1',direction=1) + vj = VehicleJourney(tdata,'VJ1','R1','BUS') + vj.setIsValidOn(datetime.date(2015,10,24)) + vj.setIsValidOn(datetime.date(2015,10,25)) + vj.add_stop('SP1',900,900,forboarding=True,foralighting=True,timingpoint=True) + vj.add_stop('SP2',1800,1860,forboarding=True,foralighting=True,timingpoint=True) + vj.add_stop('SP3',2400,2400,forboarding=True,foralighting=True,timingpoint=True) + vj.finish() + + #Check split on utc offset + self.assertEquals(1,len(tdata.vehicle_journeys)) + self.assertEquals(2,len(tdata.vehicle_journeys_utc)) + self.assertEquals(3600,tdata.vehicle_journeys_utc[('VJ1',3600)].utc_offset) + self.assertEquals(7200,tdata.vehicle_journeys_utc[('VJ1',7200)].utc_offset) + + #Correct departuretimes + self.assertEquals(900,tdata.vehicle_journeys['VJ1'].departure_time) + self.assertEquals(-2700,tdata.vehicle_journeys_utc[('VJ1',3600)].departure_time) + self.assertEquals(-6300,tdata.vehicle_journeys_utc[('VJ1',7200)].departure_time) + + #Correct validity_patterns + self.assertEquals(set([0,1]),tdata.vehicle_journeys['VJ1'].validity_pattern) + self.assertEquals(set([0]),tdata.vehicle_journeys_utc[('VJ1',7200)].validity_pattern) + self.assertEquals(set([1]),tdata.vehicle_journeys_utc[('VJ1',3600)].validity_pattern) + if __name__ == '__main__': unittest.main() diff --git a/rrtimetable/rrtimetable/tests/util_tests.py b/rrtimetable/rrtimetable/tests/util_tests.py new file mode 100644 index 0000000..bcd8a59 --- /dev/null +++ b/rrtimetable/rrtimetable/tests/util_tests.py @@ -0,0 +1,25 @@ +import unittest +from model.utils import * +import datetime + +class TestSequenceFunctions(unittest.TestCase): + + def test_utc(self): + utc_offset,utc_deptime = utc_time_gtfs(datetime.date(2015,2,23),3600,'Europe/Amsterdam') + self.assertEquals(utc_offset,3600) + self.assertEquals(utc_deptime,0) + + utc_offset,utc_deptime = utc_time_gtfs(datetime.date(2015,3,28),3600,'Europe/Amsterdam') + self.assertEquals(utc_offset,3600) + self.assertEquals(utc_deptime,0) + + utc_offset,utc_deptime = utc_time_gtfs(datetime.date(2015,3,29),3600,'Europe/Amsterdam') + self.assertEquals(utc_offset,7200) + self.assertEquals(utc_deptime,-3600) + + utc_offset,utc_deptime = utc_time_gtfs(datetime.date(2015,3,29),10*60*60,'Europe/Amsterdam') + self.assertEquals(utc_offset,7200) + self.assertEquals(utc_deptime,8*60*60) + +if __name__ == '__main__': + unittest.main() diff --git a/set.c b/set.c new file mode 100644 index 0000000..4b5f15a --- /dev/null +++ b/set.c @@ -0,0 +1,110 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and at + * https://github.com/bliksemlabs/rrrr/ + */ + +#include "config.h" +#include "rrrr_types.h" +#include "set.h" + +#if RRRR_MAX_BANNED_STOP_POINTS > 0 || RRRR_BAX_BANNED_STOPS_HARD > 0 +void set_add_sp (spidx_t *set, + uint8_t *length, uint8_t max_length, + spidx_t value) { + uint8_t i; + + if (*length >= max_length) return; + + for (i = 0; i < *length; ++i) { + if (set[i] == value) return; + } + + set[*length] = value; + (*length)++; +} + +bool set_in_sp (const spidx_t *set, uint8_t length, spidx_t value) { + uint8_t i = length; + if (i == 0) return false; + do { + i--; + if (set[i] == value) return true; + } while (i); + return false; +} +#endif + +#if RRRR_MAX_BANNED_JOURNEY_PATTERNS > 0 +void set_add_jp (jpidx_t *set, + uint8_t *length, uint8_t max_length, + jpidx_t value) { + uint8_t i; + + if (*length >= max_length) return; + + for (i = 0; i < *length; ++i) { + if (set[i] == value) return; + } + + set[*length] = value; + (*length)++; +} +#endif + +#if RRRR_MAX_BANNED_VEHICLE_JOURNEYS > 0 +void set_add_vj (jpidx_t *set1, jp_vjoffset_t *set2, + uint8_t *length, uint8_t max_length, + jpidx_t value1, jp_vjoffset_t value2) { + uint8_t i; + + if (*length >= max_length) return; + + for (i = 0; i < *length; ++i) { + if (set1[i] == value1 && + set2[i] == value2) return; + } + + set1[*length] = value1; + set2[*length] = value2; + (*length)++; +} + +bool set_in_vj (const jpidx_t *set1, const jp_vjoffset_t *set2, uint8_t length, + jpidx_t value1, jp_vjoffset_t value2) { + uint8_t i = length; + if (i == 0) return false; + do { + i--; + if (set1[i] == value1 && + set2[i] == value2) return true; + } while (i); + return false; +} +#endif + +#if RRRR_MAX_FILTERED_OPERATORS > 0 || RRRR_MAX_BANNED_OPERATORS > 0 +bool set_in_uint8 (const uint8_t *set, uint8_t length, uint8_t value) { + uint8_t i = length; + + while (i) { + i--; + if (set[i] == value) return true; + } + return false; +} + +void set_add_uint8 (uint8_t *set, + uint8_t *length, uint8_t max_length, + uint8_t value) { + uint8_t i; + + if (*length >= max_length) return; + + for (i = 0; i < *length; ++i) { + if (set[i] == value) return; + } + + set[*length] = value; + (*length)++; +} +#endif diff --git a/set.h b/set.h new file mode 100644 index 0000000..fef93d7 --- /dev/null +++ b/set.h @@ -0,0 +1,26 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and at + * https://github.com/bliksemlabs/rrrr/ + */ + +#include "config.h" +#include "rrrr_types.h" + +#if RRRR_MAX_BANNED_STOP_POINTS > 0 || RRRR_BAX_BANNED_STOPS_HARD > 0 +void set_add_sp (spidx_t *set, uint8_t *length, uint8_t max_length, spidx_t value); +bool set_in_sp (const spidx_t *set, uint8_t length, spidx_t value); +#endif + +#if RRRR_MAX_BANNED_JOURNEY_PATTERNS > 0 +void set_add_jp (jpidx_t *set, uint8_t *length, uint8_t max_length, jpidx_t value); +#endif + +#if RRRR_MAX_BANNED_VEHICLE_JOURNEYS > 0 +void set_add_vj (jpidx_t *set1, jp_vjoffset_t *set2, uint8_t *length, uint8_t max_length, jpidx_t value1, jp_vjoffset_t value2); +bool set_in_vj (const jpidx_t *set1, const jp_vjoffset_t *set2, uint8_t length, jpidx_t value1, jp_vjoffset_t value2); +#endif + +#if RRRR_MAX_FILTERED_OPERATORS > 0 +void set_add_uint8 (uint8_t *set, uint8_t *length, uint8_t max_length, uint8_t value); +bool set_in_uint8 (const uint8_t *set, uint8_t length, uint8_t value); +#endif diff --git a/street_network.c b/street_network.c new file mode 100644 index 0000000..559cfc3 --- /dev/null +++ b/street_network.c @@ -0,0 +1,63 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and at + * https://github.com/bliksemlabs/rrrr/ + */ + +#include "street_network.h" + +void +street_network_init (street_network_t *sn) { + sn->n_points = 0; +} + +rtime_t street_network_duration(spidx_t sp_index, street_network_t *sn){ + spidx_t i_origin = sn->n_points; + while (i_origin) { + --i_origin; + if (sn->stop_points[i_origin] == sp_index) { + return sn->durations[i_origin]; + } + } + return UNREACHED; +} + +bool +street_network_mark_duration_to_stop_point(street_network_t *sn, + spidx_t sp_index, + rtime_t duration) { + uint32_t i = sn->n_points; + + /* A stop point already exists in the list, only duration is updated. */ + while (i) { + --i; + if (sn->stop_points[i] == sp_index) { + sn->durations[i] = duration; + return true; + } + } + + /* An additional duration should be added, is there enough room? */ + if (sn->n_points >= RRRR_MAX_ENTRY_EXIT_POINTS) { + return false; + } + + sn->stop_points[sn->n_points] = sp_index; + sn->durations[sn->n_points] = duration; + ++sn->n_points; + + #ifdef RRRR_DEBUG + fprintf(stderr, "STREET sp_index: %d, duration %d seconds\n", + sp_index, duration); + #endif + + return true; +} + +void +street_network_null_duration(street_network_t *sn) { + uint32_t i = sn->n_points; + while (i) { + --i; + sn->durations[sn->n_points] = 0; + } +} diff --git a/street_network.h b/street_network.h new file mode 100644 index 0000000..7c5fcae --- /dev/null +++ b/street_network.h @@ -0,0 +1,18 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and at + * https://github.com/bliksemlabs/rrrr/ + */ + +#ifndef _STREET_NETWORK_H +#define _STREET_NETWORK_H + +#include "rrrr_types.h" +#include "tdata.h" + +void street_network_init (street_network_t *sn); +bool street_network_mark_duration_to_stop_point(street_network_t *sn, spidx_t sp_index, rtime_t duration); +rtime_t street_network_duration(spidx_t sp_index, street_network_t *sn); +void street_network_null_duration(street_network_t *sn); + +bool street_network_stoppoint_durations(latlon_t *latlon, float walk_speed, uint16_t max_walk_distance, tdata_t *tdata, street_network_t *sn); +#endif diff --git a/string_pool.c b/string_pool.c new file mode 100644 index 0000000..262edb9 --- /dev/null +++ b/string_pool.c @@ -0,0 +1,32 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and at + * https://github.com/bliksemlabs/rrrr/ + */ + +#include "config.h" + +#ifdef RRRR_TDATA_IO_DYNAMIC + +#include "string_pool.h" +#include "string.h" + +uint32_t string_pool_append(char *pool, uint32_t *n_pool, radixtree_t *r, const char *str) { + uint32_t location = radixtree_find_exact (r, str); + + if (location == RADIXTREE_NONE) { + size_t n = strlen(str); + + /* TODO: check if n_pool + n < max */ + strncpy(&pool[*n_pool], str, n); + pool[*n_pool+n] = '\0'; + location = *n_pool; + *n_pool += n + 1; + + radixtree_insert(r, str, location); + } + + return location; +} +#else +void string_pool_append_not_available(); +#endif /* RRRR_TDATA_IO_DYNAMIC */ diff --git a/string_pool.h b/string_pool.h new file mode 100644 index 0000000..9f4e237 --- /dev/null +++ b/string_pool.h @@ -0,0 +1,15 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and at + * https://github.com/bliksemlabs/rrrr/ + */ + +#include "config.h" + +#ifdef RRRR_TDATA_IO_DYNAMIC + +#include "rrrr_types.h" +#include "radixtree.h" + +uint32_t string_pool_append(char *pool, uint32_t *n_pool, radixtree_t *r, const char *str); + +#endif /* RRRR_TDATA_IO_DYNAMIC */ diff --git a/stubs.c b/stubs.c deleted file mode 100644 index 2744024..0000000 --- a/stubs.c +++ /dev/null @@ -1,30 +0,0 @@ -#include "stubs.h" -#include -#include -#include - -bool tdata_load(tdata_t *tdata, char* filename) { return true; } -void tdata_close(tdata_t *tdata) {} - -void router_request_initialize(router_request_t *router) {} -void router_request_dump(router_request_t *req, tdata_t *tdata) {} -void router_request_from_epoch(router_request_t *req, tdata_t *tdata, time_t epochtime) {} -bool router_request_reverse(router_t *router, router_request_t *req) { return true; } - -bool router_setup(router_t* router, tdata_t* td) { return true; } -void router_reset(router_t *router) {} -void router_teardown(router_t *router) {} -bool router_route(router_t *router, router_request_t *req) { return true; } - -/* return: number of characters written */ -uint32_t router_result_dump(router_t *router, router_request_t *req, char *buf, uint32_t buflen) { return 0; } - -char *btimetext(rtime_t rt, char *buf) { return ""; } - -rtime_t epoch_to_rtime (time_t epochtime, struct tm *localtm) { return 0; } - -time_t strtoepoch (char *time) { return 0; } - -bool strtolatlon (char *latlon, latlon_t *result) { return true; } - -void router_request_randomize (router_request_t *req, tdata_t *tdata) { } diff --git a/stubs.h b/stubs.h deleted file mode 100644 index fefac0f..0000000 --- a/stubs.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef _STUBS_H -#define _STUBS_H - -#include -#include -#include -#include - -#include "rrrr_types.h" - -#if defined (HAVE_LOCALTIME_R) - #define rrrr_localtime_r(a, b) localtime_r(a, b) -#elif defined (HAVE_LOCALTIME_S) - #define rrrr_localtime_r(a, b) localtime_s(b, a) -#else - #define rrrr_localtime_r(a, b) { \ - struct tm *tmpstm = localtime (a); \ - memcpy (b, tmpstm, sizeof(struct tm));\ -} -#endif - -#include "tdata.h" -#include "router_request.h" - -bool tdata_load_mmap(tdata_t *tdata, char* filename); -void tdata_close_mmap(tdata_t *tdata); - -/* return: number of characters written */ -uint32_t router_result_dump(router_t *router, router_request_t *req, char *buf, uint32_t buflen); - -void memset32(uint32_t *s, uint32_t u, size_t n); -char * strcasestr(const char *s, const char *find); -uint32_t rrrrandom(uint32_t limit); - -char *tdata_stop_name_for_index(tdata_t *td, uint32_t stop_index); -char *btimetext(rtime_t t, char *buf); - -void router_request_randomize (router_request_t *req, tdata_t *tdata); - -#ifdef RRRR_FEATURE_AGENCY_FILTER -uint32_t rrrrandom_stop_by_agency(tdata_t *tdata, uint16_t agency_index); -#endif - -#endif - diff --git a/tdata.c b/tdata.c index caaeac1..18234dd 100644 --- a/tdata.c +++ b/tdata.c @@ -1,15 +1,14 @@ -/* Copyright 2013 Bliksem Labs. +/* Copyright 2013-2015 Bliksem Labs B.V. * See the LICENSE file at the top-level directory of this distribution and at * https://github.com/bliksemlabs/rrrr/ */ -/* tdata.c : handles memory mapped data file containing transit timetable etc. */ +/* tdata.c : handles data-structures containing transit timetable etc. */ /* top, make sure it works alone */ #include "tdata.h" -#include "tdata_io_v3.h" +#include "tdata_io_v4.h" #include "tdata_validation.h" -#include "rrrr_types.h" #include "util.h" #ifdef RRRR_FEATURE_REALTIME_ALERTS @@ -18,177 +17,329 @@ #ifdef RRRR_FEATURE_REALTIME_EXPANDED #include "tdata_realtime_expanded.h" #endif - -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include "config.h" -#include "bitset.h" +const char *tdata_timezone(const tdata_t *td){ + return td->string_pool + td->timezone; +} + +const char *tdata_line_id_for_journey_pattern(const tdata_t *td, jpidx_t jp_index) { + routeidx_t route_index; + if (jp_index == JP_NONE) return "NONE"; + route_index = td->journey_patterns[jp_index].route_index; + return tdata_line_id_for_index(td,td->line_for_route[route_index]); +} + +const char *tdata_stop_point_id_for_index(const tdata_t *td, spidx_t sp_index) { + return td->string_pool + td->stop_point_ids[sp_index]; +} + +uint8_t *tdata_stop_point_attributes_for_index(const tdata_t *td, spidx_t sp_index) { + return td->stop_point_attributes + sp_index; +} + +const char *tdata_vehicle_journey_id_for_index(const tdata_t *td, vjidx_t vj_index) { + return td->string_pool + td->vj_ids[vj_index]; +} + +const char *tdata_vehicle_journey_id_for_jp_vj_offset(const tdata_t *td, jpidx_t jp_index, jp_vjoffset_t vj_offset) { + return tdata_vehicle_journey_id_for_index(td,td->journey_patterns[jp_index].vj_index + vj_offset); +} + +int32_t tdata_utc_offset_for_vj_index(const tdata_t *td, vjidx_t vj_index) { + return td->utc_offset-td->vj_time_offsets[vj_index]*15*60; +} + +int32_t tdata_stoptime_utc_for_index(const tdata_t *td, jpidx_t jp_index, jppidx_t jpp_offset, jp_vjoffset_t vj_offset, bool arrival){ + return tdata_stoptime_for_index(td,jp_index,jpp_offset,vj_offset,arrival) - + SIGNED_SEC_TO_RTIME(tdata_utc_offset_for_jp_vj_offset(td,jp_index,vj_offset)); +} + +rtime_t tdata_stoptime_for_index(const tdata_t *td, jpidx_t jp_index, jppidx_t jpp_offset, jp_vjoffset_t vj_offset, bool arrival){ + vjidx_t vj_index = td->journey_patterns[jp_index].vj_index + vj_offset; + vehicle_journey_t vj = td->vjs[vj_index]; + return vj.begin_time + (arrival ? (td->stop_times[vj.stop_times_offset + jpp_offset]).arrival : + td->stop_times[vj.stop_times_offset + jpp_offset].departure); +} + +rtime_t tdata_stoptime_local_for_index(const tdata_t *td, jpidx_t jp_index, jppidx_t jpp_offset, jp_vjoffset_t vj_offset, bool arrival){ + return (rtime_t) (tdata_stoptime_for_index(td,jp_index,jpp_offset,vj_offset,arrival) - + SIGNED_SEC_TO_RTIME(tdata_time_offset_for_jp_vj_offset(td, jp_index, vj_offset))); +} + +int32_t tdata_utc_offset_for_jp_vj_offset(const tdata_t *td, jpidx_t jp_index, jp_vjoffset_t vj_offset){ + return tdata_utc_offset_for_vj_index(td, td->journey_patterns[jp_index].vj_index + vj_offset); +} + +int32_t tdata_time_offset_for_vj_index(const tdata_t *td, vjidx_t vj_index) { + return td->vj_time_offsets[vj_index]*15*60; +} + +int32_t tdata_time_offset_for_jp_vj_offset(const tdata_t *td, jpidx_t jp_index, jp_vjoffset_t vj_offset){ + return tdata_time_offset_for_vj_index(td, td->journey_patterns[jp_index].vj_index + vj_offset); +} + +const char *tdata_operator_id_for_index(const tdata_t *td, opidx_t operator_index) { + return td->string_pool + (td->operator_ids[operator_index]); +} + +const char *tdata_operator_name_for_index(const tdata_t *td, opidx_t operator_index) { + return td->string_pool + (td->operator_names[operator_index]); +} -const char *tdata_line_id_for_journey_pattern(tdata_t *td, uint32_t jp_index) { - if (jp_index == NONE) return "NONE"; - return td->line_ids + (td->line_ids_width * jp_index); +const char *tdata_operator_url_for_index(const tdata_t *td, opidx_t operator_index) { + return td->string_pool + (td->operator_urls[operator_index]); } -const char *tdata_stop_id_for_index(tdata_t *td, spidx_t stop_index) { - return td->stop_ids + (td->stop_ids_width * stop_index); +const char *tdata_line_code_for_index(const tdata_t *td, lineidx_t line_index) { + return td->string_pool + td->line_codes[line_index]; } -uint8_t *tdata_stop_attributes_for_index(tdata_t *td, spidx_t stop_index) { - return td->stop_attributes + stop_index; +const char *tdata_line_color_for_index(const tdata_t *td, lineidx_t line_index) { + return td->string_pool + td->line_colors[line_index]; } -const char *tdata_vehicle_journey_id_for_index(tdata_t *td, uint32_t vj_index) { - return td->vj_ids + (td->vj_ids_width * vj_index); +const char *tdata_line_color_text_for_index(const tdata_t *td, lineidx_t line_index) { + return td->string_pool + td->line_colors_text[line_index]; } -const char *tdata_vehicle_journey_id_for_jp_vj_index(tdata_t *td, uint32_t jp_index, uint32_t vj_index) { - return td->vj_ids + (td->vj_ids_width * (td->journey_patterns[jp_index].vj_ids_offset + vj_index)); +const char *tdata_line_name_for_index(const tdata_t *td, lineidx_t line_index) { + if (td->line_names == NULL) return NULL; + return td->string_pool + td->line_names[line_index]; } -const char *tdata_agency_id_for_index(tdata_t *td, uint32_t agency_index) { - return td->agency_ids + (td->agency_ids_width * agency_index); +const char *tdata_line_id_for_index(const tdata_t *td, lineidx_t line_index) { + if (td->line_names == NULL) return NULL; + return td->string_pool + td->line_ids[line_index]; } -const char *tdata_agency_name_for_index(tdata_t *td, uint32_t agency_index) { - return td->agency_names + (td->agency_names_width * agency_index); +const char *tdata_name_for_commercial_mode_index(const tdata_t *td, uint32_t commercial_mode_index) { + return td->string_pool + (td->commercial_mode_names[commercial_mode_index]); } -const char *tdata_agency_url_for_index(tdata_t *td, uint32_t agency_index) { - return td->agency_urls + (td->agency_urls_width * agency_index); +const char *tdata_id_for_commercial_mode_index(const tdata_t *td, uint32_t commercial_mode_index) { + return td->string_pool + (td->commercial_mode_ids[commercial_mode_index]); } -const char *tdata_headsign_for_offset(tdata_t *td, uint32_t headsign_offset) { - return td->headsigns + headsign_offset; +const char *tdata_name_for_physical_mode_index(const tdata_t *td, uint32_t physical_mode_index) { + return td->string_pool + (td->physical_mode_names[physical_mode_index]); } -const char *tdata_line_code_for_index(tdata_t *td, uint32_t line_code_index) { - return td->line_codes + (td->line_codes_width * line_code_index); +const char *tdata_id_for_physical_mode_index(const tdata_t *td, uint32_t physical_mode_index) { + return td->string_pool + (td->physical_mode_ids[physical_mode_index]); } -const char *tdata_productcategory_for_index(tdata_t *td, uint32_t productcategory_index) { - return td->productcategories + (td->productcategories_width * productcategory_index); +latlon_t *tdata_stop_point_coord_for_index(const tdata_t *td, spidx_t sp_index){ + return &td->stop_point_coords[sp_index]; } -const char *tdata_platformcode_for_index(tdata_t *td, spidx_t stop_index) { - switch (stop_index) { +latlon_t *tdata_stop_area_coord_for_index(const tdata_t *td, spidx_t sa_index){ + return &td->stop_area_coords[sa_index]; +} + +const char *tdata_platformcode_for_index(const tdata_t *td, spidx_t sp_index) { + switch (sp_index) { case STOP_NONE : return NULL; case ONBOARD : return NULL; default : - return td->platformcodes + (td->platformcodes_width * stop_index); + return td->string_pool + td->platformcodes[sp_index]; } } -spidx_t tdata_stopidx_by_stop_name(tdata_t *td, char* stop_name, spidx_t stop_index_offset) { - spidx_t stop_index; - for (stop_index = stop_index_offset; - stop_index < td->n_stops; - ++stop_index) { - if (strcasestr(td->stop_names + td->stop_nameidx[stop_index], - stop_name)) { - return stop_index; +spidx_t tdata_stop_pointidx_by_stop_point_name(const tdata_t *td, char *stop_point_name, spidx_t sp_index_offset) { + spidx_t sp_index; + for (sp_index = sp_index_offset; + sp_index < td->n_stop_points; + ++sp_index) { + if (strcasestr(td->string_pool + td->stop_point_nameidx[sp_index], + stop_point_name)) { + return sp_index; } } return STOP_NONE; } -spidx_t tdata_stopidx_by_stop_id(tdata_t *td, char* stop_id, spidx_t stop_index_offset) { - spidx_t stop_index; - for (stop_index = stop_index_offset; - stop_index < td->n_stops; - ++stop_index) { - if (strcasestr(td->stop_ids + (td->stop_ids_width * stop_index), - stop_id)) { - return stop_index; +spidx_t tdata_stop_areaidx_for_index(const tdata_t *td, spidx_t sp_index) { + return td->stop_area_for_stop_point[sp_index]; +} + +spidx_t tdata_stop_areaidx_by_stop_area_name(const tdata_t *td, char *stop_area_name, spidx_t sa_index_offset) { + spidx_t sa_index; + for (sa_index = sa_index_offset; + sa_index < td->n_stop_areas; + ++sa_index) { + if (strcasestr(td->string_pool + td->stop_area_nameidx[sa_index], + stop_area_name)) { + return sa_index; } } return STOP_NONE; } -#define tdata_stopidx_by_stop_id(td, stop_id) tdata_stopidx_by_stop_id(td, stop_id, 0) +spidx_t tdata_stop_areaidx_by_stop_area_id(const tdata_t *td, char *stop_area_name, spidx_t sa_index_offset) { + spidx_t sa_index; + for (sa_index = sa_index_offset; + sa_index < td->n_stop_areas; + ++sa_index) { + if (!strcmp(td->string_pool + td->stop_area_ids[sa_index], + stop_area_name)) { + return sa_index; + } + } + return STOP_NONE; +} -uint32_t tdata_journey_pattern_idx_by_line_id(tdata_t *td, char *line_id, uint32_t jp_index_offset) { - uint32_t jp_index; +spidx_t tdata_stop_pointidx_by_stop_point_id(const tdata_t *td, char *stop_point_id, spidx_t sp_index_offset) { + spidx_t sp_index; + for (sp_index = sp_index_offset; + sp_index < td->n_stop_points; + ++sp_index) { + if (!strcmp(tdata_stop_point_id_for_index(td, sp_index), + stop_point_id)) { + return sp_index; + } + } + return STOP_NONE; +} + +jpidx_t tdata_journey_pattern_idx_by_line_id(const tdata_t *td, char *line_id, jpidx_t jp_index_offset) { + jpidx_t jp_index; for (jp_index = jp_index_offset; jp_index < td->n_journey_patterns; ++jp_index) { - if (strcasestr(td->line_ids + (td->line_ids_width * jp_index), + if (strcasestr(tdata_line_id_for_journey_pattern(td, jp_index), line_id)) { return jp_index; } } - return NONE; + return JP_NONE; } -#define tdata_journey_pattern_idx_by_line_id(td, line_id) tdata_journey_pattern_idx_by_line_id(td, jp_index, 0) - -const char *tdata_vehicle_journey_ids_in_journey_pattern(tdata_t *td, uint32_t jp_index) { +calendar_t *tdata_vj_masks_for_journey_pattern(const tdata_t *td, jpidx_t jp_index) { journey_pattern_t *jp = &(td->journey_patterns[jp_index]); - uint32_t char_offset = jp->vj_ids_offset * td->vj_ids_width; - return td->vj_ids + char_offset; + return td->vj_active + jp->vj_index; } -calendar_t *tdata_vj_masks_for_journey_pattern(tdata_t *td, uint32_t jp_index) { - journey_pattern_t *jp = &(td->journey_patterns[jp_index]); - return td->vj_active + jp->vj_ids_offset; +const char *tdata_headsign_for_journey_pattern(const tdata_t *td, jpidx_t jp_index) { + if (jp_index == JP_NONE) return "NONE"; + return td->string_pool + ((td->journey_pattern_point_headsigns)[(td->journey_patterns)[jp_index].journey_pattern_point_offset]); } -const char *tdata_headsign_for_journey_pattern(tdata_t *td, uint32_t jp_index) { - if (jp_index == NONE) return "NONE"; - return td->headsigns + (td->journey_patterns)[jp_index].headsign_offset; +const char *tdata_headsign_for_journey_pattern_point(const tdata_t *td, jpidx_t jp_index, jppidx_t jpp_offset) { + if (jp_index == JP_NONE) return "NONE"; + return td->string_pool + ((td->journey_pattern_point_headsigns)[(td->journey_patterns)[jp_index].journey_pattern_point_offset + jpp_offset]); } -const char *tdata_line_code_for_journey_pattern(tdata_t *td, uint32_t jp_index) { - if (jp_index == NONE) return "NONE"; - return td->line_codes + (td->line_codes_width * (td->journey_patterns)[jp_index].line_code_index); +const char *tdata_line_code_for_journey_pattern(const tdata_t *td, jpidx_t jp_index) { + routeidx_t route_index; + if (jp_index == JP_NONE) return "NONE"; + route_index = (td->journey_patterns)[jp_index].route_index; + return tdata_line_code_for_index(td, td->line_for_route[route_index]); } -const char *tdata_productcategory_for_journey_pattern(tdata_t *td, uint32_t jp_index) { - if (jp_index == NONE) return "NONE"; - return td->productcategories + (td->productcategories_width * (td->journey_patterns)[jp_index].productcategory_index); +const char *tdata_line_color_for_journey_pattern(const tdata_t *td, jpidx_t jp_index) { + routeidx_t route_index; + if (jp_index == JP_NONE) return "NONE"; + route_index = (td->journey_patterns)[jp_index].route_index; + return tdata_line_color_for_index(td, td->line_for_route[route_index]); } -uint32_t tdata_agencyidx_by_agency_name(tdata_t *td, char* agency_name, uint32_t agency_index_offset) { - uint32_t agency_index; - for (agency_index = agency_index_offset; - agency_index < td->n_agency_names; - ++agency_index) { - if (strcasestr(td->agency_names + (td->agency_names_width * agency_index), - agency_name)) { - return agency_index; +const char *tdata_line_color_text_for_journey_pattern(const tdata_t *td, jpidx_t jp_index) { + routeidx_t route_index; + if (jp_index == JP_NONE) return "NONE"; + route_index = (td->journey_patterns)[jp_index].route_index; + return tdata_line_color_text_for_index(td, td->line_for_route[route_index]); +} + +const char *tdata_line_name_for_journey_pattern(const tdata_t *td, jpidx_t jp_index) { + routeidx_t route_index; + if (jp_index == JP_NONE) return "NONE"; + route_index = (td->journey_patterns)[jp_index].route_index; + return tdata_line_name_for_index(td, td->line_for_route[route_index]); +} + +const char *tdata_commercial_mode_name_for_journey_pattern(const tdata_t *td, jpidx_t jp_index) { + if (jp_index == JP_NONE) return "NONE"; + return tdata_name_for_commercial_mode_index(td,(td->commercial_mode_for_jp)[jp_index]); +} + +const char *tdata_commercial_mode_id_for_journey_pattern(const tdata_t *td, jpidx_t jp_index) { + if (jp_index == JP_NONE) return "NONE"; + return tdata_id_for_commercial_mode_index(td,(td->commercial_mode_for_jp)[jp_index]); +} + +const char *tdata_physical_mode_name_for_journey_pattern(const tdata_t *td, jpidx_t jp_index) { + routeidx_t route_index; + lineidx_t line_index; + if (jp_index == JP_NONE) return "NONE"; + route_index = (td->journey_patterns)[jp_index].route_index; + line_index = (td->line_for_route)[route_index]; + return tdata_name_for_physical_mode_index(td,(td->physical_mode_for_line)[line_index]); +} + +const char *tdata_physical_mode_id_for_journey_pattern(const tdata_t *td, jpidx_t jp_index) { + routeidx_t route_index; + lineidx_t line_index; + if (jp_index == JP_NONE) return "NONE"; + route_index = (td->journey_patterns)[jp_index].route_index; + line_index = (td->line_for_route)[route_index]; + return tdata_id_for_physical_mode_index(td,(td->physical_mode_for_line)[line_index]); +} + +opidx_t tdata_operator_idx_by_operator_name(const tdata_t *td, const char *operator_name, + opidx_t operator_index_offset) { + opidx_t operator_index; + for (operator_index = operator_index_offset; + operator_index < td->n_operator_names; + ++operator_index) { + if (strcasestr(tdata_operator_name_for_index(td, operator_index), + operator_name)) { + return operator_index; } } - return NONE; + return OP_NONE; +} + +opidx_t tdata_operator_idx_for_journey_pattern(const tdata_t *td, jpidx_t jp_index) { + routeidx_t route_index; + lineidx_t line_index; + if (jp_index == JP_NONE) return OP_NONE; + route_index = (td->journey_patterns)[jp_index].route_index; + line_index = (td->line_for_route)[route_index]; + return td->operator_for_line[line_index]; } -const char *tdata_agency_id_for_journey_pattern(tdata_t *td, uint32_t jp_index) { - if (jp_index == NONE) return "NONE"; - return td->agency_ids + (td->agency_ids_width * (td->journey_patterns)[jp_index].agency_index); +const char *tdata_operator_id_for_journey_pattern(const tdata_t *td, jpidx_t jp_index) { + routeidx_t route_index; + lineidx_t line_index; + if (jp_index == JP_NONE) return "NONE"; + route_index = (td->journey_patterns)[jp_index].route_index; + line_index = (td->line_for_route)[route_index]; + return tdata_operator_id_for_index(td, td->operator_for_line[line_index]); } -const char *tdata_agency_name_for_journey_pattern(tdata_t *td, uint32_t jp_index) { - if (jp_index == NONE) return "NONE"; - return td->agency_names + (td->agency_names_width * (td->journey_patterns)[jp_index].agency_index); +const char *tdata_operator_name_for_journey_pattern(const tdata_t *td, jpidx_t jp_index) { + routeidx_t route_index; + lineidx_t line_index; + if (jp_index == JP_NONE) return "NONE"; + route_index = (td->journey_patterns)[jp_index].route_index; + line_index = (td->line_for_route)[route_index]; + return tdata_operator_name_for_index(td, td->operator_for_line[line_index]); } -const char *tdata_agency_url_for_journey_pattern(tdata_t *td, uint32_t jp_index) { - if (jp_index == NONE) return "NONE"; - return td->agency_urls + (td->agency_urls_width * (td->journey_patterns)[jp_index].agency_index); +const char *tdata_operator_url_for_journey_pattern(const tdata_t *td, jpidx_t jp_index) { + routeidx_t route_index; + lineidx_t line_index; + if (jp_index == JP_NONE) return "NONE"; + route_index = (td->journey_patterns)[jp_index].route_index; + line_index = (td->line_for_route)[route_index]; + return tdata_operator_url_for_index(td, td->operator_for_line[line_index]); } bool tdata_load(tdata_t *td, char *filename) { - if ( !tdata_io_v3_load (td, filename)) return false; + if ( !tdata_io_v4_load (td, filename)) return false; #ifdef RRRR_FEATURE_REALTIME_EXPANDED if ( !tdata_alloc_expanded (td)) return false; @@ -209,60 +360,90 @@ bool tdata_load(tdata_t *td, char *filename) { } void tdata_close(tdata_t *td) { + hashgrid_teardown (&td->hg); + + #ifdef RRRR_FEATURE_REALTIME + if (td->stop_point_id_index) radixtree_destroy (td->stop_point_id_index); + if (td->vjid_index) radixtree_destroy (td->vjid_index); + if (td->lineid_index) radixtree_destroy (td->lineid_index); + #ifdef RRRR_FEATURE_REALTIME_EXPANDED tdata_free_expanded (td); #endif #ifdef RRRR_FEATURE_REALTIME_ALERTS tdata_clear_gtfsrt_alerts (td); #endif - - tdata_io_v3_close (td); + #endif + tdata_io_v4_close (td); } -spidx_t *tdata_points_for_journey_pattern(tdata_t *td, uint32_t jp_index) { +spidx_t *tdata_points_for_journey_pattern(const tdata_t *td, jpidx_t jp_index) { return td->journey_pattern_points + td->journey_patterns[jp_index].journey_pattern_point_offset; } -uint8_t *tdata_stop_attributes_for_journey_pattern(tdata_t *td, uint32_t jp_index) { +uint8_t *tdata_stop_point_attributes_for_journey_pattern(const tdata_t *td, jpidx_t jp_index) { journey_pattern_t *jp = &(td->journey_patterns[jp_index]); return td->journey_pattern_point_attributes + jp->journey_pattern_point_offset; } -uint32_t tdata_journey_patterns_for_stop(tdata_t *td, spidx_t stop_index, uint32_t **jp_ret) { - stop_t *stop0 = &(td->stops[stop_index]); - stop_t *stop1 = &(td->stops[stop_index + 1]); - *jp_ret = td->journey_patterns_at_stop + stop0->journey_patterns_at_stop_offset; - return stop1->journey_patterns_at_stop_offset - stop0->journey_patterns_at_stop_offset; +jpidx_t tdata_journey_patterns_for_stop_point(const tdata_t *td, spidx_t sp_index, jpidx_t **jp_ret) { + stop_point_t *stop0 = &(td->stop_points[sp_index]); + stop_point_t *stop1 = &(td->stop_points[sp_index + 1]); + *jp_ret = td->journey_patterns_at_stop + stop0->journey_patterns_at_stop_point_offset; + return (jpidx_t) (stop1->journey_patterns_at_stop_point_offset - stop0->journey_patterns_at_stop_point_offset); } -stoptime_t *tdata_timedemand_type(tdata_t *td, uint32_t jp_index, uint32_t vj_index) { - return td->stop_times + td->vjs[td->journey_patterns[jp_index].vj_ids_offset + vj_index].stop_times_offset; +stoptime_t *tdata_timedemand_type(const tdata_t *td, jpidx_t jp_index, jp_vjoffset_t vj_offset) { + return td->stop_times + td->vjs[td->journey_patterns[jp_index].vj_index + vj_offset].stop_times_offset; } -vehicle_journey_t *tdata_vehicle_journeys_in_journey_pattern(tdata_t *td, uint32_t jp_index) { - return td->vjs + td->journey_patterns[jp_index].vj_ids_offset; +vehicle_journey_t *tdata_vehicle_journeys_in_journey_pattern(const tdata_t *td, jpidx_t jp_index) { + return td->vjs + td->journey_patterns[jp_index].vj_index; } -const char *tdata_stop_name_for_index(tdata_t *td, spidx_t stop_index) { - switch (stop_index) { +const char *tdata_stop_point_name_for_index(const tdata_t *td, spidx_t sp_index) { + switch (sp_index) { case STOP_NONE : return "NONE"; case ONBOARD : return "ONBOARD"; default : - return td->stop_names + td->stop_nameidx[stop_index]; + return td->string_pool + td->stop_point_nameidx[sp_index]; } } +const char *tdata_stop_area_name_for_index(const tdata_t *td, spidx_t sa_index) { + switch (sa_index) { + case STOP_NONE : + return "NONE"; + case ONBOARD : + return "ONBOARD"; + default : + return td->string_pool + td->stop_area_nameidx[sa_index]; + } +} + +const char *tdata_stop_area_id_for_index(const tdata_t *td, spidx_t sa_index) { + return td->string_pool + td->stop_area_ids[sa_index]; +} + +const char *tdata_stop_area_timezone_for_index(const tdata_t *td, spidx_t sa_index) { + return td->string_pool + td->stop_area_timezones[sa_index]; +} + +rtime_t tdata_stop_point_waittime (const tdata_t *tdata, spidx_t sp_index) { + return tdata->stop_point_waittime[sp_index]; +} + /* Rather than reserving a place to store the transfers used to create the initial state, we look them up as needed. */ -rtime_t transfer_duration (tdata_t *tdata, router_request_t *req, spidx_t stop_index_from, spidx_t stop_index_to) { +rtime_t transfer_duration (const tdata_t *tdata, router_request_t *req, spidx_t sp_index_from, spidx_t sp_index_to) { UNUSED(req); - if (stop_index_from != stop_index_to) { - uint32_t t = tdata->stops[stop_index_from ].transfers_offset; - uint32_t tN = tdata->stops[stop_index_from + 1].transfers_offset; + if (sp_index_from != sp_index_to) { + uint32_t t = tdata->stop_points[sp_index_from ].transfers_offset; + uint32_t tN = tdata->stop_points[sp_index_from + 1].transfers_offset; for ( ; t < tN ; ++t) { - if (tdata->transfer_target_stops[t] == stop_index_to) { - return (rtime_t) tdata->transfer_dist_meters[t] + req->walk_slack; + if (tdata->transfer_target_stops[t] == sp_index_to) { + return (rtime_t) tdata->transfer_durations[t] + req->walk_slack; } } } else { @@ -272,42 +453,42 @@ rtime_t transfer_duration (tdata_t *tdata, router_request_t *req, spidx_t stop_i } #ifdef RRRR_DEBUG -void tdata_dump_journey_pattern(tdata_t *td, uint32_t jp_index, uint32_t vj_index) { +void tdata_dump_journey_pattern(const tdata_t *td, jpidx_t jp_index, jp_vjoffset_t vj_offset) { spidx_t *stops = tdata_points_for_journey_pattern(td, jp_index); - uint32_t ti; + jp_vjoffset_t vj_o; spidx_t si; journey_pattern_t jp = td->journey_patterns[jp_index]; printf("\njourney_pattern details for %s %s %s '%s %s' [%d] (n_stops %d, n_vjs %d)\n" "vjid, stop sequence, stop name (index), departures \n", - tdata_agency_name_for_journey_pattern(td, jp_index), - tdata_agency_id_for_journey_pattern(td, jp_index), - tdata_agency_url_for_journey_pattern(td, jp_index), + tdata_operator_name_for_journey_pattern(td, jp_index), + tdata_operator_id_for_journey_pattern(td, jp_index), + tdata_operator_url_for_journey_pattern(td, jp_index), tdata_line_code_for_journey_pattern(td, jp_index), tdata_headsign_for_journey_pattern(td, jp_index), jp_index, jp.n_stops, jp.n_vjs); - for (ti = (vj_index == NONE ? 0 : vj_index); - ti < (vj_index == NONE ? jp.n_vjs : - vj_index + 1); - ++ti) { - stoptime_t *times = tdata_timedemand_type(td, jp_index, ti); + for (vj_o = (vj_offset == VJ_NONE ? 0 : vj_offset); + vj_o < (vj_offset == VJ_NONE ? jp.n_vjs : + vj_offset + 1); + ++vj_o) { + stoptime_t *times = tdata_timedemand_type(td, jp_index, vj_o); /* TODO should this really be a 2D array ? - stoptime_t (*times)[jp.n_stops] = (void*) tdata_timedemand_type(td, jp_index, ti); */ + stoptime_t (*times)[jp.n_stops] = (void*) tdata_timedemand_type(td, jp_index, vj_o); */ - printf("%s\n", tdata_vehicle_journey_id_for_index(td, jp.vj_ids_offset + ti)); + printf("%s\n", tdata_vehicle_journey_id_for_index(td, jp.vj_index + vj_o)); for (si = 0; si < jp.n_stops; ++si) { - const char *stop_id = tdata_stop_name_for_index (td, stops[si]); + const char *stop_id = tdata_stop_point_name_for_index (td, stops[si]); char arrival[13], departure[13]; printf("%4d %35s [%06d] : %s %s", si, stop_id, stops[si], - btimetext(times[si].arrival + td->vjs[jp.vj_ids_offset + ti].begin_time + RTIME_ONE_DAY, arrival), - btimetext(times[si].departure + td->vjs[jp.vj_ids_offset + ti].begin_time + RTIME_ONE_DAY, departure)); + btimetext(times[si].arrival + td->vjs[jp.vj_index + vj_o].begin_time + RTIME_ONE_DAY, arrival), + btimetext(times[si].departure + td->vjs[jp.vj_index + vj_o].begin_time + RTIME_ONE_DAY, departure)); #ifdef RRRR_FEATURE_REALTIME_EXPANDED - if (td->vj_stoptimes && td->vj_stoptimes[jp.vj_ids_offset + ti]) { + if (td->vj_stoptimes && td->vj_stoptimes[jp.vj_index + vj_o]) { printf (" %s %s", - btimetext(td->vj_stoptimes[jp.vj_ids_offset + ti][si].arrival + RTIME_ONE_DAY, arrival), - btimetext(td->vj_stoptimes[jp.vj_ids_offset + ti][si].departure + RTIME_ONE_DAY, departure)); + btimetext(td->vj_stoptimes[jp.vj_index + vj_o][si].arrival + RTIME_ONE_DAY, arrival), + btimetext(td->vj_stoptimes[jp.vj_index + vj_o][si].departure + RTIME_ONE_DAY, departure)); } #endif @@ -318,22 +499,22 @@ void tdata_dump_journey_pattern(tdata_t *td, uint32_t jp_index, uint32_t vj_inde printf("\n"); } -void tdata_dump(tdata_t *td) { +void tdata_dump(const tdata_t *td) { uint32_t i; printf("\nCONTEXT\n" - "n_stops: %d\n" - "n_journey_patterns: %d\n", td->n_stops, td->n_journey_patterns); + "n_stop_points: %d\n" + "n_journey_patterns: %d\n", td->n_stop_points, td->n_journey_patterns); printf("\nSTOPS\n"); - for (i = 0; i < td->n_stops; i++) { - stop_t s0 = td->stops[i]; - stop_t s1 = td->stops[i+1]; - uint32_t j0 = s0.journey_patterns_at_stop_offset; - uint32_t j1 = s1.journey_patterns_at_stop_offset; + for (i = 0; i < td->n_stop_points; i++) { + stop_point_t s0 = td->stop_points[i]; + stop_point_t s1 = td->stop_points[i+1]; + uint32_t j0 = s0.journey_patterns_at_stop_point_offset; + uint32_t j1 = s1.journey_patterns_at_stop_point_offset; uint32_t j; printf("stop %d at lat %f lon %f\n", - i, td->stop_coords[i].lat, td->stop_coords[i].lon); + i, td->stop_point_coords[i].lat, td->stop_point_coords[i].lon); printf("served by journey_patterns "); for (j=j0; jjourney_patterns_at_stop[j]); @@ -357,8 +538,8 @@ void tdata_dump(tdata_t *td) { printf("\n"); } printf("\nSTOPIDS\n"); - for (i = 0; i < td->n_stops; i++) { - printf("stop %03d has id %s \n", i, tdata_stop_name_for_index(td, i)); + for (i = 0; i < td->n_stop_points; i++) { + printf("stop %03d has id %s \n", i, tdata_stop_point_name_for_index(td, (spidx_t) i)); } for (i = 0; i < td->n_journey_patterns; i++) { /* TODO: Remove? @@ -366,7 +547,154 @@ void tdata_dump(tdata_t *td) { * tdata_route_desc_for_index(td, i), * tdata_vehicle_journey_ids_for_route(td, i)); */ - tdata_dump_journey_pattern(td, i, NONE); + tdata_dump_journey_pattern(td, (jpidx_t) i, VJ_NONE); } } #endif + +bool tdata_hashgrid_setup (tdata_t *tdata) { + uint32_t i_sp; + + tdata->coords = (coord_t *) malloc(sizeof(coord_t) * tdata->n_stop_points); + if (!tdata->coords) return false; + + i_sp = tdata->n_stop_points; + do { + i_sp--; + coord_from_latlon(tdata->coords + i_sp, + tdata->stop_point_coords + i_sp); + } while(i_sp); + + if (!hashgrid_init (&tdata->hg, 100, 500.0, tdata->coords, tdata->n_stop_points)) { + return false; + } + + return true; +} + +void tdata_hashgrid_teardown (tdata_t *tdata) { + free (tdata->coords); +} + +bool strtoopidx (const char *str, const tdata_t *td, opidx_t *op, char **endptr) { + long op_idx = strtol(str, endptr, 10); + if (op_idx >= 0 && op_idx < td->n_operator_ids) { + *op = (opidx_t) op_idx; + return true; + } + return false; +} + +bool strtospidx (const char *str, const tdata_t *td, spidx_t *sp, char **endptr) { + long stop_idx = strtol(str, endptr, 10); + if (stop_idx >= 0 && stop_idx < td->n_stop_points) { + *sp = (spidx_t) stop_idx; + return true; + } + return false; +} + +bool strtojpidx (const char *str, const tdata_t *td, jpidx_t *jp, char **endptr) { + long jp_idx = strtol(str, endptr, 10); + if (jp_idx >= 0 && jp_idx < td->n_journey_patterns) { + *jp = (jpidx_t) jp_idx; + return true; + } + return false; +} + +bool strtovjoffset (const char *str, const tdata_t *td, jpidx_t jp_index, jp_vjoffset_t *vj_o, char **endptr) { + long vj_offset = strtol(str, endptr, 10); + if (vj_offset >= 0 && vj_offset < td->journey_patterns[jp_index].n_vjs) { + *vj_o = (jp_vjoffset_t) vj_offset; + return true; + } + return false; +} + +#ifdef RRRR_FEATURE_REALTIME +static +radixtree_t *tdata_radixtree_string_pool_setup (const tdata_t *td, uint32_t *s, uint32_t n) { + uint32_t idx; + radixtree_t *r = radixtree_new(); + for (idx = 0; idx < n; idx++) { + radixtree_insert (r, td->string_pool + s[idx], idx); + } + return r; +} + +static +radixtree_t *tdata_radixtree_full_string_pool_setup (char *strings, uint32_t n) { + radixtree_t *r = radixtree_new(); + char *strings_end = strings + n; + char *s = strings; + uint32_t idx = 0; + while (s < strings_end) { + size_t width = strlen(s) + 1; + radixtree_insert (r, s, idx); + idx += width; + s += width; + } + + return r; +} + +bool tdata_realtime_setup (tdata_t *tdata) { + tdata->stop_point_id_index = tdata_radixtree_string_pool_setup (tdata, tdata->stop_point_ids, tdata->n_stop_points); + tdata->vjid_index = tdata_radixtree_string_pool_setup (tdata, tdata->vj_ids, tdata->n_vjs); + tdata->lineid_index = tdata_radixtree_string_pool_setup (tdata, tdata->line_ids, tdata->n_line_ids); + tdata->stringpool_index = tdata_radixtree_full_string_pool_setup (tdata->string_pool, tdata->n_string_pool); + + /* Validate the radixtrees are actually created. */ + return (tdata->stop_point_id_index && + tdata->vjid_index && + tdata->lineid_index); +} +#endif + +void tdata_validity (const tdata_t *tdata, uint64_t *min, uint64_t *max) { + *min = tdata->calendar_start_time; + *max = tdata->calendar_start_time + (tdata->n_days - 1) * SEC_IN_ONE_DAY; +} + +void tdata_extends (const tdata_t *tdata, latlon_t *ll, latlon_t *ur) { + spidx_t i_stop = (spidx_t) tdata->n_stop_points; + + float min_lon = 180.0f, min_lat = 90.0f, max_lon = -180.0f, max_lat = -90.0f; + + do { + i_stop--; + if (tdata->stop_point_coords[i_stop].lat == 0.0f || + tdata->stop_point_coords[i_stop].lon == 0.0f) continue; + + max_lat = MAX(tdata->stop_point_coords[i_stop].lat, max_lat); + max_lon = MAX(tdata->stop_point_coords[i_stop].lon, max_lon); + min_lat = MIN(tdata->stop_point_coords[i_stop].lat, min_lat); + min_lon = MIN(tdata->stop_point_coords[i_stop].lon, min_lon); + } while (i_stop > 0); + + ll->lat = min_lat; + ll->lon = min_lon; + ur->lat = max_lat; + ur->lon = max_lon; +} + +void tdata_modes (const tdata_t *tdata, tmode_t *m) { + jpidx_t i_jp = (jpidx_t) tdata->n_journey_patterns; + uint16_t attributes = 0; + + do { + i_jp--; + attributes |= tdata->journey_patterns[i_jp].attributes; + } while (i_jp > 0); + + *m = (tmode_t) attributes; +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-macros" + +#define tdata_stop_pointidx_by_stop_point_id(td, stop_id) tdata_stopidx_by_stop_id(td, stop_id, 0) +#define tdata_journey_pattern_idx_by_line_id(td, line_id) tdata_journey_pattern_idx_by_line_id(td, jp_index_offset, 0) + +#pragma clang diagnostic pop diff --git a/tdata.h b/tdata.h index eba15a0..fe37657 100644 --- a/tdata.h +++ b/tdata.h @@ -1,4 +1,7 @@ -/* Copyright 2013 Bliksem Labs. See the LICENSE file at the top-level directory of this distribution and at https://github.com/bliksemlabs/rrrr/. */ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and at + * https://github.com/bliksemlabs/rrrr/ + */ /* tdata.h */ @@ -9,6 +12,8 @@ #include "geometry.h" #include "rrrr_types.h" +#include "hashgrid.h" + #ifdef RRRR_FEATURE_REALTIME #include "gtfs-realtime.pb-c.h" #include "radixtree.h" @@ -17,167 +22,123 @@ #include #include -typedef struct stop stop_t; -struct stop { - uint32_t journey_patterns_at_stop_offset; - uint32_t transfers_offset; -}; - -/* An individual JourneyPattern in the RAPTOR sense: - * A group of VehicleJourneys all share the same JourneyPattern. - */ -typedef struct journey_pattern journey_pattern_t; -struct journey_pattern { - uint32_t journey_pattern_point_offset; - uint32_t vj_ids_offset; - uint32_t headsign_offset; - uint16_t n_stops; - uint16_t n_vjs; - uint16_t attributes; - uint16_t agency_index; - uint16_t line_code_index; - uint16_t productcategory_index; - rtime_t min_time; - rtime_t max_time; -}; - -/* An individual VehicleJourney, - * a materialized instance of a time demand type. */ -typedef struct vehicle_journey vehicle_journey_t; -struct vehicle_journey { - /* The offset of the first stoptime of the - * time demand type used by this vehicle_journey. - */ - uint32_t stop_times_offset; - - /* The absolute start time since at the - * departure of the first stop - */ - rtime_t begin_time; - - /* The vj_attributes, including CANCELED flag - */ - uint16_t vj_attributes; -}; - -typedef struct stoptime stoptime_t; -struct stoptime { - rtime_t arrival; - rtime_t departure; -}; - -typedef enum stop_attribute { - /* the stop is accessible for a wheelchair */ - sa_wheelchair_boarding = 1, - - /* the stop is accessible for the visible impaired */ - sa_visual_accessible = 2, - - /* a shelter is available against rain */ - sa_shelter = 4, - - /* a bicycle can be parked */ - sa_bikeshed = 8, - - /* a bicycle may be rented */ - sa_bicyclerent = 16, - - /* a car can be parked */ - sa_parking = 32 -} stop_attribute_t; - -typedef enum journey_pattern_point_attribute { - /* the vehicle waits if it arrives early */ - rsa_waitingpoint = 1, - - /* a passenger can enter the vehicle at this stop */ - rsa_boarding = 2, - - /* a passenger can leave the vehicle at this stop */ - rsa_alighting = 4 -} journey_pattern_point_attribute_t; - typedef struct tdata tdata_t; struct tdata { void *base; size_t size; - /* Midnight of the first day in the 32-day calendar in seconds - * since the epoch, ignores Daylight Saving Time (DST). - */ + + /* Pointer to string pool, with the timezone of the times in the data*/ + uint32_t timezone; + + /* Midnight (UTC) of the first day in the 32-day calendar in seconds since the epoch */ uint64_t calendar_start_time; - /* Dates within the active calendar which have DST. */ - calendar_t dst_active; - uint32_t n_stops; - uint32_t n_stop_attributes; - uint32_t n_stop_coords; + /* The offset in seconds from UTC for the entire timetable */ + int32_t utc_offset; + uint32_t n_days; + uint32_t n_stop_points; + uint32_t n_stop_areas; + uint32_t n_stop_point_attributes; + uint32_t n_stop_point_coords; + uint32_t n_stop_area_coords; + uint32_t n_stop_area_for_stop_point; uint32_t n_journey_patterns; uint32_t n_journey_pattern_points; uint32_t n_journey_pattern_point_attributes; + uint32_t n_journey_pattern_point_headsigns; uint32_t n_stop_times; uint32_t n_vjs; uint32_t n_journey_patterns_at_stop; uint32_t n_transfer_target_stops; - uint32_t n_transfer_dist_meters; + uint32_t n_transfer_durations; uint32_t n_vj_active; uint32_t n_journey_pattern_active; uint32_t n_platformcodes; - uint32_t n_stop_names; - uint32_t n_stop_nameidx; - uint32_t n_agency_ids; - uint32_t n_agency_names; - uint32_t n_agency_urls; - uint32_t n_headsigns; + uint32_t n_stop_point_nameidx; + uint32_t n_stop_area_nameidx; + uint32_t n_operator_ids; + uint32_t n_operator_names; + uint32_t n_operator_urls; + uint32_t n_commercial_mode_ids; + uint32_t n_commercial_mode_names; + uint32_t n_physical_mode_ids; + uint32_t n_physical_mode_names; + uint32_t n_string_pool; uint32_t n_line_codes; - uint32_t n_productcategories; + uint32_t n_line_names; uint32_t n_line_ids; - uint32_t n_stop_ids; + uint32_t n_line_colors; + uint32_t n_line_colors_text; + uint32_t n_stop_point_ids; + uint32_t n_stop_area_ids; + uint32_t n_stop_area_timezones; uint32_t n_vj_ids; - stop_t *stops; - uint8_t *stop_attributes; + uint32_t n_vj_time_offsets; + uint32_t n_line_for_route; + uint32_t n_operator_for_line; + uint32_t n_commercial_mode_for_jp; + uint32_t n_physical_mode_for_line; + uint32_t n_vehicle_journey_transfers_backward; + uint32_t n_vehicle_journey_transfers_forward; + uint32_t n_stop_point_waittime; + stop_point_t *stop_points; + uint8_t *stop_point_attributes; journey_pattern_t *journey_patterns; spidx_t *journey_pattern_points; uint8_t *journey_pattern_point_attributes; stoptime_t *stop_times; vehicle_journey_t *vjs; - uint32_t *journey_patterns_at_stop; spidx_t *transfer_target_stops; - uint8_t *transfer_dist_meters; + rtime_t *transfer_durations; + rtime_t *stop_point_waittime; rtime_t max_time; /* optional data: * NULL pointer means it is not available */ - latlon_t *stop_coords; - uint32_t platformcodes_width; - char *platformcodes; - char *stop_names; - uint32_t *stop_nameidx; - uint32_t agency_ids_width; - char *agency_ids; - uint32_t agency_names_width; - char *agency_names; - uint32_t agency_urls_width; - char *agency_urls; - char *headsigns; - uint32_t line_codes_width; - char *line_codes; - uint32_t productcategories_width; - char *productcategories; + jpidx_t *journey_patterns_at_stop; + latlon_t *stop_point_coords; + latlon_t *stop_area_coords; + spidx_t *stop_area_for_stop_point; + uint32_t *platformcodes; + uint32_t *stop_point_nameidx; + uint32_t *stop_area_nameidx; + uint32_t *operator_ids; + uint32_t *operator_names; + uint32_t *operator_urls; + uint32_t *commercial_mode_ids; + uint32_t *commercial_mode_names; + uint32_t *physical_mode_ids; + uint32_t *physical_mode_names; + uint16_t *commercial_mode_for_jp; + uint16_t *physical_mode_for_line; + lineidx_t *line_for_route; + opidx_t *operator_for_line; + vehicle_journey_ref_t *vehicle_journey_transfers_backward; + vehicle_journey_ref_t *vehicle_journey_transfers_forward; + char *string_pool; + uint32_t *line_codes; + uint32_t *line_names; calendar_t *vj_active; calendar_t *journey_pattern_active; - uint32_t line_ids_width; - char *line_ids; - uint32_t stop_ids_width; - char *stop_ids; - uint32_t vj_ids_width; - char *vj_ids; + rtime_t *journey_pattern_min; + rtime_t *journey_pattern_max; + uint32_t *journey_pattern_point_headsigns; + uint32_t *line_ids; + uint32_t *line_colors; + uint32_t *line_colors_text; + uint32_t *stop_point_ids; + uint32_t *stop_area_ids; + uint32_t *stop_area_timezones; + int8_t *vj_time_offsets; + uint32_t *vj_ids; #ifdef RRRR_FEATURE_REALTIME radixtree_t *lineid_index; - radixtree_t *stopid_index; + radixtree_t *stop_point_id_index; radixtree_t *vjid_index; + radixtree_t *stringpool_index; #ifdef RRRR_FEATURE_REALTIME_EXPANDED stoptime_t **vj_stoptimes; uint32_t *vjs_in_journey_pattern; - list_t **rt_journey_patterns_at_stop; + list_t **rt_journey_patterns_at_stop_point; calendar_t *vj_active_orig; calendar_t *journey_pattern_active_orig; #endif @@ -185,86 +146,270 @@ struct tdata { TransitRealtime__FeedMessage *alerts; #endif #endif + /* The latlon lookup for each stop_point */ + hashgrid_t hg; + coord_t *coords; }; +#ifdef RRRR_DEBUG +void tdata_dump(const tdata_t *td); +void tdata_dump_journey_pattern(const tdata_t *td, jpidx_t jp_index, jp_vjoffset_t vj_offset); +#endif + +/* * * * * * * * * * * * * * * * * * * + * + * GENERAL FUNCTIONS + * + * * * * * * * * * * * * * * * * * * */ + bool tdata_load(tdata_t *td, char *filename); void tdata_close(tdata_t *td); -void tdata_dump(tdata_t *td); +/* Return timezone used in general for timetable, eg 'Europe/Amsterdam */ +const char *tdata_timezone(const tdata_t *td); -spidx_t *tdata_points_for_journey_pattern(tdata_t *td, uint32_t jp_index); +bool tdata_hashgrid_setup (tdata_t *tdata); -uint8_t *tdata_stop_attributes_for_journey_pattern(tdata_t *td, uint32_t jp_index); +void tdata_hashgrid_teardown (tdata_t *tdata); -/* TODO: return number of items and store pointer to beginning, to allow restricted pointers */ -uint32_t tdata_journey_patterns_for_stop(tdata_t *td, spidx_t stop_index, uint32_t **jp_ret); +#ifdef RRRR_FEATURE_REALTIME +bool tdata_realtime_setup (tdata_t *tdata); +#endif + +/* Return UTC epoch of start and end midnights with the valditiy of the timetable */ +void tdata_validity (const tdata_t *tdata, uint64_t *min, uint64_t *max); +/* Return the lower-left and upper-right of the extends of all stop_point's in the timetable */ +void tdata_extends (const tdata_t *tdata, latlon_t *ll, latlon_t *ur); +/* Return the mode enum of all journey_patterns in the timetable */ +void tdata_modes (const tdata_t *tdata, tmode_t *m); + + +/* * * * * * * * * * * * * * * * * * * + * + * OPERATOR: + * An operator is a company responsible for a line + * + * * * * * * * * * * * * * * * * * * */ + +const char *tdata_operator_id_for_index(const tdata_t *td, opidx_t operator_index); + +const char *tdata_operator_name_for_index(const tdata_t *td, opidx_t operator_index); + +const char *tdata_operator_url_for_index(const tdata_t *td, opidx_t operator_index); + +opidx_t tdata_operator_idx_by_operator_name(const tdata_t *td, const char *operator_name, opidx_t operator_index_offset); + +opidx_t tdata_operator_idx_for_journey_pattern(const tdata_t *td, jpidx_t jp_index); + +/* * * * * * * * * * * * * * * * * * * + * + * PHYSICAL_MODE: + * Physical_mode is the type of transport used for a line. For example BUS, TRAIN or TRAM, etc. + * + * * * * * * * * * * * * * * * * * * */ + +const char *tdata_name_for_physical_mode_index(const tdata_t *td, uint32_t physical_mode_index); + +const char *tdata_id_for_physical_mode_index(const tdata_t *td, uint32_t physical_mode_index); + +/* * * * * * * * * * * * * * * * * * * + * + * COMMERCIAL_MODE: + * Commercial_mode is the type of transport used to market a line. For example R-NET, Intercity, Expressbus, etc. + * + * * * * * * * * * * * * * * * * * * */ -stoptime_t *tdata_stoptimes_for_journey_pattern(tdata_t *td, uint32_t jp_index); +const char *tdata_name_for_commercial_mode_index(const tdata_t *td, uint32_t commercial_mode_index); -void tdata_dump_journey_pattern(tdata_t *td, uint32_t jp_index, uint32_t vj_index); +const char *tdata_id_for_commercial_mode_index(const tdata_t *td, uint32_t commercial_mode_index); -const char *tdata_line_id_for_journey_pattern(tdata_t *td, uint32_t jp_index); -const char *tdata_stop_id_for_index(tdata_t *td, spidx_t stop_index); +/* * * * * * * * * * * * * * * * * * * + * + * LINE: + * An line is a collection of routes, used to communicate a logical path for multiple journeys + * by linenumbers, colors and names. + * + * * * * * * * * * * * * * * * * * * */ -uint8_t *tdata_stop_attributes_for_index(tdata_t *td, spidx_t stop_index); +const char *tdata_line_id_for_index(const tdata_t *td, lineidx_t line_index); -const char *tdata_vehicle_journey_id_for_index(tdata_t *td, uint32_t vj_index); +const char *tdata_line_code_for_index(const tdata_t *td, lineidx_t line_index); -const char *tdata_vehicle_journey_id_for_jp_vj_index(tdata_t *td, uint32_t jp_index, uint32_t vj_index); +const char *tdata_line_color_for_index(const tdata_t *td, lineidx_t line_index); -uint32_t tdata_agencyidx_by_agency_name(tdata_t *td, char* agency_name, uint32_t start_index); +const char *tdata_line_color_text_for_index(const tdata_t *td, lineidx_t line_index); -const char *tdata_agency_id_for_index(tdata_t *td, uint32_t agency_index); +const char *tdata_line_name_for_index(const tdata_t *td, lineidx_t line_index); -const char *tdata_agency_name_for_index(tdata_t *td, uint32_t agency_index); +/* * * * * * * * * * * * * * * * * * * + * + * ROUTE: + * An route is a collection of journey_patterns, that all match a common commercially used direction, + * This direction is given by the operator and does not necessarily match any physical reality. + * + * * * * * * * * * * * * * * * * * * */ -const char *tdata_agency_url_for_index(tdata_t *td, uint32_t agency_index); +/* TODO No function definied for the type, however it would make sense to add id's to data for routes to be able to request them */ -const char *tdata_headsign_for_offset(tdata_t *td, uint32_t headsign_offset); +/* * * * * * * * * * * * * * * * * * * + * + * JOURNEY_PATTERN: + * A journey pattern is an ordered list of stop_points. + * Two vehicles that serve exactly the same stop_points in exactly the same order and attributes (forboarding/foralighting/etc.) + * belong to to the same journey_pattern. + * + * * * * * * * * * * * * * * * * * * */ -const char *tdata_line_code_for_index(tdata_t *td, uint32_t line_code_index); +spidx_t *tdata_points_for_journey_pattern(const tdata_t *td, jpidx_t jp_index); -const char *tdata_productcategory_for_index(tdata_t *td, uint32_t productcategory_index); +stoptime_t *tdata_stoptimes_for_journey_pattern(const tdata_t *td, jpidx_t jp_index); -const char *tdata_stop_name_for_index(tdata_t *td, spidx_t stop_index); +const char *tdata_headsign_for_journey_pattern(const tdata_t *td, jpidx_t jp_index); -const char *tdata_platformcode_for_index(tdata_t *td, spidx_t stop_index); +const char *tdata_headsign_for_journey_pattern_point(const tdata_t *td, jpidx_t jp_index,jppidx_t jpp_offset); -spidx_t tdata_stopidx_by_stop_name(tdata_t *td, char* stop_name, spidx_t start_index); +/* Get a pointer to the array of vehicle_journeys for this journey_pattern. */ +vehicle_journey_t *tdata_vehicle_journeys_in_journey_pattern(const tdata_t *td, jpidx_t jp_index); + +const char *tdata_line_id_for_journey_pattern(const tdata_t *td, jpidx_t jp_index); + +calendar_t *tdata_vj_masks_for_journey_pattern(const tdata_t *td, jpidx_t jp_index); + +const char *tdata_line_code_for_journey_pattern(const tdata_t *td, jpidx_t jp_index); + +const char *tdata_line_color_for_journey_pattern(const tdata_t *td, jpidx_t jp_index); + +const char *tdata_line_color_text_for_journey_pattern(const tdata_t *td, jpidx_t jp_index); + +const char *tdata_line_name_for_journey_pattern(const tdata_t *td, jpidx_t jp_index); + +const char *tdata_commercial_mode_name_for_journey_pattern(const tdata_t *td, jpidx_t jp_index); + +const char *tdata_commercial_mode_id_for_journey_pattern(const tdata_t *td, jpidx_t jp_index); + +const char *tdata_physical_mode_name_for_journey_pattern(const tdata_t *td, jpidx_t jp_index); -spidx_t tdata_stopidx_by_stop_id(tdata_t *td, char* stop_id, spidx_t start_index); +const char *tdata_physical_mode_id_for_journey_pattern(const tdata_t *td, jpidx_t jp_index); -uint32_t tdata_journey_pattern_idx_by_line_id(tdata_t *td, char *line_id, uint32_t start_index); +const char *tdata_operator_id_for_journey_pattern(const tdata_t *td, jpidx_t jp_index); -const char *tdata_vehicle_journey_ids_in_journey_pattern(tdata_t *td, uint32_t jp_index); +const char *tdata_operator_name_for_journey_pattern(const tdata_t *td, jpidx_t jp_index); -calendar_t *tdata_vj_masks_for_journey_pattern(tdata_t *td, uint32_t jp_index); +const char *tdata_operator_url_for_journey_pattern(const tdata_t *td, jpidx_t jp_index); -const char *tdata_headsign_for_journey_pattern(tdata_t *td, uint32_t jp_index); +jpidx_t tdata_journey_pattern_idx_by_line_id(const tdata_t *td, char *line_id, jpidx_t start_index); -const char *tdata_line_code_for_journey_pattern(tdata_t *td, uint32_t jp_index); +/* * * * * * * * * * * * * * * * * * * + * + * VEHICLE_JOURNEY: + * A journey is a single run of a vehicle along a journey_pattern. + * For example, vehicle bus 23 leaving at 14:20 from stop_area Amsterdam Central Station. + * + * * * * * * * * * * * * * * * * * * */ -const char *tdata_productcategory_for_journey_pattern(tdata_t *td, uint32_t jp_index); +const char *tdata_vehicle_journey_id_for_index(const tdata_t *td, vjidx_t vj_index); -const char *tdata_agency_id_for_journey_pattern(tdata_t *td, uint32_t jp_index); +const char *tdata_vehicle_journey_id_for_jp_vj_offset(const tdata_t *td, jpidx_t jp_index, jp_vjoffset_t vj_offset); -const char *tdata_agency_name_for_journey_pattern(tdata_t *td, uint32_t jp_index); +int32_t tdata_utc_offset_for_vj_index(const tdata_t *td, vjidx_t vj_index); -const char *tdata_agency_url_for_journey_pattern(tdata_t *td, uint32_t jp_index); +int32_t tdata_time_offset_for_vj_index(const tdata_t *td, vjidx_t vj_index); + +int32_t tdata_utc_offset_for_jp_vj_offset(const tdata_t *td, jpidx_t jp_index, jp_vjoffset_t vj_offset); + +int32_t tdata_time_offset_for_jp_vj_offset(const tdata_t *td, jpidx_t jp_index, jp_vjoffset_t vj_offset); /* Returns a pointer to the first stoptime for the VehicleJourney. These are generally TimeDemandTypes that must be shifted in time to get the true scheduled arrival and departure times. */ -stoptime_t *tdata_timedemand_type(tdata_t *td, uint32_t jp_index, uint32_t vj_index); +stoptime_t *tdata_timedemand_type(const tdata_t *td, jpidx_t jp_index, jp_vjoffset_t vj_offset); -/* Get a pointer to the array of vehicle_journeys for this journey_pattern. */ -vehicle_journey_t *tdata_vehicle_journeys_in_journey_pattern(tdata_t *td, uint32_t jp_index); +/* Parse string with vj offset (within journey_pattern) to jp_vj_offset_t */ +bool strtovjoffset (const char *str, const tdata_t *td, jpidx_t jp_index, jp_vjoffset_t *vj_offset, char **endptr); + +/* * * * * * * * * * * * * * * * * * * + * + * STOP_TIME: + * A stop_time represents the time when a vehicle is planned to arrive and to leave a stop_point. + * + * * * * * * * * * * * * * * * * * * */ + +/* Return stop_time in seconds to/from midnight in the time-offset used by tdata */ +rtime_t tdata_stoptime_for_index(const tdata_t *td, jpidx_t jp_index, jppidx_t jpp_offset, jp_vjoffset_t vj_offset, bool arrival); + +/* Return stop_time in seconds to/from midnight local-time */ +rtime_t tdata_stoptime_local_for_index(const tdata_t *td, jpidx_t jp_index, jppidx_t jpp_offset, jp_vjoffset_t vj_offset, bool arrival); + +/* Return stop_time in seconds to/from midnight UTC */ +int32_t tdata_stoptime_utc_for_index(const tdata_t *td, jpidx_t jp_index, jppidx_t jpp_offset, jp_vjoffset_t vj_offset, bool arrival); + + +/* * * * * * * * * * * * * * * * * * * + * + * STOP_AREA: + * A stop_area is a collection of stop_points. + * Generally there are at least two stop_points per stop_area, one per direction of a line. + * Now think of a hub, you will have more than one line. + * Therefore your stop_area will contain more than two stop_points. + * In particular cases your stop_area can contain only one stop_point. + * + * * * * * * * * * * * * * * * * * * */ + +/* Parse string with journey_pattenr index to jp_index*/ +bool strtojpidx (const char *str, const tdata_t *td, jpidx_t *jp, char **endptr); + +bool strtoopidx (const char *str, const tdata_t *td, opidx_t *op, char **endptr); + +latlon_t *tdata_stop_area_coord_for_index(const tdata_t *td, spidx_t sa_index); + +const char *tdata_stop_area_id_for_index(const tdata_t *td, spidx_t sa_index); + +const char *tdata_stop_area_name_for_index(const tdata_t *td, spidx_t sa_index); + +const char *tdata_stop_area_timezone_for_index(const tdata_t *td, spidx_t saindex); + +spidx_t tdata_stop_areaidx_by_stop_area_name(const tdata_t *td, char *stop_area_name, spidx_t sp_index_offset); + +spidx_t tdata_stop_areaidx_for_index(const tdata_t *td, spidx_t sp_index); + +spidx_t tdata_stop_areaidx_by_stop_area_name(const tdata_t *td, char *stop_area_name, spidx_t sa_index_offset); + +spidx_t tdata_stop_areaidx_by_stop_area_id(const tdata_t *td, char *stop_area_name, spidx_t sa_index_offset); + +/* * * * * * * * * * * * * * * * * * * + * + * STOP_POINT: + * A stop_point is the location where a vehicle can stop. + * + * * * * * * * * * * * * * * * * * * */ + +/* TODO: return number of items and store pointer to beginning, to allow restricted pointers */ +jpidx_t tdata_journey_patterns_for_stop_point(const tdata_t *td, spidx_t sp_index, jpidx_t **jp_ret); + +const char *tdata_stop_point_id_for_index(const tdata_t *td, spidx_t sp_index); + +/* Parse string with stop_point index to sp_index */ +bool strtospidx (const char *str, const tdata_t *td, spidx_t *sp, char **endptr); + +uint8_t *tdata_stop_point_attributes_for_journey_pattern(const tdata_t *td, jpidx_t jp_index); + +uint8_t *tdata_stop_point_attributes_for_index(const tdata_t *td, spidx_t sp_index); + +const char *tdata_stop_point_name_for_index(const tdata_t *td, spidx_t sp_index); + +const char *tdata_stop_point_name_for_index(const tdata_t *td, spidx_t sp_index); + +latlon_t *tdata_stop_point_coord_for_index(const tdata_t *td, spidx_t sp_index); + +const char *tdata_platformcode_for_index(const tdata_t *td, spidx_t sp_index); -const char *tdata_stop_desc_for_index(tdata_t *td, spidx_t stop_index); +spidx_t tdata_stop_pointidx_by_stop_point_name(const tdata_t *td, char *stop_point_name, spidx_t sp_index_offset); -rtime_t transfer_duration (tdata_t *tdata, router_request_t *req, spidx_t stop_index_from, spidx_t stop_index_to); +spidx_t tdata_stop_pointidx_by_stop_point_id(const tdata_t *td, char *stop_point_id, spidx_t sp_index_offset); -const char *tdata_stop_name_for_index(tdata_t *td, spidx_t stop_index); +/* Get the minimum waittime a passenger has to wait before transferring to another vehicle */ +rtime_t tdata_stop_point_waittime (const tdata_t *tdata, spidx_t sp_index); +rtime_t transfer_duration (const tdata_t *tdata, router_request_t *req, spidx_t sp_index_from, spidx_t sp_index_to); #endif /* _TDATA_H */ diff --git a/tdata_io_v3.h b/tdata_io_v3.h deleted file mode 100644 index e1b1b0f..0000000 --- a/tdata_io_v3.h +++ /dev/null @@ -1,68 +0,0 @@ -#include "rrrr_types.h" -#include "tdata.h" - -/* file-visible struct */ -typedef struct tdata_header tdata_header_t; -struct tdata_header { - /* Contents must read "TTABLEV3" */ - char version_string[8]; - uint64_t calendar_start_time; - calendar_t dst_active; - uint32_t n_stops; - uint32_t n_stop_attributes; - uint32_t n_stop_coords; - uint32_t n_journey_patterns; - uint32_t n_journey_pattern_points; - uint32_t n_journey_pattern_point_attributes; - uint32_t n_stop_times; - uint32_t n_vjs; - uint32_t n_journey_patterns_at_stop; - uint32_t n_transfer_target_stops; - uint32_t n_transfer_dist_meters; - uint32_t n_vj_active; - uint32_t n_journey_pattern_active; - uint32_t n_platformcodes; - /* length of the object in bytes */ - uint32_t n_stop_names; - uint32_t n_stop_nameidx; - uint32_t n_agency_ids; - uint32_t n_agency_names; - uint32_t n_agency_urls; - - - /* length of the object in bytes */ - uint32_t n_headsigns; - uint32_t n_line_codes; - uint32_t n_productcategories; - uint32_t n_line_ids; - uint32_t n_stop_ids; - uint32_t n_vj_ids; - uint32_t loc_stops; - uint32_t loc_stop_attributes; - uint32_t loc_stop_coords; - uint32_t loc_journey_patterns; - uint32_t loc_journey_pattern_points; - uint32_t loc_journey_pattern_point_attributes; - uint32_t loc_stop_times; - uint32_t loc_vjs; - uint32_t loc_journey_patterns_at_stop; - uint32_t loc_transfer_target_stops; - uint32_t loc_transfer_dist_meters; - uint32_t loc_vj_active; - uint32_t loc_journey_pattern_active; - uint32_t loc_platformcodes; - uint32_t loc_stop_names; - uint32_t loc_stop_nameidx; - uint32_t loc_agency_ids; - uint32_t loc_agency_names; - uint32_t loc_agency_urls; - uint32_t loc_headsigns; - uint32_t loc_line_codes; - uint32_t loc_productcategories; - uint32_t loc_line_ids; - uint32_t loc_stop_ids; - uint32_t loc_vj_ids; -}; - -bool tdata_io_v3_load(tdata_t *td, char* filename); -void tdata_io_v3_close(tdata_t *td); diff --git a/tdata_io_v3_dynamic.c b/tdata_io_v3_dynamic.c deleted file mode 100644 index 79e5442..0000000 --- a/tdata_io_v3_dynamic.c +++ /dev/null @@ -1,174 +0,0 @@ -/* Copyright 2013 Bliksem Labs. - * See the LICENSE file at the top-level directory of this distribution and at - * https://github.com/bliksemlabs/rrrr/ - */ - -#include "config.h" - -#ifdef RRRR_TDATA_IO_DYNAMIC - -#include "tdata_io_v3.h" -#include "tdata.h" -#include "rrrr_types.h" - -#include -#include -#include -#include -#include -#include -#include - - -#define load_dynamic(fd, storage, type) \ - td->n_##storage = header->n_##storage; \ - td->storage = (type*) malloc (sizeof(type) * RRRR_DYNAMIC_SLACK * (td->n_##storage + 1)); \ - if (lseek (fd, header->loc_##storage, SEEK_SET) == -1) goto fail_close_fd; \ - if (read (fd, td->storage, sizeof(type) * (td->n_##storage + 1)) != (ssize_t) (sizeof(type) * (td->n_##storage + 1))) goto fail_close_fd; - -#define load_dynamic_string(fd, storage) \ - td->n_##storage = header->n_##storage; \ - if (lseek (fd, header->loc_##storage, SEEK_SET) == -1) goto fail_close_fd; \ - if (read (fd, &td->storage##_width, sizeof(uint32_t)) != sizeof(uint32_t)) goto fail_close_fd; \ - if (!(td->storage##_width > 0 && td->storage##_width < UINT16_MAX)) goto fail_close_fd; \ - td->storage = (char*) malloc (((uint64_t) sizeof(char)) * RRRR_DYNAMIC_SLACK * td->n_##storage * td->storage##_width); \ - if (!td->storage) goto fail_close_fd; \ - if (read (fd, td->storage, ((uint64_t) sizeof(char)) * td->n_##storage * td->storage##_width) != (ssize_t) (((uint64_t) sizeof(char)) * td->n_##storage * td->storage##_width)) goto fail_close_fd; - -/* Set the maximum drivetime of any day in tdata */ -void set_max_time(tdata_t *td){ - uint32_t jp_index; - td->max_time = 0; - for (jp_index = 0; jp_index < td->n_journey_patterns; jp_index++){ - if (td->journey_patterns[jp_index].max_time > td->max_time) { - td->max_time = td->journey_patterns[jp_index].max_time; - } - } -} - -bool tdata_io_v3_load(tdata_t *td, char *filename) { - tdata_header_t h; - tdata_header_t *header = &h; - - int fd = open(filename, O_RDONLY); - if (fd == -1) { - fprintf(stderr, "The input file %s could not be found.\n", filename); - return false; - } - - if (read (fd, header, sizeof(tdata_header_t)) != sizeof(tdata_header_t)) { - goto fail_close_fd; - } - - td->base = NULL; - td->size = 0; - - if( strncmp("TTABLEV3", header->version_string, 8) ) { - fprintf(stderr, "The input file %s does not appear to be a timetable or is of the wrong version.\n", filename); - goto fail_close_fd; - } - - /* More input validation in the dynamic loading case. */ - if ( !( header->n_stops < ((spidx_t) -2) && - header->n_stop_attributes < ((spidx_t) -2) && - header->n_stop_coords < ((spidx_t) -2) && - header->n_journey_patterns < (UINT32_MAX - 1) && - header->n_journey_pattern_points < (UINT32_MAX) && - header->n_journey_pattern_point_attributes < (UINT32_MAX) && - header->n_stop_times < (UINT32_MAX) && - header->n_vjs < (UINT32_MAX) && - header->n_journey_patterns_at_stop < (UINT32_MAX) && - header->n_transfer_target_stops < (UINT32_MAX) && - header->n_transfer_dist_meters < (UINT32_MAX) && - header->n_vj_active < (UINT32_MAX) && - header->n_journey_pattern_active < (UINT32_MAX) && - header->n_platformcodes < (UINT32_MAX) && - header->n_stop_names < (UINT32_MAX) && - header->n_stop_nameidx < ((spidx_t) -2) && - header->n_agency_ids < (UINT16_MAX) && - header->n_agency_names < (UINT16_MAX) && - header->n_agency_urls < (UINT16_MAX) && - header->n_headsigns < (UINT32_MAX) && - header->n_line_codes < (UINT16_MAX) && - header->n_productcategories < (UINT16_MAX) && - header->n_line_ids < (UINT32_MAX) && - header->n_stop_ids < ((spidx_t) -2) && - header->n_vj_ids < (UINT32_MAX) ) ) { - - fprintf(stderr, "The input file %s does not appear to be a valid timetable.\n", filename); - goto fail_close_fd; - } - - td->calendar_start_time = header->calendar_start_time; - td->dst_active = header->dst_active; - - load_dynamic (fd, stops, stop_t); - load_dynamic (fd, stop_attributes, uint8_t); - load_dynamic (fd, stop_coords, latlon_t); - load_dynamic (fd, journey_patterns, journey_pattern_t); - load_dynamic (fd, journey_pattern_points, spidx_t); - load_dynamic (fd, journey_pattern_point_attributes, uint8_t); - load_dynamic (fd, stop_times, stoptime_t); - load_dynamic (fd, vjs, vehicle_journey_t); - load_dynamic (fd, journey_patterns_at_stop, uint32_t); - load_dynamic (fd, transfer_target_stops, spidx_t); - load_dynamic (fd, transfer_dist_meters, uint8_t); - load_dynamic (fd, vj_active, calendar_t); - load_dynamic (fd, journey_pattern_active, calendar_t); - load_dynamic (fd, headsigns, char); - load_dynamic (fd, stop_names, char); - load_dynamic (fd, stop_nameidx, uint32_t); - - load_dynamic_string (fd, platformcodes); - load_dynamic_string (fd, stop_ids); - load_dynamic_string (fd, vj_ids); - load_dynamic_string (fd, agency_ids); - load_dynamic_string (fd, agency_names); - load_dynamic_string (fd, agency_urls); - load_dynamic_string (fd, line_codes); - load_dynamic_string (fd, line_ids); - load_dynamic_string (fd, productcategories); - - set_max_time(td); - close (fd); - - return true; - -fail_close_fd: - close (fd); - - return false; -} - -void tdata_io_v3_close(tdata_t *td) { - free (td->stops); - free (td->stop_attributes); - free (td->stop_coords); - free (td->journey_patterns); - free (td->journey_pattern_points); - free (td->journey_pattern_point_attributes); - free (td->stop_times); - free (td->vjs); - free (td->journey_patterns_at_stop); - free (td->transfer_target_stops); - free (td->transfer_dist_meters); - free (td->vj_active); - free (td->journey_pattern_active); - free (td->headsigns); - free (td->stop_names); - free (td->stop_nameidx); - - free (td->platformcodes); - free (td->stop_ids); - free (td->vj_ids); - free (td->agency_ids); - free (td->agency_names); - free (td->agency_urls); - free (td->line_codes); - free (td->line_ids); - free (td->productcategories); -} - -#else -void tdata_io_v3_dynamic_not_available(); -#endif /* RRRR_TDATA_IO_DYNAMIC */ diff --git a/tdata_io_v3_mmap.c b/tdata_io_v3_mmap.c deleted file mode 100644 index 0c09f74..0000000 --- a/tdata_io_v3_mmap.c +++ /dev/null @@ -1,127 +0,0 @@ -/* Copyright 2013 Bliksem Labs. - * See the LICENSE file at the top-level directory of this distribution and at - * https://github.com/bliksemlabs/rrrr/ - */ - -#include "config.h" - -#ifdef RRRR_TDATA_IO_MMAP - -#include "tdata_io_v3.h" -#include "tdata.h" -#include "rrrr_types.h" - -#include -#include -#include -#include -#include -#include -#include - -#define load_mmap(b, storage, type) \ - td->n_##storage = header->n_##storage; \ - td->storage = (type *) (((char *) b) + header->loc_##storage) - -#define load_mmap_string(b, storage) \ - td->n_##storage = header->n_##storage; \ - td->storage##_width = *((uint32_t *) (((char *) b) + header->loc_##storage)); \ - td->storage = (char*) (((char *) b) + header->loc_##storage + sizeof(uint32_t)) - -/* Set the maximum drivetime of any day in tdata */ -void set_max_time(tdata_t *td){ - uint32_t jp_index; - td->max_time = 0; - for (jp_index = 0; jp_index < td->n_journey_patterns; jp_index++){ - if (td->journey_patterns[jp_index].max_time > td->max_time) { - td->max_time = td->journey_patterns[jp_index].max_time; - } - } -} - -/* Map an input file into memory and reconstruct pointers to its contents. */ -bool tdata_io_v3_load(tdata_t *td, char *filename) { - struct stat st; - tdata_header_t *header; - int fd; - - fd = open(filename, O_RDONLY); - if (fd == -1) { - fprintf(stderr, "The input file %s could not be found.\n", filename); - return false; - } - - if (stat(filename, &st) == -1) { - fprintf(stderr, "The input file %s could not be stat.\n", filename); - goto fail_close_fd; - } - - td->base = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); - td->size = st.st_size; - if (td->base == MAP_FAILED) { - fprintf(stderr, "The input file %s could not be mapped.\n", filename); - goto fail_close_fd; - } - - header = (tdata_header_t *) td->base; - if( strncmp("TTABLEV3", header->version_string, 8) ) { - fprintf(stderr, "The input file %s does not appear to be a timetable or is of the wrong version.\n", filename); - goto fail_munmap_base; - } - - td->calendar_start_time = header->calendar_start_time; - td->dst_active = header->dst_active; - - load_mmap (td->base, stops, stop_t); - load_mmap (td->base, stop_attributes, uint8_t); - load_mmap (td->base, stop_coords, latlon_t); - load_mmap (td->base, journey_patterns, journey_pattern_t); - load_mmap (td->base, journey_pattern_points, spidx_t); - load_mmap (td->base, journey_pattern_point_attributes, uint8_t); - load_mmap (td->base, stop_times, stoptime_t); - load_mmap (td->base, vjs, vehicle_journey_t); - load_mmap (td->base, journey_patterns_at_stop, uint32_t); - load_mmap (td->base, transfer_target_stops, spidx_t); - load_mmap (td->base, transfer_dist_meters, uint8_t); - load_mmap (td->base, vj_active, calendar_t); - load_mmap (td->base, journey_pattern_active, calendar_t); - load_mmap (td->base, headsigns, char); - load_mmap (td->base, stop_names, char); - load_mmap (td->base, stop_nameidx, uint32_t); - - load_mmap_string (td->base, platformcodes); - load_mmap_string (td->base, stop_ids); - load_mmap_string (td->base, vj_ids); - load_mmap_string (td->base, agency_ids); - load_mmap_string (td->base, agency_names); - load_mmap_string (td->base, agency_urls); - load_mmap_string (td->base, line_codes); - load_mmap_string (td->base, line_ids); - load_mmap_string (td->base, productcategories); - - /* Set the maximum drivetime of any day in tdata */ - set_max_time(td); - /* We must close the file descriptor otherwise we will - * leak it. Because mmap has created a reference to it - * there will not be a problem. - */ - close (fd); - - return true; - -fail_munmap_base: - munmap(td->base, td->size); - -fail_close_fd: - close(fd); - - return false; -} - -void tdata_io_v3_close(tdata_t *td) { - munmap(td->base, td->size); -} - -#else -void tdata_io_v3_mmap_not_available(); -#endif /* RRRR_TDATA_IO_MMAP */ diff --git a/tdata_io_v4.h b/tdata_io_v4.h new file mode 100644 index 0000000..f91c428 --- /dev/null +++ b/tdata_io_v4.h @@ -0,0 +1,111 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and at + * https://github.com/bliksemlabs/rrrr/ + */ + +#include "rrrr_types.h" +#include "tdata.h" + +/* file-visible struct */ +typedef struct tdata_header tdata_header_t; +struct tdata_header { + /* Contents must read "TTABLEV4" */ + char version_string[8]; + uint64_t calendar_start_time; + uint32_t timezone; + int32_t utc_offset; + uint32_t n_days; + uint32_t n_stop_points; + uint32_t n_stop_areas; + uint32_t n_stop_point_attributes; + uint32_t n_stop_point_coords; + uint32_t n_stop_area_coords; + uint32_t n_stop_area_for_stop_point; + uint32_t n_journey_patterns; + uint32_t n_journey_pattern_points; + uint32_t n_journey_pattern_point_attributes; + uint32_t n_journey_pattern_point_headsigns; + uint32_t n_stop_times; + uint32_t n_vjs; + uint32_t n_journey_patterns_at_stop; + uint32_t n_transfer_target_stops; + uint32_t n_transfer_durations; + uint32_t n_vj_active; + uint32_t n_journey_pattern_active; + uint32_t n_platformcodes; + uint32_t n_stop_point_nameidx; + uint32_t n_stop_area_nameidx; + uint32_t n_operator_ids; + uint32_t n_operator_names; + uint32_t n_operator_urls; + uint32_t n_commercial_mode_ids; + uint32_t n_commercial_mode_names; + uint32_t n_physical_mode_ids; + uint32_t n_physical_mode_names; + /* length of the object in bytes */ + uint32_t n_string_pool; + uint32_t n_line_codes; + uint32_t n_line_ids; + uint32_t n_line_colors; + uint32_t n_line_colors_text; + uint32_t n_line_names; + uint32_t n_stop_point_ids; + uint32_t n_stop_area_ids; + uint32_t n_stop_area_timezones; + uint32_t n_vj_ids; + uint32_t n_vj_time_offsets; + uint32_t n_line_for_route; + uint32_t n_operator_for_line; + uint32_t n_commercial_mode_for_jp; + uint32_t n_physical_mode_for_line; + uint32_t n_vehicle_journey_transfers_backward; + uint32_t n_vehicle_journey_transfers_forward; + uint32_t n_stop_point_waittime; + uint32_t loc_stop_points; + uint32_t loc_stop_point_attributes; + uint32_t loc_stop_point_coords; + uint32_t loc_journey_patterns; + uint32_t loc_journey_pattern_points; + uint32_t loc_journey_pattern_point_attributes; + uint32_t loc_journey_pattern_point_headsigns; + uint32_t loc_stop_times; + uint32_t loc_vjs; + uint32_t loc_journey_patterns_at_stop; + uint32_t loc_transfer_target_stops; + uint32_t loc_transfer_durations; + uint32_t loc_stop_point_waittime; + uint32_t loc_vehicle_journey_transfers_backward; + uint32_t loc_vehicle_journey_transfers_forward; + uint32_t loc_vj_active; + uint32_t loc_journey_pattern_active; + uint32_t loc_platformcodes; + uint32_t loc_stop_point_nameidx; + uint32_t loc_stop_area_nameidx; + uint32_t loc_line_for_route; + uint32_t loc_operator_for_line; + uint32_t loc_operator_ids; + uint32_t loc_operator_names; + uint32_t loc_operator_urls; + uint32_t loc_commercial_mode_ids; + uint32_t loc_commercial_mode_names; + uint32_t loc_commercial_mode_for_jp; + uint32_t loc_physical_mode_ids; + uint32_t loc_physical_mode_names; + uint32_t loc_physical_mode_for_line; + uint32_t loc_string_pool; + uint32_t loc_line_codes; + uint32_t loc_line_names; + uint32_t loc_line_ids; + uint32_t loc_line_colors; + uint32_t loc_line_colors_text; + uint32_t loc_stop_point_ids; + uint32_t loc_stop_area_ids; + uint32_t loc_stop_area_timezones; + uint32_t loc_vj_time_offsets; + uint32_t loc_vj_ids; + uint32_t loc_stop_area_coords; + uint32_t loc_stop_area_for_stop_point; +}; + +bool tdata_io_v4_load(tdata_t *td, char* filename); +void tdata_io_v4_close(tdata_t *td); diff --git a/tdata_io_v4_dynamic.c b/tdata_io_v4_dynamic.c new file mode 100644 index 0000000..4ea59f9 --- /dev/null +++ b/tdata_io_v4_dynamic.c @@ -0,0 +1,222 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and at + * https://github.com/bliksemlabs/rrrr/ + */ + +#include "config.h" + +#ifdef RRRR_TDATA_IO_DYNAMIC + +#include "tdata_io_v4.h" +#include "tdata.h" +#include "rrrr_types.h" + +#include +#include +#include +#include +#include +#include +#include + +#define load_dynamic(fd, storage, type) \ + td->n_##storage = header->n_##storage; \ + td->storage = (type*) malloc (sizeof(type) * RRRR_DYNAMIC_SLACK * (td->n_##storage + 1)); \ + if (lseek (fd, header->loc_##storage, SEEK_SET) == -1) goto fail_close_fd; \ + if (read (fd, td->storage, sizeof(type) * (td->n_##storage + 1)) != (ssize_t) (sizeof(type) * (td->n_##storage + 1))) goto fail_close_fd; + +/* Set the maximum drivetime of any day in tdata */ +static void set_max_time(tdata_t *td){ + jpidx_t jp_index; + td->max_time = 0; + for (jp_index = 0; jp_index < td->n_journey_patterns; jp_index++){ + if (td->journey_patterns[jp_index].max_time > td->max_time) { + td->max_time = td->journey_patterns[jp_index].max_time; + } + } +} + +bool tdata_io_v4_load(tdata_t *td, char *filename) { + tdata_header_t h; + tdata_header_t *header = &h; + + int fd = open(filename, O_RDONLY); + if (fd == -1) { + fprintf(stderr, "The input file %s could not be found.\n", filename); + return false; + } + + if (read (fd, header, sizeof(tdata_header_t)) != sizeof(tdata_header_t)) { + goto fail_close_fd; + } + + td->base = NULL; + td->size = 0; + + if( strncmp("TTABLEV4", header->version_string, 8) ) { + fprintf(stderr, "The input file %s does not appear to be a timetable or is of the wrong version.\n", filename); + goto fail_close_fd; + } + + /* More input validation in the dynamic loading case. */ + if ( !( header->n_stop_points < ((spidx_t) -2) && + header->n_stop_areas < ((spidx_t) -2) && + header->n_stop_point_attributes < ((spidx_t) -2) && + header->n_stop_point_coords < ((spidx_t) -2) && + header->n_stop_area_coords < ((spidx_t) -2) && + header->n_journey_patterns < ((jpidx_t) -1) && + header->n_journey_pattern_points < (UINT32_MAX) && + header->n_journey_pattern_point_attributes < (UINT32_MAX) && + header->n_journey_pattern_point_headsigns < (UINT32_MAX) && + header->n_stop_times < (UINT32_MAX) && + header->n_vjs < (UINT32_MAX) && + header->n_journey_patterns_at_stop < (UINT32_MAX) && + header->n_transfer_target_stops < (UINT32_MAX) && + header->n_transfer_durations < (UINT32_MAX) && + header->n_vj_active < (UINT32_MAX) && + header->n_journey_pattern_active < (UINT32_MAX) && + header->n_platformcodes < (UINT32_MAX) && + header->n_stop_point_nameidx < ((spidx_t) -2) && + header->n_stop_area_nameidx < ((spidx_t) -2) && + header->n_operator_ids < (UINT8_MAX) && + header->n_operator_names < (UINT8_MAX) && + header->n_operator_urls < (UINT8_MAX) && + header->n_string_pool < (UINT32_MAX) && + header->n_line_codes < (UINT16_MAX) && + header->n_commercial_mode_ids < (UINT16_MAX) && + header->n_commercial_mode_names < (UINT16_MAX) && + header->n_commercial_mode_for_jp < (UINT32_MAX) && + header->n_physical_mode_ids < (UINT16_MAX) && + header->n_physical_mode_names < (UINT16_MAX) && + header->n_physical_mode_for_line < (UINT16_MAX) && + header->n_stop_point_waittime < ((rtime_t) -2) && + header->n_vehicle_journey_transfers_backward < (UINT32_MAX) && + header->n_vehicle_journey_transfers_forward < (UINT32_MAX) && + header->n_line_ids < (UINT32_MAX) && + header->n_line_colors < (UINT32_MAX) && + header->n_line_colors_text < (UINT32_MAX) && + header->n_line_names < (UINT32_MAX) && + header->n_line_for_route < (UINT16_MAX) && + header->n_operator_for_line < (UINT16_MAX) && + header->n_stop_point_ids < ((spidx_t) -2) && + header->n_stop_area_ids < ((spidx_t) -2) && + header->n_vj_ids < (UINT32_MAX) ) && + header->n_vj_time_offsets < (UINT32_MAX) ) { + + fprintf(stderr, "The input file %s does not appear to be a valid timetable.\n", filename); + goto fail_close_fd; + } + + td->timezone = header->timezone; + td->calendar_start_time = header->calendar_start_time; + td->utc_offset = header->utc_offset; + td->n_days = header->n_days; + td->n_stop_areas = header->n_stop_areas; + + load_dynamic (fd, stop_points, stop_point_t); + load_dynamic (fd, stop_point_attributes, uint8_t); + load_dynamic (fd, stop_point_coords, latlon_t); + load_dynamic (fd, stop_area_coords, latlon_t); + load_dynamic (fd, stop_area_for_stop_point, spidx_t); + load_dynamic (fd, journey_patterns, journey_pattern_t); + load_dynamic (fd, journey_pattern_points, spidx_t); + load_dynamic (fd, journey_pattern_point_attributes, uint8_t); + load_dynamic (fd, journey_pattern_point_headsigns, uint32_t); + load_dynamic (fd, stop_times, stoptime_t); + load_dynamic (fd, vjs, vehicle_journey_t); + load_dynamic (fd, journey_patterns_at_stop, jpidx_t); + load_dynamic (fd, transfer_target_stops, spidx_t); + load_dynamic (fd, transfer_durations, rtime_t); + load_dynamic (fd, stop_point_waittime, rtime_t); + load_dynamic (fd, vehicle_journey_transfers_backward, vehicle_journey_ref_t); + load_dynamic (fd, vehicle_journey_transfers_forward, vehicle_journey_ref_t); + load_dynamic (fd, vj_active, calendar_t); + load_dynamic (fd, journey_pattern_active, calendar_t); + load_dynamic (fd, string_pool, char); + load_dynamic (fd, stop_point_nameidx, uint32_t); + load_dynamic (fd, stop_area_nameidx, uint32_t); + load_dynamic (fd, line_for_route, uint16_t); + load_dynamic (fd, operator_for_line, uint8_t); + load_dynamic (fd, commercial_mode_for_jp, uint16_t); + load_dynamic (fd, physical_mode_for_line, uint16_t); + load_dynamic (fd, line_codes, uint32_t); + load_dynamic (fd, line_colors, uint32_t); + load_dynamic (fd, line_colors_text, uint32_t); + load_dynamic (fd, line_names, uint32_t); + load_dynamic (fd, operator_ids, uint32_t); + load_dynamic (fd, operator_names, uint32_t); + load_dynamic (fd, operator_urls, uint32_t); + load_dynamic (fd, commercial_mode_ids, uint32_t); + load_dynamic (fd, commercial_mode_names, uint32_t); + load_dynamic (fd, physical_mode_ids, uint32_t); + load_dynamic (fd, physical_mode_names, uint32_t); + load_dynamic (fd, platformcodes, uint32_t); + load_dynamic (fd, line_ids, uint32_t); + load_dynamic (fd, stop_point_ids, uint32_t); + load_dynamic (fd, stop_area_ids, uint32_t); + load_dynamic (fd, stop_area_timezones, uint32_t); + load_dynamic (fd, vj_ids, uint32_t); + load_dynamic (fd, vj_time_offsets, int8_t); + + set_max_time(td); + close (fd); + + return true; + +fail_close_fd: + close (fd); + + return false; +} + +void tdata_io_v4_close(tdata_t *td) { + free (td->stop_points); + free (td->stop_point_attributes); + free (td->stop_point_coords); + free (td->stop_area_coords); + free (td->journey_patterns); + free (td->journey_pattern_points); + free (td->journey_pattern_point_attributes); + free (td->journey_pattern_point_headsigns); + free (td->stop_times); + free (td->vjs); + free (td->journey_patterns_at_stop); + free (td->transfer_target_stops); + free (td->transfer_durations); + free (td->stop_point_waittime); + free (td->vehicle_journey_transfers_backward); + free (td->vehicle_journey_transfers_forward); + free (td->vj_active); + free (td->journey_pattern_active); + free (td->string_pool); + free (td->stop_point_nameidx); + free (td->stop_area_nameidx); + free (td->line_for_route); + free (td->operator_for_line); + free (td->commercial_mode_for_jp); + free (td->physical_mode_for_line); + + free (td->platformcodes); + free (td->stop_point_ids); + free (td->stop_area_ids); + free (td->stop_area_timezones); + free (td->stop_area_for_stop_point); + free (td->vj_ids); + free (td->vj_time_offsets); + free (td->operator_ids); + free (td->operator_names); + free (td->operator_urls); + free (td->line_codes); + free (td->line_colors); + free (td->line_colors_text); + free (td->line_names); + free (td->line_ids); + free (td->commercial_mode_ids); + free (td->commercial_mode_names); + free (td->physical_mode_ids); + free (td->physical_mode_names); +} + +#else +void tdata_io_v4_dynamic_not_available(); +#endif /* RRRR_TDATA_IO_DYNAMIC */ diff --git a/tdata_io_v4_mmap.c b/tdata_io_v4_mmap.c new file mode 100644 index 0000000..16aa16f --- /dev/null +++ b/tdata_io_v4_mmap.c @@ -0,0 +1,149 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and at + * https://github.com/bliksemlabs/rrrr/ + */ + +#include "config.h" + +#ifdef RRRR_TDATA_IO_MMAP + +#include "tdata_io_v4.h" +#include +#include +#include +#include +#include +#include + +#define load_mmap(b, storage, type) \ + td->n_##storage = header->n_##storage; \ + td->storage = (type *) (((char *) b) + header->loc_##storage) + +/* Set the maximum drivetime of any day in tdata */ +static void set_max_time(tdata_t *td){ + jpidx_t jp_index; + td->max_time = 0; + for (jp_index = 0; jp_index < td->n_journey_patterns; jp_index++){ + if (td->journey_patterns[jp_index].max_time > td->max_time) { + td->max_time = td->journey_patterns[jp_index].max_time; + } + } +} + +/* Map an input file into memory and reconstruct pointers to its contents. */ +bool tdata_io_v4_load(tdata_t *td, char *filename) { + struct stat st; + tdata_header_t *header; + int fd; + + fd = open(filename, O_RDONLY); + if (fd == -1) { + fprintf(stderr, "The input file %s could not be found.\n", filename); + return false; + } + + if (stat(filename, &st) == -1) { + fprintf(stderr, "The input file %s could not be stat.\n", filename); + goto fail_close_fd; + } + + if (st.st_size < (long) sizeof(header)) { + fprintf(stderr, "The input file %s is too small.\n", filename); + goto fail_close_fd; + } + + /* we can cast off_t to size_t since we have checked it is > 0 */ + td->base = mmap(NULL, (size_t) st.st_size, PROT_READ, MAP_SHARED, fd, 0); + td->size = (size_t) st.st_size; + if (td->base == MAP_FAILED) { + fprintf(stderr, "The input file %s could not be mapped.\n", filename); + goto fail_close_fd; + } + + header = (tdata_header_t *) td->base; + if( strncmp("TTABLEV4", header->version_string, 8) ) { + fprintf(stderr, "The input file %s does not appear to be a timetable or is of the wrong version.\n", filename); + goto fail_munmap_base; + } + + td->timezone = header->timezone; + td->calendar_start_time = header->calendar_start_time; + td->utc_offset = header->utc_offset; + td->n_days = header->n_days; + td->n_stop_areas = header->n_stop_areas; + + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wcast-align" + load_mmap (td->base, stop_points, stop_point_t); + load_mmap (td->base, stop_point_attributes, uint8_t); + load_mmap (td->base, stop_point_coords, latlon_t); + load_mmap (td->base, stop_area_coords, latlon_t); + load_mmap (td->base, journey_patterns, journey_pattern_t); + load_mmap (td->base, journey_pattern_points, spidx_t); + load_mmap (td->base, journey_pattern_point_attributes, uint8_t); + load_mmap (td->base, journey_pattern_point_headsigns, uint32_t); + load_mmap (td->base, stop_times, stoptime_t); + load_mmap (td->base, vjs, vehicle_journey_t); + load_mmap (td->base, journey_patterns_at_stop, jpidx_t); + load_mmap (td->base, transfer_target_stops, spidx_t); + load_mmap (td->base, transfer_durations, rtime_t); + load_mmap (td->base, stop_point_waittime, rtime_t); + load_mmap (td->base, vj_active, calendar_t); + load_mmap (td->base, journey_pattern_active, calendar_t); + load_mmap (td->base, string_pool, char); + load_mmap (td->base, stop_point_nameidx, uint32_t); + load_mmap (td->base, stop_area_nameidx, uint32_t); + load_mmap (td->base, stop_area_for_stop_point, spidx_t); + load_mmap (td->base, line_for_route, uint16_t); + load_mmap (td->base, operator_for_line, uint8_t); + load_mmap (td->base, commercial_mode_for_jp, uint16_t); + load_mmap (td->base, physical_mode_for_line, uint16_t); + load_mmap (td->base, vehicle_journey_transfers_backward, vehicle_journey_ref_t); + load_mmap (td->base, vehicle_journey_transfers_forward, vehicle_journey_ref_t); + load_mmap (td->base, line_codes, uint32_t); + load_mmap (td->base, line_colors, uint32_t); + load_mmap (td->base, line_colors_text, uint32_t); + load_mmap (td->base, line_names, uint32_t); + load_mmap (td->base, operator_ids, uint32_t); + load_mmap (td->base, operator_names, uint32_t); + load_mmap (td->base, operator_urls, uint32_t); + load_mmap (td->base, commercial_mode_ids, uint32_t); + load_mmap (td->base, commercial_mode_names, uint32_t); + load_mmap (td->base, physical_mode_ids, uint32_t); + load_mmap (td->base, physical_mode_names, uint32_t); + load_mmap (td->base, platformcodes, uint32_t); + load_mmap (td->base, line_ids, uint32_t); + load_mmap (td->base, stop_point_ids, uint32_t); + load_mmap (td->base, stop_area_ids, uint32_t); + load_mmap (td->base, stop_area_timezones, uint32_t); + load_mmap (td->base, vj_ids, uint32_t); + load_mmap (td->base, vj_time_offsets, int8_t); + #pragma clang diagnostic pop + + /* Set the maximum drivetime of any day in tdata */ + set_max_time(td); + /* We must close the file descriptor otherwise we will + * leak it. Because mmap has created a reference to it + * there will not be a problem. + */ + close (fd); + + return true; + +fail_munmap_base: + munmap(td->base, td->size); + +fail_close_fd: + close(fd); + + return false; +} + +void tdata_io_v4_close(tdata_t *td) { + munmap(td->base, td->size); +} + +#else +void tdata_io_v4_mmap_not_available(); +#endif /* RRRR_TDATA_IO_MMAP */ + diff --git a/tdata_jit.c b/tdata_jit.c new file mode 100644 index 0000000..7adfd14 --- /dev/null +++ b/tdata_jit.c @@ -0,0 +1,662 @@ +#include "config.h" + +#ifdef RRRR_TDATA_IO_DYNAMIC + +#include "tdata_jit.h" +#include "index_journey_pattern_points.h" +#include "index_journey_patterns.h" +#include "index_vehicle_journeys.h" + +#include +#include +#include "string_pool.h" + +/* this code can't be used because we don't have the memory management in place */ + +bool tdata_transfers_new (tdata_t *td, uint32_t n) { + td->n_transfer_durations = n; + td->n_transfer_target_stops = n; + + td->transfer_target_stops = (spidx_t *) calloc (n, sizeof(spidx_t)); + td->transfer_durations = (rtime_t *) calloc (n, sizeof(rtime_t)); + + return (td->transfer_target_stops && td->transfer_durations); +} + +bool tdata_transfers_append (tdata_t *td, spidx_t orig, spidx_t *target_stops, rtime_t *durations, uint8_t n_stops) { + uint32_t n = td->n_transfer_target_stops; + + td->n_transfer_target_stops += n_stops; + td->n_transfer_durations += n_stops; + + memcpy (&td->transfer_target_stops[n], target_stops, sizeof(spidx_t) * n_stops); + memcpy (&td->transfer_durations[n], durations, sizeof(spidx_t) * n_stops); + + td->stop_points[orig].transfers_offset = n; + /* TODO: + td->stop_transfers_offset[orig] = n; + td->stop_transfers_len[orig] = n_stop; + */ + + return true; +} + +void tdata_transfers_free (tdata_t *td) { + td->n_transfer_durations = 0; + td->n_transfer_target_stops = 0; + + free (td->transfer_target_stops); + free (td->transfer_durations); + + td->transfer_target_stops = NULL; + td->transfer_durations = NULL; +} + + +bool tdata_stop_points_new (tdata_t *td, spidx_t n) { + td->n_stop_points = n; + + td->n_platformcodes = n; + td->n_stop_point_ids = n; + td->n_stop_point_coords = n; + td->n_stop_point_nameidx = n; + td->n_stop_point_waittime = n; + td->n_stop_point_attributes = n; + td->n_stop_area_for_stop_point = n; + + td->stop_points = (stop_point_t *) calloc (n + 1, sizeof(stop_point_t)); + td->platformcodes = (uint32_t *) calloc (n, sizeof(uint32_t)); + td->stop_point_ids = (uint32_t *) calloc (n, sizeof(uint32_t)); + td->stop_point_coords = (latlon_t *) calloc (n, sizeof(latlon_t)); + td->stop_point_nameidx = (uint32_t *) calloc (n, sizeof(uint32_t)); + td->stop_point_waittime = (rtime_t *) calloc (n, sizeof(rtime_t)); + td->stop_point_attributes = (uint8_t *) calloc (n, sizeof(uint8_t)); + td->stop_area_for_stop_point = (spidx_t *) calloc (n, sizeof(spidx_t)); + + return (td->stop_points && td->stop_point_ids && td->stop_point_coords && + td->stop_point_nameidx && td->stop_point_waittime && + td->stop_point_attributes && td->stop_area_for_stop_point); +} + +void tdata_stop_point_append (tdata_t *td, + const char *id, + const char *name, + const char *platformcode, + const latlon_t *coord, + const rtime_t waittime, + const spidx_t stop_area, + const uint8_t attributes) { + + spidx_t n = (spidx_t) td->n_stop_points; + + td->n_stop_points++; + td->n_platformcodes++; + td->n_stop_point_ids++; + td->n_stop_point_coords++; + td->n_stop_point_nameidx++; + td->n_stop_point_waittime++; + td->n_stop_point_attributes++; + td->n_stop_area_for_stop_point++; + + /* Als er geen transfers zijn, waar verwijst de transfer offset dan naar? */ + + td->platformcodes[n] = tdata_string_pool_append (td, platformcode); + td->stop_point_ids[n] = tdata_string_pool_append (td, id); + memcpy (&td->stop_point_coords[n], coord, sizeof(latlon_t)); + td->stop_point_nameidx[n] = tdata_string_pool_append (td, name); + td->stop_point_waittime[n] = waittime; + td->stop_point_attributes[n] = attributes; + td->stop_area_for_stop_point[n] = stop_area; +} + +/* TODO: we could optimise this function, if we implement + * a custom qsort (or better: swap function) which allows + * modifications on two columns instead of one. + * An alternative is creating a list of uint32_t and keep + * the order in that list, but requires more lookups. + */ +bool tdata_journey_patterns_at_stop (tdata_t *td) { + uint32_t *journey_patterns_at_stop_point_offset; + jpidx_t *journey_patterns_at_stop; + uint32_t n_journey_patterns_at_stop; + bool result; + + result = index_journey_pattern_points (td, &journey_patterns_at_stop, &n_journey_patterns_at_stop, &journey_patterns_at_stop_point_offset); + if (result) { + /* keep legacy compatibility now */ + spidx_t sp_index = (spidx_t) td->n_stop_points; + while (sp_index > 0) { + sp_index--; + td->stop_points[sp_index].journey_patterns_at_stop_point_offset = journey_patterns_at_stop_point_offset[sp_index]; + } + free (journey_patterns_at_stop_point_offset); + + /* but ideally do the column store dance */ + td->journey_patterns_at_stop = journey_patterns_at_stop; + } + + return result; +} + +void tdata_stop_points_free (tdata_t *td) { + td->n_stop_points = 0; + + td->n_stop_point_ids = 0; + td->n_stop_point_coords = 0; + td->n_stop_point_nameidx = 0; + td->n_stop_point_waittime = 0; + td->n_stop_point_attributes = 0; + td->n_stop_area_for_stop_point = 0; + + free (td->stop_points); + free (td->stop_point_ids); + free (td->stop_point_coords); + free (td->stop_point_nameidx); + free (td->stop_point_waittime); + free (td->stop_point_attributes); + free (td->stop_area_for_stop_point); + + td->stop_points = NULL; + td->stop_point_ids = NULL; + td->stop_point_coords = NULL; + td->stop_point_nameidx = NULL; + td->stop_point_waittime = NULL; + td->stop_point_attributes = NULL; + td->stop_area_for_stop_point = NULL; +} + +bool tdata_stop_areas_new (tdata_t *td, spidx_t n) { + td->n_stop_areas = n; + + td->n_stop_area_ids = n; + td->n_stop_area_coords = n; + td->n_stop_area_nameidx = n; + td->n_stop_area_timezones = n; + + td->stop_area_ids = (uint32_t *) calloc (n, sizeof(uint32_t)); + td->stop_area_coords = (latlon_t *) calloc (n, sizeof(latlon_t)); + td->stop_area_nameidx = (uint32_t *) calloc (n, sizeof(uint32_t)); + td->stop_area_timezones = (uint32_t *) calloc (n, sizeof(uint32_t)); + + return (td->stop_area_coords && td->stop_area_nameidx && + td->stop_area_ids && td->stop_area_timezones); +} + +void tdata_stop_area_append (tdata_t *td, + const char *id, + const char *name, + const char *timezone, + const latlon_t *coord) { + + /* current highest value */ + spidx_t n = (spidx_t) td->n_stop_areas; + + td->n_stop_areas++; + td->n_stop_area_ids++; + td->n_stop_area_coords++; + td->n_stop_area_nameidx++; + td->n_stop_area_timezones++; + + td->stop_area_ids[n] = tdata_string_pool_append (td, id); + memcpy (&td->stop_area_coords[n], coord, sizeof(latlon_t)); + td->stop_area_nameidx[n] = tdata_string_pool_append (td, name); + td->stop_area_timezones[n] = tdata_string_pool_append (td, timezone); +} + +void tdata_stop_areas_free (tdata_t *td) { + td->n_stop_areas = 0; + + td->n_stop_area_ids = 0; + td->n_stop_area_coords = 0; + td->n_stop_area_nameidx = 0; + td->n_stop_area_timezones = 0; + + free (td->stop_area_ids); + free (td->stop_area_coords); + free (td->stop_area_nameidx); + free (td->stop_area_timezones); + + td->stop_area_ids = NULL; + td->stop_area_coords = NULL; + td->stop_area_nameidx = NULL; + td->stop_area_timezones = NULL; +} + +bool tdata_journey_patterns_new (tdata_t *td, jpidx_t n_jp, jppidx_t n_jpp, uint16_t n_r) { + td->n_journey_patterns = n_jp; + + td->n_line_for_route = n_r; + td->n_journey_pattern_active = n_jp; + td->n_commercial_mode_for_jp = n_jp; + + td->journey_patterns = (journey_pattern_t *) calloc (n_jp, sizeof(journey_pattern_t)); + td->line_for_route = (uint16_t *) calloc (n_r, sizeof(uint16_t)); + td->journey_pattern_active = (calendar_t *) calloc (n_jp, sizeof(calendar_t)); + td->commercial_mode_for_jp = (uint16_t *) calloc (n_jp, sizeof(uint16_t)); + + td->n_journey_pattern_points = n_jpp; + td->n_journey_pattern_point_headsigns = n_jpp; + td->n_journey_pattern_point_attributes = n_jpp; + + td->journey_pattern_points = (spidx_t *) calloc (n_jpp, sizeof(spidx_t)); + td->journey_pattern_point_headsigns = (uint32_t *) calloc (n_jpp, sizeof(uint32_t)); + td->journey_pattern_point_attributes = (uint8_t *) calloc (n_jpp, sizeof(uint8_t)); + + return (td->journey_patterns && td->journey_pattern_active && + td->commercial_mode_for_jp && + td->journey_pattern_points && td->journey_pattern_point_headsigns && + td->journey_pattern_point_attributes); +} + +bool tdata_journey_pattern_points_append (tdata_t *td, spidx_t *journey_pattern_points, uint8_t *journey_pattern_point_attributes, uint32_t *journey_pattern_point_headsigns, jppidx_t n_stops) { + uint32_t n = td->n_journey_pattern_points; + + td->n_journey_pattern_points += n_stops; + td->n_journey_pattern_point_headsigns += n_stops; + td->n_journey_pattern_point_attributes += n_stops; + + memcpy (&td->journey_pattern_points[n], journey_pattern_points, sizeof(spidx_t) * n_stops); + memcpy (&td->journey_pattern_point_headsigns[n], journey_pattern_point_headsigns, sizeof(spidx_t) * n_stops); + memcpy (&td->journey_pattern_point_attributes[n], journey_pattern_point_attributes, sizeof(spidx_t) * n_stops); + + return true; +} + +bool tdata_journey_patterns_append (tdata_t *td, uint32_t jpp_offset, vjidx_t vj_index, jppidx_t n_stops, jp_vjoffset_t n_vjs, uint16_t attributes, routeidx_t route_index, uint16_t commercial_mode, lineidx_t line) { + jpidx_t n = (jpidx_t) td->n_journey_patterns; + + td->n_journey_patterns++; + + td->journey_patterns[n].journey_pattern_point_offset = jpp_offset; + td->journey_patterns[n].n_stops = n_stops; + td->journey_patterns[n].vj_index = vj_index; + td->journey_patterns[n].n_vjs = n_vjs; + td->journey_patterns[n].attributes = attributes; + td->journey_patterns[n].route_index = route_index; + + td->line_for_route[n] = line; + td->commercial_mode_for_jp[n] = commercial_mode; + + /* if we increase the n_journey_patterns here the index must be updated as well */ + index_vehicle_journeys (td, n, td->journey_pattern_active, td->journey_pattern_min, td->journey_pattern_max); + + return true; +} + +bool tdata_journey_patterns_index (tdata_t *td) { + return index_journey_patterns (td, &td->journey_pattern_active, &td->journey_pattern_min, &td->journey_pattern_max, &td->max_time); +} + +void tdata_journey_patterns_free (tdata_t *td) { + td->n_line_for_route = 0; + td->n_journey_patterns = 0; + td->n_journey_pattern_active = 0; + td->n_commercial_mode_for_jp = 0; + td->n_journey_pattern_points = 0; + td->n_journey_pattern_point_attributes = 0; + td->n_journey_pattern_point_headsigns = 0; + + free (td->journey_patterns); + free (td->journey_patterns); + free (td->journey_pattern_active); + free (td->commercial_mode_for_jp); + free (td->journey_pattern_points); + free (td->journey_pattern_point_headsigns); + free (td->journey_pattern_point_attributes); + + td->journey_patterns = NULL; + td->journey_pattern_active = NULL; + td->commercial_mode_for_jp = NULL; + td->journey_pattern_points = NULL; + td->journey_pattern_point_headsigns = NULL; + td->journey_pattern_point_attributes = NULL; +} + +bool tdata_vehicle_journeys_new (tdata_t *td, vjidx_t n) { + td->n_vjs = n; + td->n_vj_ids = n; + td->n_vj_active = n; + td->n_vj_time_offsets = n; + td->n_vehicle_journey_transfers_forward = n; + td->n_vehicle_journey_transfers_backward = n; + + td->vjs = (vehicle_journey_t *) calloc (n, sizeof(vehicle_journey_t)); + td->vj_ids = (uint32_t *) calloc (n, sizeof(uint32_t)); + td->vj_active = (calendar_t *) calloc (n, sizeof(uint32_t)); + td->vj_time_offsets = (int8_t *) calloc (n, sizeof(int8_t)); + td->vehicle_journey_transfers_forward = (vehicle_journey_ref_t *) calloc (n, sizeof(vehicle_journey_ref_t)); + td->vehicle_journey_transfers_backward = (vehicle_journey_ref_t *) calloc (n, sizeof(vehicle_journey_ref_t)); + + return (td->vjs && td->vj_ids && td->vj_active && + td->vj_time_offsets && + td->vehicle_journey_transfers_forward && + td->vehicle_journey_transfers_backward); +} + +bool tdata_vehicle_journey_append (tdata_t *td, + const char *id, + const uint32_t stop_times_offset, + const rtime_t begin_time, + const vj_attribute_mask_t attributes, + const calendar_t active, + const int8_t time_offset, + const vehicle_journey_ref_t transfer_forward, + const vehicle_journey_ref_t transfer_backward) { + vjidx_t n = (vjidx_t) td->n_vjs; + + td->n_vjs++; + td->n_vj_ids++; + td->n_vj_active++; + td->n_vj_time_offsets++; + td->n_vehicle_journey_transfers_forward++; + td->n_vehicle_journey_transfers_backward++; + + td->vjs[n].stop_times_offset = stop_times_offset; + td->vjs[n].begin_time = begin_time; + td->vjs[n].vj_attributes = attributes; + + td->vj_ids[n] = tdata_string_pool_append (td, id); + td->vj_active[n] = active; + td->vj_time_offsets[n] = time_offset; + td->vehicle_journey_transfers_forward[n] = transfer_forward; + td->vehicle_journey_transfers_backward[n] = transfer_backward; + + return true; +} + +void tdata_vehicle_journeys_free (tdata_t *td) { + td->n_vjs = 0; + td->n_vj_ids = 0; + td->n_vj_active = 0; + td->n_vj_time_offsets = 0; + td->n_vehicle_journey_transfers_forward = 0; + td->n_vehicle_journey_transfers_backward = 0; + + free (td->vjs); + free (td->vj_ids); + free (td->vj_active); + free (td->vj_time_offsets); + free (td->vehicle_journey_transfers_forward); + free (td->vehicle_journey_transfers_backward); + + td->vjs = NULL; + td->vj_ids = NULL; + td->vj_active = NULL; + td->vj_time_offsets = NULL; + td->vehicle_journey_transfers_forward = NULL; + td->vehicle_journey_transfers_backward = NULL; +} + +bool tdata_operators_new (tdata_t *td, uint8_t n) { + td->n_operator_ids = n; + td->n_operator_urls = n; + td->n_operator_names = n; + + td->operator_ids = (uint32_t *) calloc (n, sizeof(uint32_t)); + td->operator_urls = (uint32_t *) calloc (n, sizeof(uint32_t)); + td->operator_names = (uint32_t *) calloc (n, sizeof(uint32_t)); + + return (td->operator_ids && td->operator_urls && td->operator_names); +} + +bool tdata_operators_append (tdata_t *td, + const char *id, + const char *url, + const char *name) { + opidx_t n = (opidx_t) td->n_operator_ids; + td->n_operator_ids++; + td->n_operator_urls++; + td->n_operator_names++; + + td->operator_ids[n] = tdata_string_pool_append (td, id); + td->operator_urls[n] = tdata_string_pool_append (td, url); + td->operator_names[n] = tdata_string_pool_append (td, name); + + return true; +} + +void tdata_operators_free (tdata_t *td) { + td->n_operator_ids = 0; + td->n_operator_urls = 0; + td->n_operator_names = 0; + + free (td->operator_ids); + free (td->operator_urls); + free (td->operator_names); + + td->operator_ids = NULL; + td->operator_urls = NULL; + td->operator_names = NULL; +} + +bool tdata_commercial_modes_new (tdata_t *td, uint16_t n) { + td->n_commercial_mode_ids = n; + td->n_commercial_mode_names = n; + + td->commercial_mode_ids = (uint32_t *) calloc (n, sizeof(uint32_t)); + td->commercial_mode_names = (uint32_t *) calloc (n, sizeof(uint32_t)); + + return (td->commercial_mode_ids && td->commercial_mode_names); +} + +bool tdata_commercial_modes_append (tdata_t *td, + const char *id, + const char *name) { + uint16_t n = (uint16_t) td->n_commercial_mode_ids; + td->n_commercial_mode_ids++; + td->n_commercial_mode_names++; + + td->commercial_mode_ids[n] = tdata_string_pool_append (td, id); + td->commercial_mode_names[n] = tdata_string_pool_append (td, name); + + return true; +} + +void tdata_commercial_modes_free (tdata_t *td) { + td->n_commercial_mode_ids = 0; + td->n_commercial_mode_names = 0; + + free (td->commercial_mode_ids); + free (td->commercial_mode_names); + + td->commercial_mode_ids = NULL; + td->commercial_mode_names = NULL; +} + +bool tdata_physical_modes_new (tdata_t *td, uint16_t n) { + td->n_physical_mode_ids = n; + td->n_physical_mode_names = n; + + td->physical_mode_ids = (uint32_t *) calloc (n, sizeof(uint32_t)); + td->physical_mode_names = (uint32_t *) calloc (n, sizeof(uint32_t)); + + return (td->physical_mode_ids && td->physical_mode_names); +} + +bool tdata_physical_modes_append (tdata_t *td, + const char *id, + const char *name) { + uint16_t n = (uint16_t) td->n_physical_mode_ids; + + td->n_physical_mode_ids++; + td->n_physical_mode_names++; + + td->physical_mode_ids[n] = tdata_string_pool_append (td, id); + td->physical_mode_names[n] = tdata_string_pool_append (td, name); + + return true; +} + +void tdata_physical_modes_free (tdata_t *td) { + td->n_physical_mode_ids = 0; + td->n_physical_mode_names = 0; + + free (td->physical_mode_ids); + free (td->physical_mode_names); + + td->physical_mode_ids = NULL; + td->physical_mode_names = NULL; +} + +bool tdata_lines_new (tdata_t *td, uint32_t n) { + td->n_line_ids = n; + td->n_line_codes = n; + td->n_line_names = n; + td->n_line_colors = n; + td->n_line_colors_text = n; + td->n_operator_for_line = n; + td->n_physical_mode_for_line = n; + + td->line_ids = (uint32_t *) calloc (n, sizeof(uint32_t)); + td->line_codes = (uint32_t *) calloc (n, sizeof(uint32_t)); + td->line_names = (uint32_t *) calloc (n, sizeof(uint32_t)); + td->line_colors = (uint32_t *) calloc (n, sizeof(uint32_t)); + td->line_colors_text = (uint32_t *) calloc (n, sizeof(uint32_t)); + td->operator_for_line = (uint8_t *) calloc (n, sizeof(uint8_t)); + td->physical_mode_for_line = (uint16_t *) calloc (n, sizeof(uint16_t)); + + return (td->line_ids && td->line_codes && td->line_names && + td->line_colors && td->line_colors_text && + td->physical_mode_for_line); +} + +bool tdata_line_append (tdata_t *td, + const char *id, + const char *code, + const char *name, + const char *color, + const char *color_text, + const uint8_t operator, + const uint16_t physical_mode) { + + uint16_t n = (uint16_t) td->n_line_ids; + + td->n_physical_mode_ids++; + td->n_line_codes++; + td->n_line_names++; + td->n_line_colors++; + td->n_line_colors_text++; + td->n_operator_for_line++; + td->n_physical_mode_for_line++; + + td->line_ids[n] = tdata_string_pool_append (td, id); + td->line_codes[n] = tdata_string_pool_append (td, code); + td->line_names[n] = tdata_string_pool_append (td, name); + td->line_colors[n] = tdata_string_pool_append (td, color); + td->line_colors_text[n] = tdata_string_pool_append (td, color_text); + + td->operator_for_line[n] = operator; + td->physical_mode_for_line[n] = physical_mode; + + return true; +} + +void tdata_lines_free (tdata_t *td) { + td->n_line_ids = 0; + td->n_line_codes = 0; + td->n_line_names = 0; + td->n_line_colors = 0; + td->n_line_colors_text = 0; + td->n_operator_for_line = 0; + td->n_physical_mode_for_line = 0; + + free (td->line_ids); + free (td->line_codes); + free (td->line_names); + free (td->line_colors); + free (td->line_colors_text); + free (td->operator_for_line); + free (td->physical_mode_for_line); + + td->line_ids = NULL; + td->line_codes = NULL; + td->line_names = NULL; + td->line_colors = NULL; + td->line_colors_text = NULL; + td->operator_for_line = NULL; + td->physical_mode_for_line = NULL; +} + +bool tdata_stop_times_new (tdata_t *td, uint32_t n) { + td->n_stop_times = n; + + td->stop_times = (stoptime_t *) calloc (n, sizeof(stoptime_t)); + + return (td->stop_times); +} + +bool tdata_stop_times_append (tdata_t *td, stoptime_t *stop_times, uint32_t n_stop_times) { + uint32_t n = td->n_stop_times; + + td->n_stop_times += n_stop_times; + + memcpy (&td->stop_times[n], stop_times, sizeof(stoptime_t) * n_stop_times); + + return true; +} + +void tdata_stop_times_free (tdata_t *td) { + td->n_stop_times = 0; + + free (td->stop_times); + + td->stop_times = NULL; +} + +bool tdata_string_pool_new (tdata_t *td, uint32_t n) { + td->n_string_pool = n; + + td->string_pool = (char *) calloc (n, sizeof(char)); + + return (td->string_pool); +} + +uint32_t tdata_string_pool_append (tdata_t *td, const char *str) { + return string_pool_append(td->string_pool, &td->n_string_pool, td->stringpool_index, str); +} + +void tdata_string_pool_free (tdata_t *td) { + td->n_string_pool = 0; + + free (td->string_pool); + + td->string_pool = NULL; +} + +bool tdata_jit_new (tdata_t *td, + const char *timezone, + uint64_t calendar_start_time, + int32_t utc_offset, + rtime_t max_time, + uint32_t n_days + ) { + tdata_string_pool_new (td, 65535); + + td->timezone = tdata_string_pool_append (td, timezone); + td->calendar_start_time = calendar_start_time; + td->utc_offset = utc_offset; + td->max_time = max_time; + td->n_days = n_days; + + return true; +} + +void tdata_jit_free (tdata_t *td) { + /* tdata_route_free (td); */ + + tdata_lines_free (td); + tdata_physical_modes_free (td); + tdata_journey_patterns_free (td); + tdata_commercial_modes_free (td); + tdata_vehicle_journeys_free (td); + tdata_stop_times_free (td); + + tdata_stop_points_free (td); + tdata_stop_areas_free (td); + tdata_journey_patterns_free (td); + + free (td); +} +#else +void tdata_jit_not_available(); +#endif /* RRRR_TDATA_IO_DYNAMIC */ diff --git a/tdata_jit.h b/tdata_jit.h new file mode 100644 index 0000000..9fdca61 --- /dev/null +++ b/tdata_jit.h @@ -0,0 +1,102 @@ +#include "config.h" + +#ifdef RRRR_TDATA_IO_DYNAMIC +#include "tdata.h" + +bool tdata_journey_patterns_at_stop (tdata_t *td); +bool tdata_journey_patterns_index (tdata_t *td); + +bool tdata_journey_pattern_points_append (tdata_t *td, spidx_t *journey_pattern_points, uint8_t *journey_pattern_point_attributes, uint32_t *journey_pattern_point_headsigns, jppidx_t n_stops); + +bool tdata_string_pool_new (tdata_t *td, uint32_t n); +void tdata_string_pool_free (tdata_t *td); + +uint32_t tdata_string_pool_append (tdata_t *tdata, const char *str); + +bool tdata_stop_areas_new (tdata_t *td, spidx_t n); +void tdata_stop_areas_free (tdata_t *td); + +void tdata_stop_area_append (tdata_t *td, + const char *id, + const char *name, + const char *timezone, + const latlon_t *coord); + +bool tdata_stop_points_new (tdata_t *td, spidx_t n); +void tdata_stop_points_free (tdata_t *td); + +void tdata_stop_point_append (tdata_t *td, + const char *id, + const char *name, + const char *platformcode, + const latlon_t *coord, + const rtime_t waittime, + const spidx_t stop_area, + const uint8_t attributes); + +bool tdata_operators_new (tdata_t *td, uint8_t n); +void tdata_operators_free (tdata_t *td); +bool tdata_operators_append (tdata_t *td, + const char *id, + const char *url, + const char *name); + + +bool tdata_physical_modes_new (tdata_t *td, uint16_t n); +void tdata_physical_modes_free (tdata_t *td); +bool tdata_physical_modes_append (tdata_t *td, + const char *id, + const char *name); + +bool tdata_lines_new (tdata_t *td, uint32_t n); +void tdata_lines_free (tdata_t *td); +bool tdata_line_append (tdata_t *td, + const char *id, + const char *code, + const char *name, + const char *color, + const char *color_text, + const uint8_t operator, + const uint16_t physical_mode); + +bool tdata_commercial_modes_new (tdata_t *td, uint16_t n); +void tdata_commercial_modes_free (tdata_t *td); +bool tdata_commercial_modes_append (tdata_t *td, + const char *id, + const char *name); + +bool tdata_journey_patterns_new (tdata_t *td, jpidx_t n_jp, jppidx_t n_jpp, uint16_t n_r); +bool tdata_journey_patterns_append (tdata_t *td, uint32_t jpp_offset, vjidx_t vj_index, jppidx_t n_stops, jp_vjoffset_t n_vjs, uint16_t attributes, uint16_t route_index, uint16_t commercial_mode, lineidx_t line); +void tdata_journey_patterns_free (tdata_t *td); + +bool tdata_vehicle_journeys_new (tdata_t *td, vjidx_t n); +void tdata_vehicle_journeys_free (tdata_t *td); +bool tdata_vehicle_journey_append (tdata_t *td, + const char *id, + const uint32_t stop_times_offset, + const rtime_t begin_time, + const vj_attribute_mask_t attributes, + const calendar_t active, + const int8_t time_offset, + const vehicle_journey_ref_t transfer_forward, + const vehicle_journey_ref_t transfer_backward); + +bool tdata_stop_times_new (tdata_t *td, uint32_t n); +void tdata_stop_times_free (tdata_t *td); +bool tdata_stop_times_append (tdata_t *td, stoptime_t *stop_times, uint32_t n_stop_times); + +bool tdata_transfers_new (tdata_t *td, uint32_t n); +bool tdata_transfers_append (tdata_t *td, spidx_t orig, spidx_t *target_stops, rtime_t *durations, uint8_t n_stops); +void tdata_transfers_free (tdata_t *td); + +bool tdata_jit_new (tdata_t *td, + const char *timezone, + uint64_t calendar_start_time, + int32_t utc_offset, + rtime_t max_time, + uint32_t n_days + ); + +void tdata_jit_free (tdata_t *td); + +#endif diff --git a/tdata_realtime_alerts.c b/tdata_realtime_alerts.c index 714283f..1599a21 100644 --- a/tdata_realtime_alerts.c +++ b/tdata_realtime_alerts.c @@ -1,4 +1,4 @@ -/* Copyright 2013 Bliksem Labs. +/* Copyright 2013-2015 Bliksem Labs B.V. * See the LICENSE file at the top-level directory of this distribution and at * https://github.com/bliksemlabs/rrrr/ */ @@ -8,17 +8,12 @@ #ifdef RRRR_FEATURE_REALTIME_ALERTS #include "tdata_realtime_alerts.h" -#include "radixtree.h" -#include "gtfs-realtime.pb-c.h" -#include "rrrr_types.h" #include #include -#include #include #include #include -#include void tdata_apply_gtfsrt_alerts (tdata_t *tdata, uint8_t *buf, size_t len) { size_t e; @@ -53,8 +48,8 @@ void tdata_apply_gtfsrt_alerts (tdata_t *tdata, uint8_t *buf, size_t len) { if (!informed_entity) continue; if (informed_entity->route_id) { - uint32_t jp_index = radixtree_find (tdata->lineid_index, - informed_entity->route_id); + uint32_t jp_index = radixtree_find_exact (tdata->lineid_index, + informed_entity->route_id); /*TODO This only applies the alert to one of the journey_patterns in the line/route.*/ #ifdef RRRR_DEBUG if (jp_index == RADIXTREE_NONE) { @@ -63,25 +58,25 @@ void tdata_apply_gtfsrt_alerts (tdata_t *tdata, uint8_t *buf, size_t len) { } #endif - *(informed_entity->route_id) = jp_index; + *(informed_entity->route_id) = (char) jp_index; } if (informed_entity->stop_id) { - uint32_t stop_index = radixtree_find (tdata->stopid_index, - informed_entity->stop_id); + uint32_t sp_index = radixtree_find_exact (tdata->stop_point_id_index, + informed_entity->stop_id); #ifdef RRRR_DEBUG - if (stop_index == RADIXTREE_NONE) { + if (sp_index == RADIXTREE_NONE) { fprintf (stderr, " stop id was not found in the radix tree.\n"); } #endif - *(informed_entity->stop_id) = stop_index; + *(informed_entity->stop_id) = (char) sp_index; } if (informed_entity->trip && informed_entity->trip->trip_id) { - uint32_t trip_index = radixtree_find (tdata->vjid_index, - informed_entity->trip->trip_id); + uint32_t trip_index = radixtree_find_exact (tdata->vjid_index, + informed_entity->trip->trip_id); #ifdef RRRR_DEBUG if (trip_index == RADIXTREE_NONE) { fprintf (stderr, @@ -89,7 +84,7 @@ void tdata_apply_gtfsrt_alerts (tdata_t *tdata, uint8_t *buf, size_t len) { } #endif - *(informed_entity->trip->trip_id) = trip_index; + *(informed_entity->trip->trip_id) = (char) trip_index; } } } @@ -122,14 +117,14 @@ void tdata_apply_gtfsrt_alerts_file (tdata_t *tdata, char *filename) { goto fail_clean_fd; } - buf = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + buf = mmap(NULL, (size_t) st.st_size, PROT_READ, MAP_SHARED, fd, 0); if (buf == MAP_FAILED) { fprintf(stderr, "Could not mmap GTFS-RT input file %s.\n", filename); goto fail_clean_fd; } - tdata_apply_gtfsrt_alerts (tdata, buf, st.st_size); - munmap (buf, st.st_size); + tdata_apply_gtfsrt_alerts (tdata, buf, (size_t) st.st_size); + munmap (buf, (size_t) st.st_size); fail_clean_fd: close (fd); @@ -142,5 +137,5 @@ void tdata_clear_gtfsrt_alerts (tdata_t *tdata) { } } #else -void tdata_gtfsrt_alerts_not_available() {} +void tdata_gtfsrt_alerts_not_available(); #endif /* RRRR_FEATURE_REALTIME_ALERTS */ diff --git a/tdata_realtime_alerts.h b/tdata_realtime_alerts.h index 4dbcabf..378080f 100644 --- a/tdata_realtime_alerts.h +++ b/tdata_realtime_alerts.h @@ -1,4 +1,4 @@ -/* Copyright 2013 Bliksem Labs. +/* Copyright 2013-2015 Bliksem Labs B.V. * See the LICENSE file at the top-level directory of this distribution and at * https://github.com/bliksemlabs/rrrr/ */ diff --git a/tdata_realtime_expanded.c b/tdata_realtime_expanded.c index 51f2a2d..720b68a 100644 --- a/tdata_realtime_expanded.c +++ b/tdata_realtime_expanded.c @@ -1,4 +1,4 @@ -/* Copyright 2013 Bliksem Labs. +/* Copyright 2013-2015 Bliksem Labs B.V. * See the LICENSE file at the top-level directory of this distribution and at * https://github.com/bliksemlabs/rrrr/ */ @@ -8,70 +8,67 @@ #ifdef RRRR_FEATURE_REALTIME_EXPANDED #include "tdata_realtime_expanded.h" -#include "radixtree.h" -#include "gtfs-realtime.pb-c.h" -#include "rrrr_types.h" #include "util.h" +#include "string_pool.h" -#include #include #include #include -#include #include #include #include #include -#include -/* rt_journey_patterns_at_stop store the delta to the planned journey_patterns_at_stop */ -static void tdata_rt_journey_patterns_at_stop_append(tdata_t *tdata, - uint32_t stop_index, +/* rt_journey_patterns_at_stop_point store the delta to the planned journey_patterns_at_stop_point */ +static void tdata_rt_journey_patterns_at_stop_point_append(tdata_t *tdata, + uint32_t sp_index, uint32_t jp_index) { uint32_t i; - if (tdata->rt_journey_patterns_at_stop[stop_index]) { - for (i = 0; i < tdata->rt_journey_patterns_at_stop[stop_index]->len; ++i) { - if (((uint32_t *) tdata->rt_journey_patterns_at_stop[stop_index]->list)[i] == + if (tdata->rt_journey_patterns_at_stop_point[sp_index]) { + for (i = 0; i < tdata->rt_journey_patterns_at_stop_point[sp_index]->len; ++i) { + if (((uint32_t *) tdata->rt_journey_patterns_at_stop_point[sp_index]->list)[i] == jp_index) return; } } else { - tdata->rt_journey_patterns_at_stop[stop_index] = + tdata->rt_journey_patterns_at_stop_point[sp_index] = (list_t *) calloc(1, sizeof(list_t)); } - if (tdata->rt_journey_patterns_at_stop[stop_index]->len == - tdata->rt_journey_patterns_at_stop[stop_index]->size) { - tdata->rt_journey_patterns_at_stop[stop_index]->list = - realloc(tdata->rt_journey_patterns_at_stop[stop_index]->list, + if (tdata->rt_journey_patterns_at_stop_point[sp_index]->len == + tdata->rt_journey_patterns_at_stop_point[sp_index]->size) { + tdata->rt_journey_patterns_at_stop_point[sp_index]->list = + realloc(tdata->rt_journey_patterns_at_stop_point[sp_index]->list, sizeof(uint32_t) * - (tdata->rt_journey_patterns_at_stop[stop_index]->size + 8)); - tdata->rt_journey_patterns_at_stop[stop_index]->size += 8; + (tdata->rt_journey_patterns_at_stop_point[sp_index]->size + 8)); + tdata->rt_journey_patterns_at_stop_point[sp_index]->size += 8; } - ((uint32_t *) tdata->rt_journey_patterns_at_stop[stop_index]->list)[tdata->rt_journey_patterns_at_stop[stop_index]->len++] = jp_index; + ((uint32_t *) tdata->rt_journey_patterns_at_stop_point[sp_index]->list)[tdata->rt_journey_patterns_at_stop_point[sp_index]->len++] = jp_index; } -static void tdata_rt_journey_patterns_at_stop_remove(tdata_t *tdata, - uint32_t stop_index, +static void tdata_rt_journey_patterns_at_stop_point_remove(tdata_t *tdata, + uint32_t sp_index, uint32_t jp_index) { uint32_t i; - for (i = 0; i < tdata->rt_journey_patterns_at_stop[stop_index]->len; ++i) { - if (((uint32_t *) tdata->rt_journey_patterns_at_stop[stop_index]->list)[i] == + for (i = 0; i < tdata->rt_journey_patterns_at_stop_point[sp_index]->len; ++i) { + if (((uint32_t *) tdata->rt_journey_patterns_at_stop_point[sp_index]->list)[i] == jp_index) { - tdata->rt_journey_patterns_at_stop[stop_index]->len--; - ((uint32_t *) tdata->rt_journey_patterns_at_stop[stop_index]->list)[i] = - ((uint32_t *) tdata->rt_journey_patterns_at_stop[stop_index]->list)[tdata->rt_journey_patterns_at_stop[stop_index]->len]; + tdata->rt_journey_patterns_at_stop_point[sp_index]->len--; + ((uint32_t *) tdata->rt_journey_patterns_at_stop_point[sp_index]->list)[i] = + ((uint32_t *) tdata->rt_journey_patterns_at_stop_point[sp_index]->list)[tdata->rt_journey_patterns_at_stop_point[sp_index]->len]; return; } } } -static void tdata_apply_gtfsrt_time (TransitRealtime__TripUpdate__StopTimeUpdate *update, +static void tdata_apply_gtfsrt_time (tdata_t *tdata, + time_t startdate_midnight_utc, + TransitRealtime__TripUpdate__StopTimeUpdate *update, stoptime_t *stoptime) { if (update->arrival) { if (update->arrival->has_time) { - stoptime->arrival = epoch_to_rtime ((time_t) update->arrival->time, NULL) - RTIME_ONE_DAY; + stoptime->arrival = SEC_TO_RTIME(update->arrival->time - startdate_midnight_utc + tdata->utc_offset); } else if (update->arrival->has_delay) { stoptime->arrival += SEC_TO_RTIME(update->arrival->delay); } @@ -80,14 +77,14 @@ static void tdata_apply_gtfsrt_time (TransitRealtime__TripUpdate__StopTimeUpdate /* not mutually exclusive */ if (update->departure) { if (update->departure->has_time) { - stoptime->departure = epoch_to_rtime ((time_t) update->departure->time, NULL) - RTIME_ONE_DAY; + stoptime->departure = SEC_TO_RTIME(update->departure->time - startdate_midnight_utc + tdata->utc_offset); } else if (update->departure->has_delay) { stoptime->departure += SEC_TO_RTIME(update->departure->delay); } } } -static void tdata_realtime_free_vj_index(tdata_t *tdata, uint32_t vj_index) { +static void tdata_realtime_free_vj_index(tdata_t *tdata, vjidx_t vj_index) { if (tdata->vj_stoptimes[vj_index]) { free(tdata->vj_stoptimes[vj_index]); tdata->vj_stoptimes[vj_index] = NULL; @@ -104,12 +101,12 @@ static void tdata_realtime_free_vj_index(tdata_t *tdata, uint32_t vj_index) { /* Our datastructure requires us to commit on a fixed number of - * vehicle_journeys and a fixed number of stops in the journey_pattern. + * vehicle_journeys and a fixed number of stop_points in the journey_pattern. * Generally speaking, when a new journey_pattern is dynamically added, - * we will have only one vj, a list of stops in the journey_pattern. + * we will have only one vj, a list of stop_points in the journey_pattern. * * This call will preallocate, and fill the journey_pattern and matching - * vehicle_journeys, and wire them together. Stops and times will be added + * vehicle_journeys, and wire them together. stop_points and times will be added * later, they can be completely dynamic up to the allocated * length. * @@ -119,34 +116,39 @@ static void tdata_realtime_free_vj_index(tdata_t *tdata, uint32_t vj_index) { * will contain. */ -static uint32_t tdata_new_journey_pattern(tdata_t *tdata, char *vj_ids, - uint16_t n_stops, uint16_t n_vjs, - uint16_t attributes, uint32_t headsign_offset, - uint16_t agency_index, uint16_t line_code_index, - uint16_t productcategory_index) { - journey_pattern_t *new; +static jpidx_t tdata_new_journey_pattern(tdata_t *tdata, char *vj_ids, + uint16_t n_sp, uint16_t n_vjs, + uint16_t attributes, routeidx_t route_index) { + journey_pattern_t *new, *sentinel; uint32_t journey_pattern_point_offset = tdata->n_journey_pattern_points; uint32_t stop_times_offset = tdata->n_stop_times; - uint32_t vj_index = tdata->n_vjs; - uint16_t i_stop; + vjidx_t vj_index = (vjidx_t) tdata->n_vjs; + jpidx_t jp_index = (jpidx_t) tdata->n_journey_patterns; + spidx_t sp_index; uint16_t i_vj; new = &tdata->journey_patterns[tdata->n_journey_patterns]; new->journey_pattern_point_offset = journey_pattern_point_offset; - new->vj_ids_offset = vj_index; - new->headsign_offset = headsign_offset; - new->n_stops = n_stops; + new->vj_index = vj_index; + new->n_stops = n_sp; new->n_vjs = n_vjs; new->attributes = attributes; - new->agency_index = agency_index; - new->productcategory_index = productcategory_index; - new->line_code_index = line_code_index; + new->route_index = route_index; + + /* Move the sentinel journey pattern one to the right */ + sentinel = &tdata->journey_patterns[tdata->n_journey_patterns + 1]; + sentinel->journey_pattern_point_offset = journey_pattern_point_offset + n_sp; + sentinel->vj_index = 0; + sentinel->n_stops = 0; + sentinel->n_vjs = 0; + sentinel->attributes = 0; + sentinel->route_index = 0; tdata->vjs[vj_index].stop_times_offset = stop_times_offset; - for (i_stop = 0; i_stop < n_stops; ++i_stop) { - tdata->journey_pattern_points[journey_pattern_point_offset] = NONE; + for (sp_index = 0; sp_index < n_sp; ++sp_index) { + tdata->journey_pattern_points[journey_pattern_point_offset] = JPP_NONE; journey_pattern_point_offset++; /* Initialise the timetable */ @@ -156,42 +158,49 @@ static uint32_t tdata_new_journey_pattern(tdata_t *tdata, char *vj_ids, } - /* append the allocated vj_ids to the array */ - strncpy(&tdata->vj_ids[tdata->n_vjs * tdata->vj_ids_width], - vj_ids, tdata->vj_ids_width * n_vjs); + tdata->commercial_mode_for_jp[jp_index] = 0; + tdata->vehicle_journey_transfers_backward[jp_index].jp_index = JP_NONE; + tdata->vehicle_journey_transfers_forward[jp_index].jp_index = JP_NONE; /* add the last journey_pattern index to the lookup table */ for (i_vj = 0; i_vj < n_vjs; ++i_vj) { - tdata->vj_stoptimes[vj_index] = (stoptime_t *) malloc(sizeof(stoptime_t) * n_stops); + /* TODO: this doesn't handle multiple vjs! */ + tdata->vj_ids[vj_index] = string_pool_append (tdata->string_pool, &tdata->n_string_pool, tdata->stringpool_index, vj_ids); + + tdata->vj_stoptimes[vj_index] = (stoptime_t *) malloc(sizeof(stoptime_t) * n_sp); - for (i_stop = 0; i_stop < n_stops; ++i_stop) { + for (sp_index = 0; sp_index < n_sp; ++sp_index) { /* Initialise the realtime stoptimes */ - tdata->vj_stoptimes[vj_index][i_stop].arrival = UNREACHED; - tdata->vj_stoptimes[vj_index][i_stop].departure = UNREACHED; + tdata->vj_stoptimes[vj_index][sp_index].arrival = UNREACHED; + tdata->vj_stoptimes[vj_index][sp_index].departure = UNREACHED; } tdata->vjs[vj_index].begin_time = UNREACHED; tdata->vjs_in_journey_pattern[vj_index] = tdata->n_journey_patterns; + tdata->vj_active[vj_index] = 0; + tdata->vj_time_offsets[vj_index] = 0; vj_index++; - radixtree_insert(tdata->lineid_index, - &vj_ids[i_vj * tdata->vj_ids_width], + /* TODO: this doesn't handle multiple vjs! */ + radixtree_insert(tdata->lineid_index, vj_ids, tdata->n_journey_patterns); } /* housekeeping in tdata: increment for each new element */ - tdata->n_stop_times += n_stops; - tdata->n_journey_pattern_points += n_stops; - tdata->n_journey_pattern_point_attributes += n_stops; + tdata->n_stop_times += n_sp; + tdata->n_journey_pattern_points += n_sp; + tdata->n_journey_pattern_point_attributes += n_sp; + tdata->n_journey_pattern_point_headsigns += n_sp; tdata->n_vjs += n_vjs; tdata->n_vj_ids += n_vjs; tdata->n_vj_active += n_vjs; tdata->n_journey_pattern_active++; + tdata->n_journey_patterns++; - return tdata->n_journey_patterns++; + return jp_index; } -void tdata_apply_stop_time_update (tdata_t *tdata, uint32_t jp_index, uint32_t vj_index, TransitRealtime__TripUpdate *rt_trip_update) { +static void tdata_apply_stop_time_update (tdata_t *tdata, time_t startdate_midnight_utc, jpidx_t jp_index, vjidx_t vj_index, TransitRealtime__TripUpdate *rt_trip_update) { uint32_t journey_pattern_point_offset = tdata->journey_patterns[jp_index].journey_pattern_point_offset; uint32_t rs = 0; uint32_t i_stu; @@ -208,38 +217,41 @@ void tdata_apply_stop_time_update (tdata_t *tdata, uint32_t jp_index, uint32_t v char *stop_id = rt_stop_time_update->stop_id; if (stop_id) { - uint32_t stop_index = radixtree_find (tdata->stopid_index, stop_id); - if (tdata->journey_pattern_points[journey_pattern_point_offset] != stop_index && - tdata->journey_pattern_points[journey_pattern_point_offset] != NONE) { - tdata_rt_journey_patterns_at_stop_remove(tdata, tdata->journey_pattern_points[journey_pattern_point_offset], jp_index); + uint32_t sp_index = radixtree_find_exact (tdata->stop_point_id_index, stop_id); + if (tdata->journey_pattern_points[journey_pattern_point_offset] != sp_index && + tdata->journey_pattern_points[journey_pattern_point_offset] != JPP_NONE) { + tdata_rt_journey_patterns_at_stop_point_remove(tdata, tdata->journey_pattern_points[journey_pattern_point_offset], jp_index); } /* TODO: Should this be communicated in GTFS-RT? */ tdata->journey_pattern_point_attributes[journey_pattern_point_offset] = (rsa_boarding | rsa_alighting); - tdata->journey_pattern_points[journey_pattern_point_offset] = stop_index; - tdata_apply_gtfsrt_time (rt_stop_time_update, &tdata->vj_stoptimes[vj_index][rs]); - tdata_rt_journey_patterns_at_stop_append(tdata, stop_index, jp_index); + /* TODO: We dont know headsign, set to string_idx of last stop in jp?? */ + tdata->journey_pattern_point_headsigns[journey_pattern_point_offset] = 0; + tdata->journey_pattern_points[journey_pattern_point_offset] = (spidx_t) sp_index; + tdata_apply_gtfsrt_time (tdata, startdate_midnight_utc, rt_stop_time_update, &tdata->vj_stoptimes[vj_index][rs]); + tdata_rt_journey_patterns_at_stop_point_append(tdata, sp_index, jp_index); journey_pattern_point_offset++; rs++; } } } - /* update the last stop to be alighting only */ + /* update the last stop_point to be alighting only */ tdata->journey_pattern_point_attributes[--journey_pattern_point_offset] = rsa_alighting; - /* update the first stop to be boarding only */ + /* update the first stop_point to be boarding only */ journey_pattern_point_offset = tdata->journey_patterns[jp_index].journey_pattern_point_offset; tdata->journey_pattern_point_attributes[journey_pattern_point_offset] = rsa_boarding; } -static void tdata_realtime_changed_journey_pattern(tdata_t *tdata, uint32_t vj_index, int16_t cal_day, uint16_t n_stops, TransitRealtime__TripUpdate *rt_trip_update) { +static void tdata_realtime_changed_journey_pattern(tdata_t *tdata, time_t startdate_midnight_utc, vjidx_t vj_index, int16_t cal_day, uint16_t n_sp, TransitRealtime__TripUpdate *rt_trip_update) { TransitRealtime__TripDescriptor *rt_trip = rt_trip_update->trip; journey_pattern_t *jp_new = NULL; char *vj_id_new; + size_t len; uint32_t jp_index; - /* Don't ever continue if we found that n_stops == 0. */ - if (n_stops == 0) return; + /* Don't ever continue if we found that n_sp == 0. */ + if (n_sp == 0) return; #ifdef RRRR_DEBUG fprintf (stderr, "WARNING: this is a changed journey_pattern!\n"); @@ -248,50 +260,60 @@ static void tdata_realtime_changed_journey_pattern(tdata_t *tdata, uint32_t vj_i /* The idea is to fork a vj to a new journey_pattern, based on * the vehicle_journey id to find if the vehicle_journey id already exists */ - vj_id_new = (char *) alloca (sizeof(char) * tdata->vj_ids_width); + + len = strlen(rt_trip->trip_id); + vj_id_new = (char *) alloca (len + 2); vj_id_new[0] = '@'; - strncpy(&vj_id_new[1], rt_trip->trip_id, tdata->vj_ids_width - 1); + strncpy(&vj_id_new[1], rt_trip->trip_id, len); + vj_id_new[len + 1] = '\0'; - jp_index = radixtree_find (tdata->lineid_index, vj_id_new); + jp_index = radixtree_find_exact (tdata->lineid_index, vj_id_new); if (jp_index != RADIXTREE_NONE) { /* Fixes the case where a vj changes a second time */ jp_new = &tdata->journey_patterns[jp_index]; - if (jp_new->n_stops != n_stops) { - uint32_t i_stop_index; + if (jp_new->n_stops != n_sp) { + uint32_t sp_index; #ifdef RRRR_DEBUG fprintf (stderr, "WARNING: this is changed vehicle_journey %s being CHANGED again!\n", vj_id_new); #endif - tdata->vj_stoptimes[jp_new->vj_ids_offset] = (stoptime_t *) realloc(tdata->vj_stoptimes[jp_new->vj_ids_offset], sizeof(stoptime_t) * n_stops); + tdata->vj_stoptimes[jp_new->vj_index] = (stoptime_t *) realloc(tdata->vj_stoptimes[jp_new->vj_index], sizeof(stoptime_t) * n_sp); /* Only initialises if the length of the list increased */ - for (i_stop_index = jp_new->n_stops; - i_stop_index < n_stops; - ++i_stop_index) { - tdata->journey_pattern_points[jp_new->journey_pattern_point_offset + i_stop_index] = NONE; + for (sp_index = jp_new->n_stops; + sp_index < n_sp; + ++sp_index) { + tdata->journey_pattern_points[jp_new->journey_pattern_point_offset + sp_index] = JPP_NONE; } - jp_new->n_stops = n_stops; + jp_new->n_stops = n_sp; } } if (jp_new == NULL) { - journey_pattern_t *jp = tdata->journey_patterns + tdata->vjs_in_journey_pattern[vj_index]; - vehicle_journey_t *vj = tdata->vjs + vj_index; + vj_attribute_mask_t vj_attributes; uint16_t i_vj; - /* remove the planned vj from the operating day */ - tdata->vj_active[vj_index] &= ~(1 << cal_day); + if(vj_index == VJ_NONE) { + /* Create a new journey_pattern with default attributes */ + jp_index = tdata_new_journey_pattern(tdata, vj_id_new, n_sp, 1, 1, 0); + vj_attributes = vja_none; + } else { + journey_pattern_t *jp = tdata->journey_patterns + tdata->vjs_in_journey_pattern[vj_index]; + vehicle_journey_t *vj = tdata->vjs + vj_index; - /* we fork a new journey_pattern with all of its old properties - * having one single vj, which is the modification. - */ - jp_index = tdata_new_journey_pattern(tdata, vj_id_new, n_stops, 1, - jp->attributes, - jp->headsign_offset, - jp->agency_index, - jp->line_code_index, - jp->productcategory_index); + /* remove the planned vj from the operating day */ + tdata->vj_active[vj_index] &= ~(1 << cal_day); + + /* we fork a new journey_pattern with all of its old properties + * having one single vj, which is the modification. + */ + jp_index = tdata_new_journey_pattern(tdata, vj_id_new, n_sp, 1, + jp->attributes, + jp->route_index); + + vj_attributes = vj->vj_attributes; + } jp_new = &(tdata->journey_patterns[jp_index]); @@ -299,32 +321,32 @@ static void tdata_realtime_changed_journey_pattern(tdata_t *tdata, uint32_t vj_i * NOTE: if we would have allocated multiple vehicle_journeys this * should be a for loop over n_vjs. */ - vj_index = jp_new->vj_ids_offset; + vj_index = jp_new->vj_index; for (i_vj = 0; i_vj < 1; ++i_vj) { - tdata->vjs[vj_index].vj_attributes = vj->vj_attributes; + tdata->vjs[vj_index].vj_attributes = vj_attributes; tdata->journey_pattern_active[vj_index] |= (1 << cal_day); tdata->vj_active[vj_index] |= (1 << cal_day); vj_index++; } /* Restore the first vj_index again */ - vj_index = jp_new->vj_ids_offset; + vj_index = jp_new->vj_index; } - tdata_apply_stop_time_update (tdata, jp_index, vj_index, rt_trip_update); + tdata_apply_stop_time_update (tdata, startdate_midnight_utc, (jpidx_t) jp_index, vj_index, rt_trip_update); /* being blissfully naive, a journey_pattern having only one vehicle_journey, * will have the same start and end time as its vehicle_journey */ - jp_new->min_time = tdata->vj_stoptimes[jp_new->vj_ids_offset][0].arrival; + jp_new->min_time = tdata->vj_stoptimes[jp_new->vj_index][0].arrival; jp_new->max_time = tdata->vj_stoptimes[vj_index][jp_new->n_stops - 1].departure; } -static void tdata_realtime_journey_pattern_type(TransitRealtime__TripUpdate *rt_trip_update, uint16_t *n_stops, bool *changed_jp, bool *nodata_jp) { +static void tdata_realtime_journey_pattern_type(TransitRealtime__TripUpdate *rt_trip_update, uint16_t *n_sp, bool *changed_jp, bool *nodata_jp) { size_t i_stu; - *n_stops = 0; + *n_sp = 0; *changed_jp = false; *nodata_jp = true; @@ -337,12 +359,12 @@ static void tdata_realtime_journey_pattern_type(TransitRealtime__TripUpdate *rt_ rt_stop_time_update->schedule_relationship == TRANSIT_REALTIME__TRIP_UPDATE__STOP_TIME_UPDATE__SCHEDULE_RELATIONSHIP__SKIPPED); *nodata_jp &= (rt_stop_time_update->schedule_relationship == TRANSIT_REALTIME__TRIP_UPDATE__STOP_TIME_UPDATE__SCHEDULE_RELATIONSHIP__NO_DATA); - *n_stops += (rt_stop_time_update->schedule_relationship != TRANSIT_REALTIME__TRIP_UPDATE__STOP_TIME_UPDATE__SCHEDULE_RELATIONSHIP__SKIPPED && + *n_sp += (rt_stop_time_update->schedule_relationship != TRANSIT_REALTIME__TRIP_UPDATE__STOP_TIME_UPDATE__SCHEDULE_RELATIONSHIP__SKIPPED && rt_stop_time_update->stop_id != NULL); } } -static void tdata_realtime_apply_tripupdates (tdata_t *tdata, uint32_t vj_index, TransitRealtime__TripUpdate *rt_trip_update) { +static void tdata_realtime_apply_tripupdates (tdata_t *tdata, time_t startdate_midnight_utc, vjidx_t vj_index, TransitRealtime__TripUpdate *rt_trip_update) { TransitRealtime__TripUpdate__StopTimeUpdate *rt_stop_time_update_prev = NULL; TransitRealtime__TripUpdate__StopTimeUpdate *rt_stop_time_update; journey_pattern_t *jp; @@ -375,16 +397,16 @@ static void tdata_realtime_apply_tripupdates (tdata_t *tdata, uint32_t vj_index, for (i_stu = 0; i_stu < rt_trip_update->n_stop_time_update; ++i_stu) { spidx_t *journey_pattern_points = tdata->journey_pattern_points + jp->journey_pattern_point_offset; char *stop_id; - uint32_t stop_index; + uint32_t sp_index; rt_stop_time_update = rt_trip_update->stop_time_update[i_stu]; stop_id = rt_stop_time_update->stop_id; - stop_index = radixtree_find (tdata->stopid_index, stop_id); + sp_index = radixtree_find_exact (tdata->stop_point_id_index, stop_id); - if (journey_pattern_points[rs] == stop_index) { + if (journey_pattern_points[rs] == sp_index) { if (rt_stop_time_update->schedule_relationship == TRANSIT_REALTIME__TRIP_UPDATE__STOP_TIME_UPDATE__SCHEDULE_RELATIONSHIP__SCHEDULED) { - tdata_apply_gtfsrt_time (rt_stop_time_update, &tdata->vj_stoptimes[vj_index][rs]); + tdata_apply_gtfsrt_time (tdata, startdate_midnight_utc, rt_stop_time_update, &tdata->vj_stoptimes[vj_index][rs]); } /* In case of NO_DATA we won't do anything */ rs++; @@ -393,8 +415,8 @@ static void tdata_realtime_apply_tripupdates (tdata_t *tdata, uint32_t vj_index, /* we do not align up with the realtime messages */ if (rt_stop_time_update->schedule_relationship == TRANSIT_REALTIME__TRIP_UPDATE__STOP_TIME_UPDATE__SCHEDULE_RELATIONSHIP__SCHEDULED) { uint32_t propagate = rs; - while (journey_pattern_points[++rs] != stop_index && rs < jp->n_stops); - if (journey_pattern_points[rs] == stop_index) { + while (journey_pattern_points[++rs] != sp_index && rs < jp->n_stops); + if (journey_pattern_points[rs] == sp_index) { if (rt_stop_time_update_prev) { if (rt_stop_time_update_prev->schedule_relationship == TRANSIT_REALTIME__TRIP_UPDATE__STOP_TIME_UPDATE__SCHEDULE_RELATIONSHIP__SCHEDULED && rt_stop_time_update_prev->departure && rt_stop_time_update_prev->departure->has_delay) { @@ -404,9 +426,9 @@ static void tdata_realtime_apply_tripupdates (tdata_t *tdata, uint32_t vj_index, } } } - tdata_apply_gtfsrt_time (rt_stop_time_update, &tdata->vj_stoptimes[vj_index][rs]); + tdata_apply_gtfsrt_time (tdata, startdate_midnight_utc, rt_stop_time_update, &tdata->vj_stoptimes[vj_index][rs]); } else { - /* we couldn't find the stop at all */ + /* we couldn't find the stop_point at all */ rs = propagate; } rs++; @@ -433,20 +455,20 @@ static void tdata_realtime_apply_tripupdates (tdata_t *tdata, uint32_t vj_index, bool tdata_alloc_expanded(tdata_t *td) { uint32_t i_jp; - td->vj_stoptimes = (stoptime_t **) calloc(td->n_vjs, sizeof(stoptime_t *)); - td->vjs_in_journey_pattern = (uint32_t *) malloc(sizeof(uint32_t) * td->n_vjs); + td->vj_stoptimes = (stoptime_t **) calloc(td->n_vjs * RRRR_DYNAMIC_SLACK, sizeof(stoptime_t *)); + td->vjs_in_journey_pattern = (uint32_t *) malloc(sizeof(uint32_t) * td->n_vjs * RRRR_DYNAMIC_SLACK); if (!td->vj_stoptimes || !td->vjs_in_journey_pattern) return false; for (i_jp = 0; i_jp < td->n_journey_patterns; ++i_jp) { uint32_t i_vj; for (i_vj = 0; i_vj < td->journey_patterns[i_jp].n_vjs; ++i_vj) { - td->vjs_in_journey_pattern[td->journey_patterns[i_jp].vj_ids_offset + i_vj] = + td->vjs_in_journey_pattern[td->journey_patterns[i_jp].vj_index + i_vj] = i_jp; } } - td->rt_journey_patterns_at_stop = (list_t **) calloc(td->n_stops, sizeof(list_t *)); + td->rt_journey_patterns_at_stop_point = (list_t **) calloc(td->n_stop_points, sizeof(list_t *)); td->vj_active_orig = (calendar_t *) malloc(sizeof(calendar_t) * td->n_vjs); @@ -458,7 +480,7 @@ bool tdata_alloc_expanded(tdata_t *td) { memcpy (td->journey_pattern_active_orig, td->journey_pattern_active, sizeof(calendar_t) * td->n_journey_patterns); - if (!td->rt_journey_patterns_at_stop) return false; + if (!td->rt_journey_patterns_at_stop_point) return false; return true; } @@ -475,15 +497,15 @@ void tdata_free_expanded(tdata_t *td) { free (td->vj_stoptimes); } - if (td->rt_journey_patterns_at_stop) { - uint32_t i_stop; - for (i_stop = 0; i_stop < td->n_stops; ++i_stop) { - if (td->rt_journey_patterns_at_stop[i_stop]) { - free (td->rt_journey_patterns_at_stop[i_stop]->list); + if (td->rt_journey_patterns_at_stop_point) { + uint32_t i_sp; + for (i_sp = 0; i_sp < td->n_stop_points; ++i_sp) { + if (td->rt_journey_patterns_at_stop_point[i_sp]) { + free (td->rt_journey_patterns_at_stop_point[i_sp]->list); } } - free (td->rt_journey_patterns_at_stop); + free (td->rt_journey_patterns_at_stop_point); } free (td->vj_active_orig); @@ -521,25 +543,30 @@ void tdata_apply_gtfsrt_tripupdates (tdata_t *tdata, uint8_t *buf, size_t len) { if (rt_trip_update) { TransitRealtime__TripDescriptor *rt_trip = rt_trip_update->trip; struct tm ltm; - uint32_t vj_index; - time_t epochtime; + vjidx_t vj_index; + time_t startdate_midnight_utc; int16_t cal_day; - char buf[9]; + char date_buf[9]; if (rt_trip == NULL) continue; - vj_index = radixtree_find (tdata->vjid_index, rt_trip->trip_id); - if (vj_index == RADIXTREE_NONE) { - #ifdef RRRR_DEBUG - fprintf (stderr, " trip id was not found in the radix tree.\n"); - #endif - continue; - } + if(rt_trip->schedule_relationship == + TRANSIT_REALTIME__TRIP_DESCRIPTOR__SCHEDULE_RELATIONSHIP__ADDED) { + vj_index = VJ_NONE; + } else { + vj_index = (vjidx_t) radixtree_find_exact (tdata->vjid_index, rt_trip->trip_id); + if (vj_index == RADIXTREE_NONE) { + #ifdef RRRR_DEBUG + fprintf (stderr, " trip id was not found in the radix tree.\n"); + #endif + continue; + } - if (rt_entity->is_deleted) { - tdata_realtime_free_vj_index(tdata, vj_index); - continue; + if (rt_entity->is_deleted) { + tdata_realtime_free_vj_index(tdata, vj_index); + continue; + } } if (!rt_trip->start_date) { @@ -551,17 +578,17 @@ void tdata_apply_gtfsrt_tripupdates (tdata_t *tdata, uint8_t *buf, size_t len) { /* Take care of the realtime validity */ memset (<m, 0, sizeof(struct tm)); - strncpy (buf, rt_trip->start_date, 8); - buf[8] = '\0'; - ltm.tm_mday = strtol(&buf[6], NULL, 10); - buf[6] = '\0'; - ltm.tm_mon = strtol(&buf[4], NULL, 10) - 1; - buf[4] = '\0'; - ltm.tm_year = strtol(&buf[0], NULL, 10) - 1900; + strncpy (date_buf, rt_trip->start_date, 8); + date_buf[8] = '\0'; + ltm.tm_mday = (int) strtol(&date_buf[6], NULL, 10); + date_buf[6] = '\0'; + ltm.tm_mon = (int) strtol(&date_buf[4], NULL, 10) - 1; + date_buf[4] = '\0'; + ltm.tm_year = (int) strtol(&date_buf[0], NULL, 10) - 1900; ltm.tm_isdst = -1; - epochtime = mktime(<m); + startdate_midnight_utc = mktime(<m); - cal_day = (epochtime - tdata->calendar_start_time) / SEC_IN_ONE_DAY; + cal_day = (int16_t) (((uint64_t)startdate_midnight_utc - tdata->calendar_start_time) / SEC_IN_ONE_DAY); if (cal_day < 0 || cal_day > 31 ) { #ifdef RRRR_DEBUG @@ -579,9 +606,13 @@ void tdata_apply_gtfsrt_tripupdates (tdata_t *tdata, uint8_t *buf, size_t len) { tdata->vj_active[vj_index] &= ~(1 << cal_day); } else if (rt_trip->schedule_relationship == - TRANSIT_REALTIME__TRIP_DESCRIPTOR__SCHEDULE_RELATIONSHIP__SCHEDULED) { + TRANSIT_REALTIME__TRIP_DESCRIPTOR__SCHEDULE_RELATIONSHIP__SCHEDULED || + rt_trip->schedule_relationship == + TRANSIT_REALTIME__TRIP_DESCRIPTOR__SCHEDULE_RELATIONSHIP__ADDED) { /* Mark in the schedule the vj is scheduled */ - tdata->vj_active[vj_index] |= (1 << cal_day); + if (vj_index != VJ_NONE) { + tdata->vj_active[vj_index] |= (1 << cal_day); + } if (rt_trip_update->n_stop_time_update) { uint16_t n_stops; @@ -590,6 +621,9 @@ void tdata_apply_gtfsrt_tripupdates (tdata_t *tdata, uint8_t *buf, size_t len) { tdata_realtime_journey_pattern_type(rt_trip_update, &n_stops, &changed_jp, &nodata_jp); + changed_jp |= rt_trip->schedule_relationship == + TRANSIT_REALTIME__TRIP_DESCRIPTOR__SCHEDULE_RELATIONSHIP__ADDED; + /* Don't ever continue if we found that n_stops == 0, * this entire journey_pattern doesn't have any data. */ @@ -597,7 +631,9 @@ void tdata_apply_gtfsrt_tripupdates (tdata_t *tdata, uint8_t *buf, size_t len) { /* If data previously was available, we should fall * back to the schedule */ - tdata_realtime_free_vj_index(tdata, vj_index); + if (vj_index != VJ_NONE) { + tdata_realtime_free_vj_index(tdata, vj_index); + } /* In any case, we are done with processing this entity. */ continue; @@ -608,10 +644,10 @@ void tdata_apply_gtfsrt_tripupdates (tdata_t *tdata, uint8_t *buf, size_t len) { * into a new journey_pattern */ else if (changed_jp) { - tdata_realtime_changed_journey_pattern(tdata, vj_index, cal_day, n_stops, rt_trip_update); + tdata_realtime_changed_journey_pattern(tdata, startdate_midnight_utc, vj_index, cal_day, n_stops, rt_trip_update); } else { - tdata_realtime_apply_tripupdates (tdata, vj_index, rt_trip_update); + tdata_realtime_apply_tripupdates (tdata, startdate_midnight_utc, vj_index, rt_trip_update); } } @@ -639,24 +675,27 @@ void tdata_apply_gtfsrt_tripupdates_file (tdata_t *tdata, char *filename) { goto fail_clean_fd; } - buf = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + buf = mmap(NULL, (size_t) st.st_size, PROT_READ, MAP_SHARED, fd, 0); if (buf == MAP_FAILED) { fprintf(stderr, "Could not map GTFS-RT input file %s.\n", filename); goto fail_clean_fd; } - tdata_apply_gtfsrt_tripupdates (tdata, buf, st.st_size); - munmap (buf, st.st_size); + tdata_apply_gtfsrt_tripupdates (tdata, buf, (size_t) st.st_size); + munmap (buf, (size_t) st.st_size); fail_clean_fd: close (fd); } void tdata_clear_gtfsrt (tdata_t *tdata) { - uint32_t i_vj_index; - for (i_vj_index = 0; i_vj_index < tdata->n_vjs; ++i_vj_index) { - tdata_realtime_free_vj_index(tdata, i_vj_index); - } + vjidx_t vj_index = tdata->n_vjs; + + do { + vj_index--; + tdata_realtime_free_vj_index(tdata, vj_index); + } while (vj_index); + memcpy (tdata->vj_active, tdata->vj_active_orig, sizeof(calendar_t) * tdata->n_vjs); memcpy (tdata->journey_pattern_active, tdata->journey_pattern_active_orig, @@ -664,5 +703,5 @@ void tdata_clear_gtfsrt (tdata_t *tdata) { } #else - void tdata_gtfsrt_not_available() {} + void tdata_gtfsrt_not_available(); #endif /* RRRR_FEATURE_REALTIME_EXPANDED */ diff --git a/tdata_realtime_expanded.h b/tdata_realtime_expanded.h index 4ef6444..088ff77 100644 --- a/tdata_realtime_expanded.h +++ b/tdata_realtime_expanded.h @@ -1,4 +1,4 @@ -/* Copyright 2013 Bliksem Labs. +/* Copyright 2013-2015 Bliksem Labs B.V. * See the LICENSE file at the top-level directory of this distribution and at * https://github.com/bliksemlabs/rrrr/ */ @@ -6,6 +6,8 @@ #ifndef _TDATA_REALTIME_H #define _TDATA_REALTIME_H +#ifdef RRRR_FEATURE_REALTIME_EXPANDED + #include "tdata.h" bool tdata_alloc_expanded (tdata_t *td); @@ -17,4 +19,7 @@ void tdata_apply_gtfsrt_tripupdates (tdata_t *td, uint8_t *buf, size_t len); void tdata_apply_gtfsrt_tripupdates_file (tdata_t *td, char *filename); void tdata_clear_gtfsrt (tdata_t *td); + +#endif /* RRRR_FEATURE_REALTIME_EXPANDED */ + #endif /* _TDATA_REALTIME_H */ diff --git a/tdata_validation.c b/tdata_validation.c index 7c5e219..c070fe0 100644 --- a/tdata_validation.c +++ b/tdata_validation.c @@ -1,11 +1,10 @@ -/* Copyright 2013 Bliksem Labs. +/* Copyright 2013-2015 Bliksem Labs B.V. * See the LICENSE file at the top-level directory of this distribution and at * https://github.com/bliksemlabs/rrrr/ */ +/* tdata_validation.c : a set of validation tools to check whether a timetable is correct for RRRR*/ #include "tdata_validation.h" -#include "tdata.h" - #include /* Validate that the first journey_pattern_point have won't alighting set and @@ -13,7 +12,7 @@ */ int tdata_validation_boarding_alighting(tdata_t *tdata) { int32_t ret_invalid = 0; - uint32_t i_jp = tdata->n_journey_patterns; + jpidx_t i_jp = (jpidx_t) tdata->n_journey_patterns; do { journey_pattern_t *jp; @@ -28,7 +27,7 @@ int tdata_validation_boarding_alighting(tdata_t *tdata) { if ((rsa[0] & rsa_alighting) == rsa_alighting || (rsa[jp->n_stops - 1] & rsa_boarding) == rsa_boarding) { fprintf(stderr, "journey_pattern index %d %s %s %s has:\n%s%s", i_jp, - tdata_agency_name_for_journey_pattern(tdata, i_jp), + tdata_operator_name_for_journey_pattern(tdata, i_jp), tdata_line_code_for_journey_pattern(tdata, i_jp), tdata_headsign_for_journey_pattern(tdata, i_jp), ((rsa[0] & rsa_alighting) == rsa_alighting ? @@ -65,21 +64,21 @@ int tdata_validation_coordinates(tdata_t *tdata) { int32_t ret_invalid = 0; - uint32_t i_stop = tdata->n_stops; + uint32_t sp_index = tdata->n_stop_points; do { latlon_t ll; - i_stop--; + sp_index--; - ll = tdata->stop_coords[i_stop]; + ll = tdata->stop_point_coords[sp_index]; if (ll.lat < min_lat || ll.lat > max_lat || ll.lon < min_lon || ll.lon > max_lon) { fprintf (stderr, "stop lat/lon out of range: lat=%f, lon=%f \n", ll.lat, ll.lon); ret_invalid--; } - } while (i_stop); + } while (sp_index); return ret_invalid; } @@ -89,11 +88,11 @@ int tdata_validation_coordinates(tdata_t *tdata) { */ int tdata_validation_increasing_times(tdata_t *tdata) { - uint32_t jp_index, stop_index, vj_index; + uint32_t jp_index, sp_index, vj_index; int ret_nonincreasing = 0; for (jp_index = 0; jp_index < tdata->n_journey_patterns; ++jp_index) { journey_pattern_t jp = tdata->journey_patterns[jp_index]; - vehicle_journey_t *vjs = tdata->vjs + jp.vj_ids_offset; + vehicle_journey_t *vjs = tdata->vjs + jp.vj_index; #ifdef RRRR_DEBUG /* statistics on errors, instead of early bail out */ @@ -104,8 +103,8 @@ int tdata_validation_increasing_times(tdata_t *tdata) { vehicle_journey_t vj = vjs[vj_index]; stoptime_t *st = tdata->stop_times + vj.stop_times_offset; stoptime_t *prev_st = NULL; - for (stop_index = 0; stop_index < jp.n_stops; ++stop_index) { - if (stop_index == 0 && st->arrival != 0) { + for (sp_index = 0; sp_index < jp.n_stops; ++sp_index) { + if (sp_index == 0 && st->arrival != 0) { fprintf (stderr, "timedemand type begins at %d,%d not 0.\n", st->arrival, st->departure); @@ -117,7 +116,7 @@ int tdata_validation_increasing_times(tdata_t *tdata) { if (st->departure < st->arrival) { fprintf (stderr, "departure before arrival at " "journey_pattern %d, vj %d, stop %d.\n", - jp_index, vj_index, stop_index); + jp_index, vj_index, sp_index); #ifndef RRRR_DEBUG return -1; #endif @@ -125,15 +124,15 @@ int tdata_validation_increasing_times(tdata_t *tdata) { if (prev_st != NULL) { if (st->arrival < prev_st->departure) { - char *vj_id = ""; + char const *vj_id = ""; if (tdata->vj_ids) { - vj_id = tdata->vj_ids + (vj_index * tdata->vj_ids_width); + vj_id = tdata_vehicle_journey_id_for_index(tdata,vj_index); } fprintf (stderr, "negative travel time arriving at " "journey_pattern %d, vj %d (%s), stop %d.\n", jp_index, vj_index, - vj_id, stop_index); + vj_id, sp_index); #if 0 fprintf (stderr, "(%d, %d) -> (%d, %d)\n", prev_st->arrival, prev_st->departure, @@ -149,7 +148,7 @@ int tdata_validation_increasing_times(tdata_t *tdata) { #if 0 fprintf (stderr, "last departure equals arrival at " "journey_pattern %d, vj %d, stop %d.\n", - jp_index, vj_index, stop_index); + jp_index, vj_index, sp_index); #ifdef RRRR_DEBUG n_nonincreasing_vjs += 1; @@ -175,73 +174,115 @@ int tdata_validation_increasing_times(tdata_t *tdata) { return ret_nonincreasing; } +/* Check that all interlines are symmetric. + */ +int tdata_validation_symmetric_interlines(tdata_t *tdata) { + bool is_valid = true; + uint32_t jp_index = 0; + /* Check forward */ + for (; jp_index < tdata->n_journey_patterns; jp_index++) { + journey_pattern_t *jp = &tdata->journey_patterns[jp_index]; + jp_vjoffset_t vj_offset = 0; + for (; vj_offset < jp->n_vjs; vj_offset++) { + vehicle_journey_ref_t *vj_interline = &tdata->vehicle_journey_transfers_forward[jp->vj_index+vj_offset]; + if (vj_interline->jp_index != JP_NONE) { + journey_pattern_t *jp_next = &tdata->journey_patterns[vj_interline->jp_index]; + vehicle_journey_ref_t *vj_interline_back = &tdata->vehicle_journey_transfers_backward[jp_next->vj_index+vj_interline->vj_offset]; + if (vj_interline_back->jp_index != jp_index || vj_interline_back->vj_offset != vj_offset){ + is_valid = false; + fprintf (stderr,"VJ transfer (clockwise) not symetric! %d,%d points to %d,%d but points back to %d,%d\n", + jp_index,vj_offset, + vj_interline->jp_index,vj_interline->vj_offset, + vj_interline_back->jp_index,vj_interline_back->vj_offset); + } + } + } + } + + for (; jp_index < tdata->n_journey_patterns; jp_index++) { + journey_pattern_t *jp = &tdata->journey_patterns[jp_index]; + jp_vjoffset_t vj_offset = 0; + for (; vj_offset < jp->n_vjs; vj_offset++) { + vehicle_journey_ref_t *vj_interline = &tdata->vehicle_journey_transfers_backward[jp->vj_index+vj_offset]; + if (vj_interline->jp_index != JP_NONE) { + journey_pattern_t *jp_next = &tdata->journey_patterns[vj_interline->jp_index]; + vehicle_journey_ref_t *vj_interline_back = &tdata->vehicle_journey_transfers_forward[jp_next->vj_index+vj_interline->vj_offset]; + if (vj_interline_back->jp_index != jp_index || vj_interline_back->vj_offset != vj_offset){ + is_valid = false; + fprintf (stderr,"VJ transfer(counterclockwise) not symetric! %d,%d points to %d,%d but points back to %d,%d\n", + jp_index,vj_offset, + vj_interline->jp_index,vj_interline->vj_offset, + vj_interline_back->jp_index,vj_interline_back->vj_offset); + } + } + } + } + return is_valid ? 0 : 1; +} + /* Check that all transfers are symmetric. */ int tdata_validation_symmetric_transfers(tdata_t *tdata) { int n_transfers_checked = 0; - uint32_t stop_index_from; - for (stop_index_from = 0; - stop_index_from < tdata->n_stops; - ++stop_index_from) { - - /* Iterate over all transfers going out of this stop */ - uint32_t t = tdata->stops[stop_index_from ].transfers_offset; - uint32_t tN = tdata->stops[stop_index_from + 1].transfers_offset; + spidx_t sp_index_from; + for (sp_index_from = 0; + sp_index_from < tdata->n_stop_points; + ++sp_index_from) { + + /* Iterate over all transfers going out of this stop_point */ + uint32_t t = tdata->stop_points[sp_index_from].transfers_offset; + uint32_t tN = tdata->stop_points[sp_index_from + 1].transfers_offset; for ( ; t < tN ; ++t) { - uint32_t stop_index_to = tdata->transfer_target_stops[t]; - uint32_t forward_distance = tdata->transfer_dist_meters[t] << 4; - /* actually in units of 2^4 == 16 meters */ + spidx_t sp_index_to = tdata->transfer_target_stops[t]; + rtime_t forward_duration = tdata->transfer_durations[t]; - /* Find the reverse transfer (stop_index_to -> stop_index_from) */ - uint32_t u = tdata->stops[stop_index_to ].transfers_offset; - uint32_t uN = tdata->stops[stop_index_to + 1].transfers_offset; + /* Find the reverse transfer (sp_index_to -> sp_index_from) */ + uint32_t u = tdata->stop_points[sp_index_to].transfers_offset; + uint32_t uN = tdata->stop_points[sp_index_to + 1].transfers_offset; bool found_reverse = false; - if (stop_index_to == stop_index_from) { - fprintf (stderr, "loop transfer from/to stop %d.\n", - stop_index_from); - } - for ( ; u < uN ; ++u) { n_transfers_checked += 1; - if (tdata->transfer_target_stops[u] == stop_index_from) { + if (tdata->transfer_target_stops[u] == sp_index_from) { /* this is the same transfer in reverse */ - uint32_t reverse_distance = tdata->transfer_dist_meters[u] << 4; - if (reverse_distance != forward_distance) { - fprintf (stderr, "transfer from %d to %d is " + rtime_t reverse_duration = tdata->transfer_durations[u]; + if (reverse_duration != forward_duration) { + fprintf (stderr, "transfer from_stop_point %d to %d is " "not symmetric. " - "forward distance is %d, " - "reverse distance is %d.\n", - stop_index_from, - stop_index_to, - forward_distance, - reverse_distance); + "forward duration is %d, " + "reverse duration is %d.\n", + sp_index_from, + sp_index_to, + forward_duration, + reverse_duration); } found_reverse = true; break; } } if ( ! found_reverse) { - fprintf (stderr, "transfer from %d to %d does not have " + fprintf (stderr, "transfer from_stop_point %d to %d does not have " "an equivalent reverse transfer.\n", - stop_index_from, stop_index_to); + sp_index_from, sp_index_to); return -1; } } } + #ifdef RRRR_DEBUG fprintf (stderr, "checked %d transfers for symmetry.\n", n_transfers_checked); + #endif return 0; } -static bool tdata_validation_check_nstops (tdata_t *tdata) { - if (tdata->n_stops < 2) { - fprintf (stderr, "n_stops should be at least two, %d found.\n", tdata->n_stops); +static bool tdata_validation_check_nstop_points(tdata_t *tdata) { + if (tdata->n_stop_points < 2) { + fprintf (stderr, "n_stop_points should be at least two, %d found.\n", tdata->n_stop_points); return false; } else - if (tdata->n_stops > ONBOARD) { - fprintf (stderr, "n_stops %d exceeds compiled spidx_t width.\n", tdata->n_stops); + if (tdata->n_stop_points > ONBOARD) { + fprintf (stderr, "n_stop_points %d exceeds compiled spidx_t width.\n", tdata->n_stop_points); return false; } @@ -249,13 +290,18 @@ static bool tdata_validation_check_nstops (tdata_t *tdata) { } bool tdata_validation_check_coherent (tdata_t *tdata) { + #ifdef RRRR_DEBUG fprintf (stderr, "checking tdata coherency...\n"); + #endif - return (tdata_validation_check_nstops(tdata) && + return (tdata_validation_check_nstop_points(tdata) && tdata->n_journey_patterns > 0 && + tdata->n_vjs > 0 && + tdata->n_journey_patterns < ((jpidx_t) -1) && tdata_validation_boarding_alighting(tdata) == 0 && tdata_validation_coordinates(tdata) == 0 && tdata_validation_increasing_times(tdata) == 0 && - tdata_validation_symmetric_transfers(tdata) == 0); + tdata_validation_symmetric_transfers(tdata) == 0 && + tdata_validation_symmetric_interlines(tdata) == 0); } diff --git a/tdata_validation.h b/tdata_validation.h index 55a1500..12fdbaa 100644 --- a/tdata_validation.h +++ b/tdata_validation.h @@ -1,4 +1,4 @@ -/* Copyright 2013 Bliksem Labs. +/* Copyright 2013-2015 Bliksem Labs B.V. * See the LICENSE file at the top-level directory of this distribution and at * https://github.com/bliksemlabs/rrrr/ */ @@ -12,6 +12,7 @@ int tdata_validation_boarding_alighting(tdata_t *tdata); int tdata_validation_coordinates(tdata_t *tdata); int tdata_validation_increasing_times(tdata_t *tdata); int tdata_validation_symmetric_transfers(tdata_t *tdata); +int tdata_validation_symmetric_interlines(tdata_t *tdata); bool tdata_validation_check_coherent (tdata_t *tdata); #endif /* _TDATA_VALIDATION_H */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index df75da4..983cc35 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -4,16 +4,84 @@ include_directories(${CHECK_INCLUDE_DIRS}) set(LIBS ${LIBS} ${CHECK_LIBRARIES}) include_directories(. ..) +set_property(SOURCE ${PROTO_HDRS} ${PROTO_SRCS} PROPERTY GENERATED ON) + set(SOURCE_FILES + ${PROTO_HDRS} + ${PROTO_SRCS} + ../api.c + ../api.h ../bitset.c ../bitset.h + ../config.h + ../geometry.c + ../geometry.h + ../hashgrid.c + ../hashgrid.h + ../hashgrid_street_network.c + ../json.c + ../json.h + ../plan.c + ../plan.h + ../plan_render_otp.c + ../plan_render_otp.h + ../plan_render_text.c + ../plan_render_text.h + ../polyline.c + ../polyline.h + ../radixtree.c + ../radixtree.h + ../router.c + ../router.h + ../router_dump.c + ../router_dump.h + ../router_result.c + ../router_result.h + ../router_request.c + ../router_request.h + ../set.c + ../set.h + ../street_network.c + ../street_network.h + ../tdata.c + ../tdata.h + ../tdata_realtime_alerts.c + ../tdata_realtime_alerts.h + ../tdata_realtime_expanded.c + ../tdata_realtime_expanded.h + ../tdata_validation.c + ../tdata_validation.h + ../tdata_io_v4_mmap.c + ../tdata_io_v4_dynamic.c + ../tdata_io_v4.h + ../index_journey_pattern_points.c + ../index_journey_pattern_points.h + ../index_journey_patterns.c + ../index_journey_patterns.h + ../index_vehicle_journeys.c + ../index_vehicle_journeys.h + ../tdata_jit.c + ../tdata_jit.h + ../string_pool.c + ../string_pool.h + ../util.c + ../util.h run_tests.c test_bitset.c - #test_hashgrid.c - #test_radixtree.c + test_hashgrid.c + test_geometry.c + test_json.c + test_radixtree.c + test_polyline.c + test_router_request.c + test_street_network.c + test_util.c + test_tdata_jit.c + test_tdata_views.c ) add_executable(tests ${SOURCE_FILES}) +add_dependencies(tests cli) SET_TARGET_PROPERTIES(tests PROPERTIES COMPILE_FLAGS "-DRRRR_DEBUG ${SHARED_FLAGS}" ) diff --git a/tests/run_tests.c b/tests/run_tests.c index e1af0c9..77bd69b 100644 --- a/tests/run_tests.c +++ b/tests/run_tests.c @@ -1,15 +1,23 @@ +#include "config.h" #include #include /* could be in a header, but simpler here */ Suite *make_bitset_suite (void); - -#if 0 +Suite *make_geometry_suite (void); Suite *make_hashgrid_suite (void); +Suite *make_json_suite (void); +Suite *make_polyline_suite (void); Suite *make_radixtree_suite (void); +Suite *make_router_request_suite (void); +Suite *make_street_network_suite (void); +#ifdef RRRR_TDATA_IO_DYNAMIC +Suite *make_tdata_jit_suite (void); #endif +Suite *make_tdata_views_suite (void); +Suite *make_util_suite (void); -Suite *make_master_suite (void) { +static Suite *make_master_suite (void) { Suite *s = suite_create ("Master"); return s; } @@ -19,10 +27,18 @@ int main (void) { SRunner *sr; sr = srunner_create (make_master_suite ()); srunner_add_suite (sr, make_bitset_suite ()); - #if 0 + srunner_add_suite (sr, make_geometry_suite ()); + srunner_add_suite (sr, make_json_suite ()); srunner_add_suite (sr, make_hashgrid_suite ()); + srunner_add_suite (sr, make_polyline_suite ()); srunner_add_suite (sr, make_radixtree_suite ()); - #endif + srunner_add_suite (sr, make_router_request_suite ()); + srunner_add_suite (sr, make_street_network_suite ()); + srunner_add_suite (sr, make_util_suite ()); + /*srunner_add_suite (sr, make_tdata_views_suite ()); TODO make a utility that builds timetables for testing*/ +#ifdef RRRR_TDATA_IO_DYNAMIC + /*srunner_add_suite (sr, make_tdata_jit_suite ()); TODO: make a utility that builds timetables for testing*/ +#endif srunner_set_log (sr, "test.log"); srunner_run_all (sr, CK_VERBOSE); /* CK_NORMAL */ number_failed = srunner_ntests_failed (sr); diff --git a/tests/test_bitset.c b/tests/test_bitset.c index b6fb39a..e703588 100644 --- a/tests/test_bitset.c +++ b/tests/test_bitset.c @@ -11,9 +11,14 @@ START_TEST (test_bitset) for (i = 0; i < 50000; i += 2) bitset_set(bs, i); + + ck_assert_int_eq (bitset_count(bs), 25000); + for (i = 1; i < 50000; i += 2) bitset_set(bs_inv, i); + ck_assert_int_eq (bitset_count(bs_inv), 25000); + for (i = 0; i < 50000; ++i){ if (i % 2 == 0){ ck_assert(bitset_get(bs, i)); @@ -44,20 +49,32 @@ START_TEST (test_bitset) /* Test flipping all bits on */ bitset_black(bs); + ck_assert_int_eq(bitset_count(bs), 50000); for (i = 0; i < 50000; ++i) { ck_assert(bitset_get(bs, i)); } /* Test clearing all bits */ bitset_clear(bs); + ck_assert_int_eq(bitset_count(bs), 0); ck_assert_int_eq(BITSET_NONE,bitset_next_set_bit(bs, 0)); + /* Test counting */ + bitset_set(bs, 49999); + bitset_set(bs, 39999); + bitset_set(bs, 29999); + bitset_set(bs, 19999); + bitset_set(bs, 1); + bitset_set(bs, 0); + ck_assert_int_eq (bitset_count(bs), 6); + /* Test unset */ bitset_black(bs); for (i = 0; i < 50000; i += 2) { bitset_unset(bs, i); } + ck_assert_int_eq(bitset_count(bs), 25000); ck_assert(!bitset_get(bs, 0)); ck_assert(bitset_get(bs, 1)); ck_assert_int_eq(1, bitset_next_set_bit(bs, 1)); @@ -70,6 +87,8 @@ START_TEST (test_bitset) } END_TEST +Suite *make_bitset_suite(void); + Suite *make_bitset_suite(void) { Suite *s = suite_create("bitset_t"); TCase *tc_core = tcase_create("Core"); diff --git a/tests/test_geometry.c b/tests/test_geometry.c new file mode 100644 index 0000000..ad4f1b5 --- /dev/null +++ b/tests/test_geometry.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include "../geometry.h" + +#define ck_assert_float_eq(a, b) ck_assert(fabs(a - b) < 0.00001f) + +START_TEST (test_strtolatlon) + { + latlon_t latlon1; + latlon_t latlon2; + coord_t coord; + double distance; + + strtolatlon("52.12000,4.50000", &latlon1); + ck_assert_float_eq(latlon1.lat, 52.12000f); + ck_assert_float_eq(latlon1.lon, 4.50000f); + + coord_from_latlon(&coord, &latlon1); + ck_assert(coord.x == 32964396 && + coord.y == 621815807); + + latlon_from_coord(&latlon2, &coord); + distance = latlon_distance_meters(&latlon1, &latlon2); + ck_assert(distance < 0.02797f); + } +END_TEST + +Suite *make_geometry_suite(void); + +Suite *make_geometry_suite(void) { + Suite *s = suite_create("geometry"); + TCase *tc_core = tcase_create("Core"); + tcase_add_test (tc_core, test_strtolatlon); + suite_add_tcase(s, tc_core); + return s; +} diff --git a/tests/test_hashgrid.c b/tests/test_hashgrid.c new file mode 100644 index 0000000..e21c30b --- /dev/null +++ b/tests/test_hashgrid.c @@ -0,0 +1,84 @@ +#include +#include +#include "../hashgrid.h" +#include "../geometry.h" + +START_TEST (test_hashgrid) + { +#if 0 + double distance; +#endif + hashgrid_result_t result; + coord_t qc; + uint32_t item; + + coord_t coords[7] = { { 0, 0 }, + { 0, 10 }, + { 10, 0 }, + { 10, 10 }, + { 0, -10 }, + { -10, 0 }, + { -10, -10 } }; + + + hashgrid_t hashgrid; + hashgrid_t *hg = &hashgrid; + + ck_assert (hashgrid_init(hg, 2, METERS_PER_BRAD, coords, 7)); + + hashgrid_dump (hg); + + qc.x = 10; + qc.y = 10; + + hashgrid_query (hg, &result, qc, 4 * METERS_PER_BRAD); + item = hashgrid_result_closest (&result); + ck_assert_int_eq (item, 3); + +#if 0 + /* TODO: This part should be tested */ + hashgrid_query (hg, &result, qc, 4 * METERS_PER_BRAD); + item = hashgrid_result_next(&result); + ck_assert_int_eq (item, 3); + + item = hashgrid_result_next_filtered(&result, &distance); + ck_assert_int_eq (item, HASHGRID_NONE); +#endif + + hashgrid_teardown (hg); + } +END_TEST + +START_TEST (test_hashgrid_init) + { + coord_t coords[2]; + double distance; + + hashgrid_t hg; + hashgrid_result_t result; + + coord_from_lat_lon(&coords[0], 1.0, 1.0); + coord_from_lat_lon(&coords[1], 2.0, 2.0); + + hashgrid_init(&hg, 100, 500, coords, 2); + hashgrid_query (&hg, &result, coords[1], 500.0); + + distance = 0.0f; + while(hashgrid_result_next_filtered(&result, &distance) != HASHGRID_NONE) { + + } + + hashgrid_teardown (&hg); + } +END_TEST + +Suite *make_hashgrid_suite(void); + +Suite *make_hashgrid_suite(void) { + Suite *s = suite_create("hashgrid"); + TCase *tc_core = tcase_create("Core"); + tcase_add_test (tc_core, test_hashgrid); + tcase_add_loop_test (tc_core, test_hashgrid_init, 0, 20); + suite_add_tcase(s, tc_core); + return s; +} diff --git a/tests/test_json.c b/tests/test_json.c new file mode 100644 index 0000000..2d86636 --- /dev/null +++ b/tests/test_json.c @@ -0,0 +1,49 @@ +#include +#include +#include "../json.h" + +START_TEST (test_json) + { + json_t json; + json_t *j = &json; + char buf[2048]; + + json_init(j, buf, 2048); + json_obj(j); + json_key_obj(j, "tests"); + json_kv(j, "string", "hello"); + json_kv(j, "tab", " "); + json_kd(j, "integer", -1); + json_kf(j, "double", 2.92100f); + json_kl(j, "long", 12345678912345); + json_kb(j, "bool", true); + json_key_obj(j, "object"); + json_end_obj(j); + json_end_obj(j); + json_key_arr(j, "others"); + json_v(j, "world"); + json_d(j, 2); + json_f(j, 50.12300f); + json_l(j, 987654321); + json_b(j, false); + json_obj(j); + json_end_obj(j); + json_arr(j); + json_end_arr(j); + json_end_arr(j); + json_end_obj(j); + json_end(j); + + ck_assert_str_eq(buf, "{\"tests\":{\"string\":\"hello\",\"tab\":\"\\t\",\"integer\":-1,\"double\":2.92100,\"long\":12345678912345,\"bool\":true,\"object\":{}},\"others\":[\"world\",2,50.12300,987654321,false,{},[]]}"); + } +END_TEST + +Suite *make_json_suite(void); + +Suite *make_json_suite(void) { + Suite *s = suite_create("json"); + TCase *tc_core = tcase_create("Core"); + tcase_add_test (tc_core, test_json); + suite_add_tcase(s, tc_core); + return s; +} diff --git a/tests/test_polyline.c b/tests/test_polyline.c new file mode 100644 index 0000000..1949ec8 --- /dev/null +++ b/tests/test_polyline.c @@ -0,0 +1,33 @@ +#include +#include +#include "../polyline.h" + +START_TEST (test_polyline) + { + char *out; + uint32_t n; + polyline_t pl; + + /* https://developers.google.com/maps/documentation/utilities/polylinealgorithm */ + polyline_begin(&pl); + polyline_point(&pl, 38.5f, -120.2f); + polyline_point(&pl, 40.7f, -120.95f); + polyline_point(&pl, 43.252, -126.453f); + + n = polyline_length(&pl); + ck_assert_int_eq(n, 3); + + out = polyline_result(&pl); + ck_assert_str_eq(out, "_p~iF~ps|U_ulLnnqC_mqNvxq`@"); + } +END_TEST + +Suite *make_polyline_suite(void); + +Suite *make_polyline_suite(void) { + Suite *s = suite_create("polyline"); + TCase *tc_core = tcase_create("Core"); + tcase_add_test (tc_core, test_polyline); + suite_add_tcase(s, tc_core); + return s; +} diff --git a/tests/test_radixtree.c b/tests/test_radixtree.c new file mode 100644 index 0000000..013eeed --- /dev/null +++ b/tests/test_radixtree.c @@ -0,0 +1,51 @@ +#include +#include +#include "../radixtree.h" + +START_TEST (test_radixtree) + { + radixtree_t *r; + + char *strings[] = {"eight thousand", + "nine thousand", + "nine tho", + "eight thousand five hundred", + "six thousand", + "nine thousand five", + "nine thousand five hundred", + "nine nine nine nine", + NULL}; + + /* tests radixtree_new, rxt_init, rxt_edge_new */ + r = radixtree_new(); + ck_assert (r); + ck_assert (r->base == NULL); + ck_assert (r->size == 0); + ck_assert (r->root); + ck_assert (r->root->next == NULL); + ck_assert (r->root->child == NULL); + ck_assert (r->root->value == RADIXTREE_NONE); + ck_assert (r->root->prefix[0] == '\0'); + + radixtree_insert(r, "", 3); + radixtree_insert(r, "eight thousand", 8000); + radixtree_insert(r, "nine thousand", 9000); + radixtree_insert(r, "eight thousand five hundred", 8500); + + ck_assert_int_eq (radixtree_find_exact(r, strings[0]), 8000); + ck_assert_int_eq (radixtree_find_exact(r, strings[1]), 9000); + ck_assert_int_eq (radixtree_find_prefix(r, strings[2], NULL), 9000); + + radixtree_destroy (r); + } +END_TEST + +Suite *make_radixtree_suite(void); + +Suite *make_radixtree_suite(void) { + Suite *s = suite_create("radixtree"); + TCase *tc_core = tcase_create("Core"); + tcase_add_test (tc_core, test_radixtree); + suite_add_tcase(s, tc_core); + return s; +} diff --git a/tests/test_router_request.c b/tests/test_router_request.c new file mode 100644 index 0000000..9cd6b85 --- /dev/null +++ b/tests/test_router_request.c @@ -0,0 +1,43 @@ +#include +#include +#include "../router_request.h" + +START_TEST (test_from_epoch) + { + struct tm ltm; + char date[11]; + router_request_t router_request; + router_request_t *req = &router_request; + + tdata_t tdata; + tdata_t *td = &tdata; + td->utc_offset = 0; + td->n_days = 2; + + /* Tue Mar 10 2015 01:00:00 GMT+0100 (CET) */ + td->calendar_start_time = 1425945600; + + router_request_from_epoch (req, td, 1425945600); + ck_assert_int_eq (req->day_mask, 1); + ck_assert_int_eq (router_request_to_date (req, td, <m), 1425945600); + strftime(date, 11, "%Y-%m-%d", <m); + ck_assert_str_eq (date, "2015-03-10"); + + router_request_from_epoch (req, td, 1425945600 + 108000); + ck_assert_int_eq (req->day_mask, 2); + ck_assert_int_eq (router_request_to_date (req, td, <m), 1425945600 + 86400); + router_request_to_date (req, td, <m); + strftime(date, 11, "%Y-%m-%d", <m); + ck_assert_str_eq (date, "2015-03-11"); + } +END_TEST + +Suite *make_router_request_suite(void); + +Suite *make_router_request_suite(void) { + Suite *s = suite_create("router_request"); + TCase *tc_core = tcase_create("Core"); + tcase_add_test (tc_core, test_from_epoch); + suite_add_tcase(s, tc_core); + return s; +} diff --git a/tests/test_street_network.c b/tests/test_street_network.c new file mode 100644 index 0000000..e878a04 --- /dev/null +++ b/tests/test_street_network.c @@ -0,0 +1,55 @@ +#include +#include +#include "../street_network.h" +#include "../util.h" + +START_TEST (test_mark_duration) + { + street_network_t street_network; + street_network_t *sn = &street_network; + bool out; + + street_network_init (sn); + + sn->stop_points[0] = 1; + sn->stop_points[1] = 3; + sn->n_points = 2; + + out = street_network_mark_duration_to_stop_point (sn, 3, 200); + ck_assert (out == true); + + out = street_network_mark_duration_to_stop_point (sn, 2, 50); + ck_assert (out == true); + + out = street_network_mark_duration_to_stop_point (sn, 1, 100); + ck_assert (out == true); + + ck_assert_int_eq (sn->durations[0], 100); + ck_assert_int_eq (sn->durations[1], 200); + ck_assert_int_eq (sn->durations[2], 50); + + ck_assert_int_eq (sn->stop_points[0], 1); + ck_assert_int_eq (sn->stop_points[1], 3); + ck_assert_int_eq (sn->stop_points[2], 2); + + ck_assert_int_eq(street_network_duration(555, sn), UNREACHED); + ck_assert_int_eq(street_network_duration(3, sn), 200); + ck_assert_int_eq(street_network_duration(2, sn), 50); + ck_assert_int_eq(street_network_duration(1, sn), 100); + + sn->n_points = RRRR_MAX_ENTRY_EXIT_POINTS; + rrrr_memset(sn->stop_points, 0, RRRR_MAX_ENTRY_EXIT_POINTS); + out = street_network_mark_duration_to_stop_point (sn, 4, 100); + ck_assert (out == false); + } +END_TEST + +Suite *make_street_network_suite(void); + +Suite *make_street_network_suite(void) { + Suite *s = suite_create("street_network"); + TCase *tc_core = tcase_create("Core"); + tcase_add_test (tc_core, test_mark_duration); + suite_add_tcase(s, tc_core); + return s; +} diff --git a/tests/test_tdata_jit.c b/tests/test_tdata_jit.c new file mode 100644 index 0000000..646923a --- /dev/null +++ b/tests/test_tdata_jit.c @@ -0,0 +1,120 @@ +#include +#include +#include +#include "../util.h" +#include "../tdata_jit.h" + +#ifdef RRRR_TDATA_IO_DYNAMIC + +void dump_journey_patterns_at_stop (const char *filename, tdata_t *tdata); +void dump_journey_patterns_active (const char *filename, tdata_t *tdata); + +void dump_journey_patterns_at_stop (const char *filename, tdata_t *tdata) { + uint32_t i; + + FILE *fp = fopen(filename, "w"); + #if 0 + i = tdata->n_journey_pattern_points; + while (i) { + i--; + fprintf (fp, "%u\n", tdata->journey_patterns_at_stop[i]); + } + + i = tdata->n_stop_points; + while (i) { + i--; + fprintf (fp, "%u\n", tdata->stop_points[i].journey_patterns_at_stop_point_offset); + } + #endif + + for (i = 0; i < tdata->n_stop_points; ++i) { + uint32_t j; + fprintf (fp, "%u %u\n", i, tdata->stop_points[i].journey_patterns_at_stop_point_offset); + + for (j = tdata->stop_points[i].journey_patterns_at_stop_point_offset; + j < tdata->stop_points[i+1].journey_patterns_at_stop_point_offset; + j++) { + fprintf (fp, " %u\n", tdata->journey_patterns_at_stop[j]); + } + } + + fclose (fp); +} + +void dump_journey_patterns_active (const char *filename, tdata_t *tdata) { + uint32_t i; + + FILE *fp = fopen(filename, "w"); + for (i = 0; i < tdata->n_journey_patterns; ++i) { + char bits[34] = ""; + renderBits(&tdata->journey_pattern_active[i], 4, bits); + fprintf (fp, "%05u %s\n", i, bits); + } + fclose (fp); +} + +START_TEST (test_tdata_jit) + { + tdata_t tdata; + uint32_t *journey_patterns_active; + jpidx_t *journey_patterns_at_stop; + uint32_t n_journey_patterns_at_stop; + uint32_t n_journey_patterns; + rtime_t max_time; + + memset (&tdata, 0, sizeof(tdata_t)); + + ck_assert_msg (tdata_load (&tdata, "/tmp/timetable4.dat"), "Copy a working timetable4.dat file to /tmp/timetabel4.dat, and rerun the test."); + journey_patterns_active = tdata.journey_pattern_active; + journey_patterns_at_stop = tdata.journey_patterns_at_stop; + n_journey_patterns_at_stop = tdata.n_journey_patterns_at_stop; + + /* Store the max_time to compare it later */ + max_time = tdata.max_time; + + /* Make sure we reset the value */ + tdata.max_time = 0; + + dump_journey_patterns_at_stop ("/tmp/jp_sp_idx-timetable.txt", &tdata); + dump_journey_patterns_active ("/tmp/jp_active-timetable.txt", &tdata); + + ck_assert (tdata_journey_patterns_at_stop (&tdata)); + ck_assert_int_eq (n_journey_patterns_at_stop, tdata.n_journey_patterns_at_stop); + dump_journey_patterns_at_stop ("/tmp/jp_sp_idx-jit.txt", &tdata); + /* This one is not the same because the Python code doesn't sort the list */ + /* ck_assert (memcmp(journey_patterns_at_stop, tdata.journey_patterns_at_stop, tdata.n_journey_patterns_at_stop) == 0); */ + + ck_assert (tdata_journey_patterns_index (&tdata)); + dump_journey_patterns_active ("/tmp/jp_active-jit.txt", &tdata); + ck_assert_int_eq (max_time, tdata.max_time); + ck_assert (memcmp(journey_patterns_active, tdata.journey_pattern_active, tdata.n_journey_patterns) == 0); + + n_journey_patterns = tdata.n_journey_patterns; + while (n_journey_patterns) { + int delta; + n_journey_patterns--; + delta = tdata.journey_patterns[n_journey_patterns].max_time - tdata.journey_pattern_max[n_journey_patterns]; + if (delta != 0) { + fprintf(stderr, "%d, %u %u %u %u %u\n", delta, n_journey_patterns, tdata.journey_pattern_min[n_journey_patterns], tdata.journey_pattern_max[n_journey_patterns], tdata.journey_patterns[n_journey_patterns].min_time, tdata.journey_patterns[n_journey_patterns].max_time); + + } + + /* Ignore this test for now, it seems that RID is one off + ck_assert_int_eq (tdata.journey_pattern_min[n_journey_patterns], tdata.journey_patterns[n_journey_patterns].min_time); + ck_assert_int_eq (tdata.journey_pattern_max[n_journey_patterns], tdata.journey_patterns[n_journey_patterns].max_time); + */ + } + } +END_TEST + +Suite *make_tdata_jit_suite(void); + +Suite *make_tdata_jit_suite(void) { + Suite *s = suite_create("tdata_jit"); + TCase *tc_core = tcase_create("Core"); + tcase_add_test (tc_core, test_tdata_jit); + suite_add_tcase(s, tc_core); + return s; +} + +#endif diff --git a/tests/test_tdata_views.c b/tests/test_tdata_views.c new file mode 100644 index 0000000..8cdb959 --- /dev/null +++ b/tests/test_tdata_views.c @@ -0,0 +1,38 @@ +#include +#include +#include +#include "../util.h" +#include "../api.h" +#include "../rrrr_types.h" + +START_TEST (test_tdata_views) + { + + tdata_t tdata; + rtime_t *result; + uint32_t n_results; + spidx_t sps[2]; + + memset (&tdata, 0, sizeof(tdata_t)); + result = (rtime_t *) malloc (sizeof(rtime_t) * 256); + sps[0] = 1; + sps[1] = 15122; + + ck_assert_msg (tdata_load (&tdata, "/tmp/timetable4.dat"), "Copy a working timetable4.dat file to /tmp/timetabel4.dat, and rerun the test."); + n_results = tdata_n_departures_since (&tdata, sps, 2, result, 256, 800 * 8, 800 * 9); + while (n_results) { + n_results--; + printf("%u\n", result[n_results]); + } + } +END_TEST + +Suite *make_tdata_views_suite(void); + +Suite *make_tdata_views_suite(void) { + Suite *s = suite_create("tdata_views"); + TCase *tc_core = tcase_create("Core"); + tcase_add_test (tc_core, test_tdata_views); + suite_add_tcase(s, tc_core); + return s; +} diff --git a/tests/test_util.c b/tests/test_util.c new file mode 100644 index 0000000..48e6535 --- /dev/null +++ b/tests/test_util.c @@ -0,0 +1,139 @@ +#include +#include +#include "../util.h" + +START_TEST (test_median_even) + { + float f[4] = {0.0f, 5.0f, 2.0f, 1.0f}; + float med, min, max; + uint32_t n = 4; + + med = median(f, n, &min, &max); + + ck_assert(med == 1.5f); + ck_assert_int_eq((int) min, 0); + ck_assert_int_eq((int) max, 5); + } +END_TEST + +START_TEST (test_median_uneven) + { + float f[4] = {0.0f, 2.0f, 1.0f}; + float med, min, max; + uint32_t n = 3; + + med = median(f, n, &min, &max); + + ck_assert(med == 1.0f); + ck_assert_int_eq((int) min, 0); + ck_assert_int_eq((int) max, 2); + } +END_TEST + +START_TEST (test_renderbits) + { + char out[34]; + uint32_t data; + + data = 14; + renderBits(&data, 1, out); + ck_assert_str_eq(out, "00001110"); + } +END_TEST + +START_TEST (test_strtoepoch) + { + time_t out; + out = strtoepoch("2013-12-11T10:09:08"); + ck_assert_int_eq(out, 1386752948); + } +END_TEST + +START_TEST (test_epoch_to_rtime) + { + struct tm tm_out; + rtime_t rtime; + rtime_t rtime_expected = SEC_TO_RTIME(36548) + RTIME_ONE_DAY; + + rtime = epoch_to_rtime(1386752948, &tm_out); + ck_assert_int_eq(rtime, rtime_expected); + rtime = epoch_to_rtime(36548, &tm_out); + ck_assert_int_eq(rtime, rtime_expected); + } +END_TEST + +START_TEST (test_rrrrandom) + { + uint32_t limit = 32; + uint32_t a = rrrrandom(limit); + uint32_t b = rrrrandom(limit); + + ck_assert_int_le(a, limit); + ck_assert_int_le(b, limit); + ck_assert_int_ne(a, b); + } +END_TEST + +START_TEST (test_btimetext) + { + char *out; + char buf[13]; + rtime_t rt = SEC_TO_RTIME(36548); + + out = btimetext(UNREACHED, buf); + ck_assert_str_eq(out, " -- "); + + out = btimetext(rt, buf); + ck_assert_str_eq(out, "10:09:08 -1D"); + + out = btimetext(rt + RTIME_ONE_DAY, buf); + ck_assert_str_eq(out, "10:09:08"); + + out = btimetext(rt + RTIME_TWO_DAYS, buf); + ck_assert_str_eq(out, "10:09:08 +1D"); + + out = btimetext(RTIME_THREE_DAYS, buf); + ck_assert_str_eq(out, "00:00:00 +2D"); + } +END_TEST + +START_TEST (test_dedupRtime) + { + rtime_t i1[11] = {1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 6}; + rtime_t j1[5] = {1, 2, 3, 4, 6}; + rtime_t i2[1] = {8}; + rtime_t j2[1] = {8}; + + uint32_t n; + + n = dedupRtime (i1, 11); + + ck_assert_int_eq (n, 5); + ck_assert ( memcmp(i1, j1, 5) == 0 ); + + n = dedupRtime (i2, 1); + + ck_assert_int_eq (n, 1); + ck_assert ( memcmp(i2, j2, 1) == 0 ); + + } +END_TEST + + + +Suite *make_util_suite(void); + +Suite *make_util_suite(void) { + Suite *s = suite_create("util"); + TCase *tc_core = tcase_create("Core"); + tcase_add_test (tc_core, test_median_even); + tcase_add_test (tc_core, test_median_uneven); + tcase_add_test (tc_core, test_renderbits); + tcase_add_test (tc_core, test_strtoepoch); + tcase_add_test (tc_core, test_epoch_to_rtime); + tcase_add_test (tc_core, test_rrrrandom); + tcase_add_test (tc_core, test_btimetext); + tcase_add_test (tc_core, test_dedupRtime); + suite_add_tcase(s, tc_core); + return s; +} diff --git a/util.c b/util.c index b704174..1327d93 100644 --- a/util.c +++ b/util.c @@ -1,19 +1,49 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and at + * https://github.com/bliksemlabs/rrrr/ + */ + #include "util.h" -#include "rrrr_types.h" #include -#include #include -#include -#include -#include -#include -#include +#include + +tmode_t strtomode (const char *modes) { + /* TODO tokenize additional modes */ + tmode_t m = m_none; + if (0 == strncmp(modes, "TRAM", 4)) m |= m_tram; else + if (0 == strncmp(modes, "SUBWAY", 6)) m |= m_subway; else + if (0 == strncmp(modes, "RAIL", 4)) m |= m_rail; else + if (0 == strncmp(modes, "BUS", 3)) m |= m_bus; else + if (0 == strncmp(modes, "FERRY", 5)) m |= m_ferry; else + if (0 == strncmp(modes, "CABLE_CAR", 9)) m |= m_cablecar; else + if (0 == strncmp(modes, "GONDOLA", 7)) m |= m_gondola; else + if (0 == strncmp(modes, "FUNICULAR", 9)) m |= m_funicular; + return m; +} + +/* TODO: + * We might want to make this more generic to something like: + * dedup(void *base, size_t num, size_t size, int (*compar)(const void*, const void*)) + */ +uint32_t dedupRtime (rtime_t *base, uint32_t n) { + uint32_t i = 0, j = 0; + if (n == 0) return n; + + for (i = 1; i < n; i++) { + if (base[i] != base[j]) { + j++; + base[j] = base[i]; + } + } + return j + 1; +} /* buffer should always be at least 13 characters long, * including terminating null */ char *btimetext(rtime_t rt, char *buf) { - char *day; + const char *day; uint32_t t, s, m, h; if (rt == UNREACHED) { @@ -58,7 +88,7 @@ uint32_t rrrrandom(uint32_t limit) { */ rtime_t epoch_to_rtime (time_t epochtime, struct tm *tm_out) { struct tm ltm; - uint32_t seconds; + int seconds; rtime_t rtime; if (epochtime < SEC_IN_ONE_DAY) { @@ -107,21 +137,21 @@ time_t strtoepoch (char *time) { memset (<m, 0, sizeof(struct tm)); strptime (time, "%Y-%m-%dT%H:%M:%S", <m); ltm.tm_isdst = -1; - return mktime(<m); + return timegm(<m); } #else time_t strtoepoch (char *time) { char *endptr; struct tm ltm; memset (<m, 0, sizeof(struct tm)); - ltm.tm_year = strtol(time, &endptr, 10) - 1900; - ltm.tm_mon = strtol(&endptr[1], &endptr, 10) - 1; - ltm.tm_mday = strtol(&endptr[1], &endptr, 10); - ltm.tm_hour = strtol(&endptr[1], &endptr, 10); - ltm.tm_min = strtol(&endptr[1], &endptr, 10); - ltm.tm_sec = strtol(&endptr[1], &endptr, 10); + ltm.tm_year = (int) strtol(time, &endptr, 10) - 1900; + ltm.tm_mon = (int) strtol(&endptr[1], &endptr, 10) - 1; + ltm.tm_mday = (int) strtol(&endptr[1], &endptr, 10); + ltm.tm_hour = (int) strtol(&endptr[1], &endptr, 10); + ltm.tm_min = (int) strtol(&endptr[1], &endptr, 10); + ltm.tm_sec = (int) strtol(&endptr[1], &endptr, 10); ltm.tm_isdst = -1; - return mktime(<m); + return timegm(<m); } #endif @@ -129,17 +159,56 @@ time_t strtoepoch (char *time) { /* assumes little endian http://stackoverflow.com/a/3974138/778449 * size in bytes */ -void printBits(size_t const size, void const * const ptr) { - unsigned char *b = (unsigned char*) ptr; + +void renderBits(const void *ptr, uint32_t size, char *out) { + const unsigned char *b = (const unsigned char*) ptr; unsigned char byte; - int i, j; - for (i = size - 1; i >= 0; i--) { - for (j = 7; j >= 0; j--) { - byte = b[i] & (1 << j); - byte >>= j; - fprintf(stderr, "%u", byte); - } - } - puts(""); + + do { + uint8_t char_size = 8; + size--; + + do { + char_size--; + byte = b[size] & (1 << char_size); + byte >>= char_size; + + *out = '0' + (char) byte; + out++; + } while (char_size); + + } while (size); + + *out = '\0'; + out++; +} + +void printBits(uint32_t const n, void const * const ptr) { + char out[34] = ""; + assert(n << 3 <= 32); + renderBits(ptr, n, out); + fprintf(stderr, "%s", out); } #endif + +/* https://answers.yahoo.com/question/index?qid=20091214075728AArnEug */ +static int compareFloats(const void *elem1, const void *elem2) { + return (int) (((*((const float*) elem1)) - (*((const float *) elem2)))); +} + +/* http://en.wikiversity.org/wiki/C_Source_Code/Find_the_median_and_mean */ +float median(float *f, uint32_t n, float *min, float *max) { + qsort (f, n, sizeof(float), compareFloats); + + if (min) *min = f[0]; + if (max) *max = f[n - 1]; + + if((n & 1) == 0) { + /* even number of elements, return the mean of the two elements */ + return ((f[(n >> 1)] + f[(n >> 1) - 1]) / 2.0f); + } else { + /* else return the element in the middle */ + return f[n >> 1]; + } +} + diff --git a/util.h b/util.h index d02b95c..415feea 100644 --- a/util.h +++ b/util.h @@ -1,3 +1,8 @@ +/* Copyright 2013-2015 Bliksem Labs B.V. + * See the LICENSE file at the top-level directory of this distribution and at + * https://github.com/bliksemlabs/rrrr/ + */ + #include "rrrr_types.h" #include #include @@ -5,6 +10,18 @@ #include #include + +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? a : b) +#endif +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? a : b) +#endif + +#ifndef UNUSED +#define UNUSED(expr) (void)(expr) +#endif + #if defined (HAVE_LOCALTIME_R) #define rrrr_localtime_r(a, b) localtime_r(a, b) #elif defined (HAVE_LOCALTIME_S) @@ -27,14 +44,18 @@ } #endif -#define UNUSED(expr) (void)(expr) - #define rrrr_memset(s, u, n) { size_t i = n; do { i--; s[i] = u; } while (i); } +uint32_t dedupRtime (rtime_t *base, uint32_t n); uint32_t rrrrandom(uint32_t limit); -void printBits(size_t const size, void const * const ptr); +void printBits(uint32_t const size, void const * const ptr); +void renderBits(const void *ptr, uint32_t size, char *out); rtime_t epoch_to_rtime (time_t epochtime, struct tm *tm_out); char *btimetext(rtime_t rt, char *buf); char *timetext(rtime_t t); time_t strtoepoch (char *time); char * strcasestr(const char *s, const char *find); +tmode_t strtomode (const char *modes); + +double round(double x); +float median(float *in, uint32_t n, float *min, float *max);