diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index fa0785f16b..8b54b246be 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -269,6 +269,8 @@ jobs:
BUILDCACHE_DIRECT_MODE: true
BUILDCACHE_MAX_CACHE_SIZE: 26843545600
BUILDCACHE_LUA_PATH: ${{ github.workspace }}/tools
+ UBSAN_OPTIONS: halt_on_error=1:abort_on_error=1
+ ASAN_OPTIONS: alloc_dealloc_mismatch=0
steps:
- uses: actions/checkout@v4
@@ -291,6 +293,19 @@ jobs:
- name: Run Integration Tests
run: ${{ matrix.config.emulator }} build/motis-test
+ # ==== FULL DATASET TEST ====
+ - name: Test Full Dataset
+ if: matrix.config.preset != 'linux-debug'
+ run: |
+ ln -s deps/tiles/profile tiles-profiles
+ wget https://github.com/motis-project/test-data/raw/aachen/aachen.osm.pbf
+ wget https://github.com/motis-project/test-data/raw/aachen/AVV_GTFS_Masten_mit_SPNV.zip
+ ${{ matrix.config.emulator }} ./build/motis config aachen.osm.pbf AVV_GTFS_Masten_mit_SPNV.zip
+ ${{ matrix.config.emulator }} ./build/motis import
+ ${{ matrix.config.emulator }} ./build/motis generate -n 10
+ ${{ matrix.config.emulator }} ./build/motis batch
+ ${{ matrix.config.emulator }} ./build/motis compare -q queries.txt -r responses.txt responses.txt
+
# ==== DISTRIBUTION ====
- name: Create Distribution
if: matrix.config.artifact
@@ -318,7 +333,7 @@ jobs:
upload_url: ${{ github.event.release.upload_url }}
asset_path: ./motis-${{ matrix.config.artifact }}.tar.bz2
asset_name: motis-${{ matrix.config.artifact }}.tar.bz2
- asset_content_type: application/x-tar
+ asset_content_type: application/x-
docker:
runs-on: ubuntu-20.04
diff --git a/.github/workflows/visual-debugger-ci.yml b/.github/workflows/visual-debugger-ci.yml
new file mode 100644
index 0000000000..cf42a5b1b5
--- /dev/null
+++ b/.github/workflows/visual-debugger-ci.yml
@@ -0,0 +1,61 @@
+name: Visual Debugger CI
+
+on:
+ push:
+ branches:
+ - staging
+ pull_request:
+ branches:
+ - staging
+ workflow_dispatch:
+
+defaults:
+ run:
+ working-directory: ./visual-debugger
+
+jobs:
+ vdb-check:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 20
+
+ - name: Install dependencies
+ run: "npm install -f"
+
+ - name: Run svelte-check
+ run: "npm run check"
+
+ vdb-unit:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 20
+
+ - name: Install dependencies
+ run: "npm install -f"
+
+ - name: Run unit tests
+ run: "npm run test:unit"
+
+ # vdb-integration:
+ # - uses: actions/checkout@v4
+
+ # - uses: actions/setup-node@v4
+ # with:
+ # node-version: 20
+
+ # - name: Install dependencies
+ # run: "npm install -f"
+
+ # - name: Run integration tests
+ # continue-on-error: true
+ # run: "npm run test:integration"
diff --git a/.pkg b/.pkg
index 3971201ff9..26f6ae8217 100644
--- a/.pkg
+++ b/.pkg
@@ -1,23 +1,23 @@
[nigiri]
url=git@github.com:mority/nigiri.git
branch=dump_round_times
- commit=c7762cc0aa3d98c514118ca41792ee6888880ca8
+ commit=96d7eb3f0cc0c0588aee6a56239603d867de3577
[cista]
url=git@github.com:felixguendling/cista.git
branch=master
- commit=950f96f4ded53a6b5753824b280550b722933e55
+ commit=6362f3ad8c3133a0abf64e5d8c9ea3e21f531ee8
[osr]
url=git@github.com:motis-project/osr.git
branch=master
- commit=478bed28978461f0728c311899cc6bb22f0dd591
+ commit=650e05c5dc59598f84a5e9bae2db75d5cc3433b8
[utl]
url=git@github.com:motis-project/utl.git
branch=master
- commit=368fdcb8326ced5bb151b60c09a07c2e5f09bf55
+ commit=07fb33df7b79b73e84e0d2c69c6ee445221f6e32
[adr]
url=git@github.com:triptix-tech/adr.git
branch=master
- commit=9bf19dd88d7805d6bea1cebf5b09a394605fa3f6
+ commit=d5036b92fed3867abb474440987343a8ae4cb3bf
[googletest]
url=git@github.com:motis-project/googletest.git
branch=master
@@ -29,7 +29,7 @@
[openapi-cpp]
url=git@github.com:triptix-tech/openapi-cpp.git
branch=master
- commit=dac46d043f07a119d8b7d9ccb47e51049b259bfe
+ commit=688d45bd96addb26eaccc5d264761030e5ef43f9
[unordered_dense]
url=git@github.com:motis-project/unordered_dense.git
branch=master
@@ -54,3 +54,7 @@
url=git@github.com:motis-project/mimalloc.git
branch=dev
commit=e2f4fe647e8aff4603a7d5119b8639fd1a47c8a6
+[lz4]
+ url=git@github.com:motis-project/lz4.git
+ branch=dev
+ commit=c4765545ebb14b0a56c663e21923166923f8280e
diff --git a/.pkg.lock b/.pkg.lock
index 5625329ff0..9207d52383 100644
--- a/.pkg.lock
+++ b/.pkg.lock
@@ -1,11 +1,12 @@
-8453904136177281916
-cista 847b27100b7e730370b810ce62206a66b0bf2d79
+9158141483030055725
+cista 6362f3ad8c3133a0abf64e5d8c9ea3e21f531ee8
PEGTL 1c1aa6e650e4d26f10fa398f148ec0cdc5f0808d
res b759b93316afeb529b6cb5b2548b24c41e382fb0
date ce88cc33b5551f66655614eeebb7c5b7189025fb
googletest 7b64fca6ea0833628d6f86255a81424365f7cc0c
+docs 75dc89a53e9c2d78574fc0ffda698e69f1682ed2
fmt dc10f83be70ac2873d5f8d1ce317596f1fd318a2
-utl 368fdcb8326ced5bb151b60c09a07c2e5f09bf55
+utl 07fb33df7b79b73e84e0d2c69c6ee445221f6e32
oh d21c30f40e52a83d6dc09bcffd0067598b5ec069
zlib-ng 68ab3e2d80253ec5dc3c83691d9ff70477b32cd3
boost 930f38eb0365ceb7853273e03da4d9e7787abfb9
@@ -22,10 +23,11 @@ opentelemetry-cpp 60770dc9dc63e3543fc87d605b2e88fd53d7a414
pugixml 60175e80e2f5e97e027ac78f7e14c5acc009ce50
unordered_dense b33b037377ca966bbdd9cccc3417e46e88f83bfb
wyhash 1e012b57fc2227a9e583a57e2eacb3da99816d99
-nigiri c7762cc0aa3d98c514118ca41792ee6888880ca8
+nigiri 96d7eb3f0cc0c0588aee6a56239603d867de3577
+lz4 c4765545ebb14b0a56c663e21923166923f8280e
mimalloc e2f4fe647e8aff4603a7d5119b8639fd1a47c8a6
yaml-cpp 1d8ca1f35eb3a9c9142462b28282a848e5d29a91
-openapi-cpp dac46d043f07a119d8b7d9ccb47e51049b259bfe
+openapi-cpp 688d45bd96addb26eaccc5d264761030e5ef43f9
net 6a457f5eaa077078fcac4153c5a178657737eee8
conf f9bf4bd83bf55a2170725707e526cbacc45dcc66
expat 636c9861e8e7c119f3626d1e6c260603ab624516
@@ -42,9 +44,9 @@ sol2 40c7cbc7c5cfed1e8c7f1bbe6fcbe23d7a67fc75
variant 5aa73631dc969087c77433a5cdef246303051f69
tiles ab6c4b13544570f893c2d64434c613d8fd7d2ceb
rtree.c 6ed73a7dc4f1184f2b5b2acd8ac1c2b28a273057
-osr 478bed28978461f0728c311899cc6bb22f0dd591
+osr 650e05c5dc59598f84a5e9bae2db75d5cc3433b8
reflect-cpp c54fe66de4650b60c23aadd4a06d9db4ffeda22f
FTXUI dd6a5d371fd7a3e2937bb579955003c54b727233
tg 20c0f298b8ce58de29a790290f44dca7c4ecc364
utf8proc 779b780da3b99d123133eb99707b65c7e4324cc8
-adr 9bf19dd88d7805d6bea1cebf5b09a394605fa3f6
+adr d5036b92fed3867abb474440987343a8ae4cb3bf
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d69ec847b2..3046f1813a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -121,6 +121,8 @@ target_link_libraries(motislib
pbf_sdf_fonts_res-res
ssl
crypto
+ tg
+ lz4_static
)
@@ -144,7 +146,6 @@ target_link_libraries(motis
rtree
geo
cista
- tg
ianatzdb-res
pbf_sdf_fonts_res-res
tiles_server_res-res
@@ -197,3 +198,9 @@ add_custom_target(motis-web-ui
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/ui"
VERBATIM
)
+
+foreach(t mimalloc adr osr nigiri gtfsrt
+ geo tiles tiles-import-library
+ motis motis-api motislib)
+ target_compile_options(${t} PUBLIC ${MOTIS_TARGET_FLAGS})
+endforeach()
diff --git a/README.md b/README.md
index 3adfc35b9e..26c1267710 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,5 @@
![](logo.svg)
-> [!NOTE]
-> This is the MOTIS 2 beta version.
-> You can find the `0.x.y` legacy branch [here](https://github.com/motis-project/motis/tree/legacy).
-
> [!TIP]
> :sparkles: Join the international MOTIS community at [**motis:matrix.org**](https://matrix.to/#/#motis:matrix.org)
@@ -60,7 +56,9 @@ Features can be turned on and off as needed.
- Download one or more GTFS datasets and place them in the folder
```bash
-./motis my.osm.pbf my.gtfs.zip
+./motis config my.osm.pbf gtfs.zip # generates a minimal config.yml
+./motis import # preprocesses data
+./motis server # starts a HTTP server on port 8080
```
This will preprocess the input files and create a `data` folder.
@@ -81,7 +79,9 @@ wget https://github.com/motis-project/motis/releases/latest/download/motis-${TAR
tar xf motis-${TARGET}.tar.bz2
wget https://github.com/motis-project/test-data/raw/aachen/aachen.osm.pbf
wget https://opendata.avv.de/current_GTFS/AVV_GTFS_Masten_mit_SPNV.zip
-./motis aachen.osm.pbf AVV_GTFS_Masten_mit_SPNV.zip
+./motis config aachen.osm.pbf AVV_GTFS_Masten_mit_SPNV.zip
+./motis import
+./motis server
```
**Windows**
@@ -90,7 +90,9 @@ wget https://opendata.avv.de/current_GTFS/AVV_GTFS_Masten_mit_SPNV.zip
Invoke-WebRequest https://github.com/motis-project/motis/releases/latest/download/motis-windows.zip -OutFile motis-windows.zip
Expand-Archive motis-windows.zip
Invoke-WebRequest https://github.com/motis-project/test-data/archive/refs/heads/aachen.zip -OutFile aachen.zip
-./motis aachen.osm.pbf AVV_GTFS_Masten_mit_SPNV.zip
+./motis config aachen.osm.pbf AVV_GTFS_Masten_mit_SPNV.zip
+./motis import
+./motis server
```
# Documentation
diff --git a/docs/STYLE.md b/docs/STYLE.md
new file mode 100644
index 0000000000..282379dcef
--- /dev/null
+++ b/docs/STYLE.md
@@ -0,0 +1,345 @@
+# MOTIS C++ Style
+
+# Preamble
+
+Beaware that these rules only apply to MOTIS C++ and are very opinionated.
+C++ has a big diversity of programming styles from "C with classes" to "Modern C++".
+A lot of codebases have specific rules that make sense in this specific context
+(e.g. embedded programming, gaming, Google search, etc.) and therefore different
+guidelines. Over the years we learned that the rules described here are a good fit
+for this specific project.
+
+So in general our goals are:
+
+- We want high-level, maintainable C++ code by default, not "high level assembly"
+- but: don’t use features just because you can (like template meta programming, etc.)
+
+# Style
+
+- Header names: ***`*.h`**, Implementation names: **`*.cc`**
+- Don’t use include guards (`#ifndef #define #endif`), use **`#pragma once`**
+- Consistently use **`struct`** instead of `class`
+ - default visibility: public (which is what we need → no getter / setter)
+ - you don’t need to write a constructor for 1-line initialization
+- Always use ++i instead of i++ if it makes no difference for the program logic:
+ `for (auto i = 0U; i < 10; ++i) { … }`
+- Don't `using namespace std;`
+- Don’t use `NULL` or `0`, use **nullptr** instead
+- Don’t write `const auto&`, write **`auto const&`**
+- Don’t write `const char*`, write **`char const`**
+
+
+# Case
+
+- Everything **`snake_case`** (as in the C++ Standard Library)
+- Template parameters **`PascalCase`** (as in the C++ Standard Library)
+- Constants **`kPascalCase`** (as in the Google C++ Styleguide), not `UPPER_CASE` to prevent collisions with macro names
+- Postfix **`member_variables_`** with an underscore to improve code readability when reading code without an IDE
+
+```cpp
+constexpr auto kMyConstant = 3.141;
+
+template
+struct my_class : public my_parent {
+ void member_fn(std::string const& fn_param) const override {
+ auto const local_cvar = abc();
+ auto local_var = def();
+ }
+ int my_field_;
+};
+```
+
+# Includes
+
+- Include only what you use (but everything you use!)
+- Group includes:
+ - for `.cc` files: first its own `.h` file
+ - Standard headers with `<...>` syntax
+ - C headers (use `` instead of ``, etc.)
+ - C++ standard library headers (e.g. ``)
+ - Non-standard headers with `"..."` syntax
+ - generic to specific = boost libraries, then more and more specific
+ - last: project includes
+ - if available: local inclues `"./test_util.h"` from the local folder (only done for tests)
+- Do not repeat include files from your own header file
+- Repeat everything else - even it's transitiveley included already through other headers.
+ The include might be removed from the header you include which leads broken compilation.
+ Try to make the compilation as robust as possible.
+
+
+Example include files for `message.cc`:
+```cpp
+#include "motis/module/message.h"
+
+#include
+#include
+
+#include "boost/asio.hpp"
+
+#include "flatbuffers/idl.h"
+#include "flatbuffers/util.h"
+
+#include "motis/core/common/logging.h"
+```
+
+# Simplify Code: Predicate Usage
+
+```cpp
+// bad
+if (is_valid()) {
+ set_param(false);
+} else {
+ set_param(true);
+}
+
+// bad
+set_param(is_valid() ? false : true);
+
+// good
+set_param(!is_valid());
+```
+
+# Always use Braces
+
+```cpp
+// bad
+for (auto i = 0u; i < 10; ++i)
+ if (is_valid())
+ return get_a();
+ else
+ count_b();
+
+// good
+for (auto i = 0u; i < 10; ++i) {
+ if (is_valid()) {
+ return get_a();
+ } else {
+ count_b();
+ }
+}
+```
+
+# Use Short Variable Names
+
+Only use shortened version of the variable name if it's still obvious what the variable holds.
+
+- Index = `idx`
+- Input = `in`
+- Output = `out`
+- Request = `req`
+- Response = `res`
+- Initialization = `init`
+- ... etc.
+
+If the context in which the variable is used is short, you can make variable names even shorter. For example `for (auto const& e : events) { /* ... */ }` or `auto const& b = get_buffer()`.
+
+Don't use `lhs` and `rhs` - for comparison with `friend bool operator==`. Use `a` and `b`.
+
+# Signatures in Headers
+
+Omit information that's not needed for a forward declaration.
+
+```cpp
+void* get_memory(my_memory_manager& memory_manager); // bad
+
+void* get_memory(my_memory_manager&); // good
+
+// const for value parameters is not needed in headers
+void calc_mask(bool const, bool const, bool const, bool const); // bad
+
+void calc_mask(bool local_traffic, // slightly less bad
+ bool long_distance_traffic,
+ bool local_stations,
+ bool long_distance_stations);
+
+void calc_mask(mask_options); // good
+```
+
+# Low Indentation
+
+Try to keep indentation at a minimum by handling cases one by one and bailing out early.
+
+Example:
+
+Bad:
+
+```cpp
+int main(int argc, char** argv) {
+ if (argc > 1) {
+ for (int i = 0; i < argc; ++i) {
+ if (std::strcmp("hello", argv[i]) == 0) {
+ /* ... 100 lines of code ... */
+ }
+ }
+ }
+}
+```
+
+Good:
+
+```cpp
+int main(int argc, char** argv) {
+ if (argc <= 1) {
+ return 0;
+ }
+ for (int i = 0; i < argc; ++i) {
+ if (std::strcmp("hello", argv[i]) != 0) {
+ continue;
+ }
+ /* ... 100 lines of code ... */
+ }
+}
+```
+
+# Function Length / File Length
+
+Functions should have one task only. If they grow over ~50 lines of code, please check if they could be split into several functions to improve readability. But: don't split just randomly to not go over some arbitrary lines of code limit.
+
+- Better: split earlier if it makes sense! Files are free! (more than one responsibility)
+- Split later i.e. if you want to keep one block of logic without interruption (easier to understand)
+
+# Pointers
+
+Read C++ data types from right to left:
+
+**`int const* const`**
+- `const` (read only) pointer (address can't be modified)
+- to `const int` (int value at address can't be modified)
+
+**int const&**
+- reference
+- on a const `int` value (read only)
+
+**auto const&**
+- reference
+- on a value (type deduced by the compiler)
+
+# Use RAII
+
+Whenever possible use RAII to manage resource like memory (`std::unique_ptr`, `std::shared_ptr`, etc.), files (`std::fstream`), network sockets (Boost Asio), etc.
+
+This means we do not want `new` or `delete` - except for placement new or placement delete in some very specific cases.
+
+# Use `utl` Library
+
+If there is no tool available in the C++ Standard Library please check first if we already have something in our [utl](https://github.com/motis-project/utl) library.
+
+# Use `strong` types
+
+Use `cista::strong` to define types, that cannot be converted implicitly. Using a `strong` type will ensure, that parameters cannot be mismatched, unlike `int` or `std::size_t`. This also makes function parameters clearer.
+
+# `const`
+
+Make everything (variables, loop variables, member functions, etc.) as `const` as possible. This indicates thread-safety (as long as only `const` methods are used) and helps to catch bugs when our mental model doesn't match the reality (the compiler will tell us).
+
+# Initializaiton
+
+Use [Aggregate Initialization](https://en.cppreference.com/w/cpp/language/aggregate_initialization) if possible. This also applies to member variables. A big advantage is that it doesn't allow implicit type conversions.
+
+# Namespaces
+
+Rename long namespace names instead of importing them completely.
+
+```cpp
+using boost::program_options; // bad
+namespace po = boost::program_option; // good
+```
+
+This way we still know where functions come from when reading code.
+It becomes hard to know where a function came from when several large namespaces are completely imported.
+
+Don't alias or import namespaces in header files.
+
+# AAA-Style
+
+Use [Almost Alway Auto (AAA)](https://herbsutter.com/2013/08/12/gotw-94-solution-aaa-style-almost-always-auto/) style if possible.
+
+- Program against interfaces
+- Abstraction
+- Less typing
+
+Example: `for (auto const& el : c())`
+
+No client code change if
+- c returns another collection type (i.e. set instead of vector)
+- the element type changes but still has a compatible interface
+
+# No Raw Loops
+
+It takes time to understand a raw for loop:
+```cpp
+for (int i = -1; i <= 9; i += 2) {
+ if (i % 2 == 0) { continue; }
+ if (i > 5 && i % 2 == 1) { break; }
+ printf(“%s\n”, i/3);
+}
+```
+
+- Raw for loops can
+ - do crazy things
+ - be boring (can often be expressed with a standard library algorithm!!)
+
+- Find an element loop → `std::find`, `std::lower_bound`, ...
+- Check each element loop → `std::all_of`, `std::none_of`, `std::any_of`
+- Conversion loop → `std::transform`, `utl::to_vec`
+- Counting: `std::count_if`, `std::accumulate`
+- Sorting: `std::sort`, `std::nth_element`, `std::is_sorted`
+- Logic: `std::all_of`, `std::any_of`
+- Iterating multiple elements at once: `utl::zip`, `utl::pairwise`, `utl::nwise`
+- Erasing elements: `utl::erase_if`, `utl::erase_duplicates`
+- etc.
+
+Hint: `utl` provides a cleaner interface wrapping `std::` functions for collections so you don't have to call `begin` and `end` all the time!
+
+Benefits:
+- Function name tells the reader of your code already what it does!
+- Standard library implementation does not contain errors and is performant!
+
+Alternative (if no function in the standard or `utl` helps):
+- Use range based for loop if there's no named function: `for (auto const& el : collection) { .. }`
+
+# Comparators
+
+Either use
+- Preferred: mark the operate you need `= default;`
+- If that doesn't do the job you can check `CISTA_FRIEND_COMPARABLE`
+- If you want to be selective and only compare a subset of member variables: `std::tie(a_, b_) == std::(a_, b_)`
+
+# Set/Map vs Vector
+
+Our go-to data structure is `std::vector`. (Hash-)maps and (hash-)sets are very expensive.
+
+Never use `std::unordered_map`. We have better alternatives in all projects (e.g. unordered_dense).
+
+## `vecvec` and `vector_map`
+
+- Use `vector_map` for mappings with a `strong` key type and a continuous domain.
+- Prefer using `vecvec` instead of `vector>`, as data is stored and accessed more efficient. To store data, that may appear in any order, you may consider `paged_vecvec` instead.
+
+# Tooling
+
+- Always develop with Address Sanitizer (ASan) and Undefined Behaviour Sanitizer (UBSan) enabled if performance allows it (it's usually worth it to use small data sets to be able to develop with sanitizers enabled!): `CXXFLAGS=-fno-omit-frame-pointer -fsanitizer=address,undefined`.
+ - **Notice**: Some checks can cause false positive and should be disabled if necessary (compare `ci.yml`).
+ Example: `ASAN_OPTIONS=alloc_dealloc_mismatch=0`
+- Check your code with `valgrind`.
+
+# Spirit
+
+- No deep inheritance hierarchies (no "enterprise" code)
+- Don't write getters / setters for member variables: just make them public
+ (which is the default for `struct` - remember: always use structs)
+- Don't introduce a new variable for every value if it gets used only one time and the variable doesn't tell the reader any important information (-> inline variables).
+- No GoF "design patterns" (Factory, Visitor, ...) if there is a simpler solution (there's always a simpler solution)
+- Function / struct length:
+ - it should be possible to understand every function by shortly looking at it
+ - hints where to split:
+ - single responsibility
+ - short enough to be reusable in another context
+- Don’t write “extensible” code that cares for functionality you might need at some point in the future. Just solve the problem at hand.
+- Build the **smallest and simplest** solution possible that solves your problem
+- Use abstractions to avoid thinking about details: helps to keep functions short
+- Comment only the tricky / hacky pieces of your code
+ (there should not be too many comments, otherwise your code is bad)
+- Instead of comments use good (but short!) names for variables and functions
+- Less code = less maintenance, less places for bugs, easier to understand
+- Write robust code: `utl::verify()` assumptions about input data
diff --git a/exe/flags.h b/exe/flags.h
new file mode 100644
index 0000000000..890ecd4d91
--- /dev/null
+++ b/exe/flags.h
@@ -0,0 +1,38 @@
+#pragma once
+
+#include
+
+#include "boost/program_options.hpp"
+
+namespace motis {
+
+inline void add_data_path_opt(boost::program_options::options_description& desc,
+ std::filesystem::path& p) {
+ desc.add_options()(
+ "data,d", boost::program_options::value(&p)->default_value(p),
+ "The data path contains all preprocessed data as well as a `config.yml`. "
+ "It will be created by the `motis import` command. After the import has "
+ "finished, `motis server` only needs the `data` folder and can run "
+ "without the input files (such as OpenStreetMap file, GTFS datasets, "
+ "tiles-profiles, etc.)");
+}
+
+inline void add_config_path_opt(
+ boost::program_options::options_description& desc,
+ std::filesystem::path& p) {
+ desc.add_options()(
+ "config,c", boost::program_options::value(&p)->default_value(p),
+ "Configuration YAML file. Legacy INI files are still supported but this "
+ "support will be dropped in the future.");
+}
+
+inline boost::program_options::variables_map parse_opt(
+ int ac, char** av, boost::program_options::options_description& desc) {
+ namespace po = boost::program_options;
+ auto vm = po::variables_map{};
+ po::store(po::command_line_parser(ac, av).options(desc).run(), vm);
+ po::notify(vm);
+ return vm;
+}
+
+} // namespace motis
\ No newline at end of file
diff --git a/exe/generate.cc b/exe/generate.cc
new file mode 100644
index 0000000000..183febb113
--- /dev/null
+++ b/exe/generate.cc
@@ -0,0 +1,341 @@
+#include "conf/configuration.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include "boost/json/parse.hpp"
+#include "boost/json/serialize.hpp"
+#include "boost/json/value_from.hpp"
+#include "boost/json/value_to.hpp"
+#include "boost/url/url.hpp"
+
+#include "fmt/std.h"
+
+#include "utl/enumerate.h"
+#include "utl/get_or_create.h"
+#include "utl/helpers/algorithm.h"
+#include "utl/init_from.h"
+#include "utl/parallel_for.h"
+#include "utl/parser/cstr.h"
+#include "utl/sorted_diff.h"
+#include "utl/timing.h"
+
+#include "nigiri/routing/query.h"
+#include "nigiri/timetable.h"
+
+#include "motis-api/motis-api.h"
+#include "motis/config.h"
+#include "motis/data.h"
+#include "motis/endpoints/routing.h"
+#include "motis/place.h"
+#include "motis/point_rtree.h"
+#include "motis/tag_lookup.h"
+
+#include "./flags.h"
+
+namespace n = nigiri;
+namespace fs = std::filesystem;
+namespace po = boost::program_options;
+namespace json = boost::json;
+
+namespace motis {
+
+static std::atomic_uint32_t seed{0U};
+
+std::uint32_t rand_in(std::uint32_t const from, std::uint32_t const to) {
+ auto a = ++seed;
+ a = (a ^ 61U) ^ (a >> 16U);
+ a = a + (a << 3U);
+ a = a ^ (a >> 4U);
+ a = a * 0x27d4eb2d;
+ a = a ^ (a >> 15U);
+ return from + (a % (to - from));
+}
+
+template
+It rand_in(It const begin, It const end) {
+ return std::next(
+ begin,
+ rand_in(0U, static_cast(std::distance(begin, end))));
+}
+
+template
+Collection::value_type rand_in(Collection const& c) {
+ using std::begin;
+ using std::end;
+ utl::verify(!c.empty(), "empty collection");
+ return *rand_in(begin(c), end(c));
+}
+
+n::location_idx_t random_stop(n::timetable const& tt,
+ std::vector const& stops) {
+ auto s = n::location_idx_t::invalid();
+ do {
+ s = rand_in(stops);
+ } while (tt.location_routes_[s].empty());
+ return s;
+}
+
+int generate(int ac, char** av) {
+ auto data_path = fs::path{"data"};
+ auto n = 100U;
+
+ auto desc = po::options_description{"Options"};
+ desc.add_options() //
+ ("help", "Prints this help message") //
+ ("n,n", po::value(&n)->default_value(n), "number of queries");
+ add_data_path_opt(desc, data_path);
+ auto vm = parse_opt(ac, av, desc);
+
+ if (vm.count("help")) {
+ std::cout << desc << "\n";
+ return 0;
+ }
+
+ auto const c = config::read(data_path / "config.yml");
+ utl::verify(c.timetable_.has_value(), "timetable required");
+
+ auto d = data{data_path, c};
+ utl::verify(d.tt_, "timetable required");
+
+ auto stops = std::vector{};
+ stops.resize(d.tt_->n_locations());
+ for (auto i = 0U; i != stops.size(); ++i) {
+ stops[i] = n::location_idx_t{i};
+ }
+
+ {
+ auto out = std::ofstream{"queries.txt"};
+ for (auto i = 0U; i != n; ++i) {
+ auto p = api::plan_params{};
+ p.fromPlace_ = d.tags_->id(*d.tt_, random_stop(*d.tt_, stops));
+ p.toPlace_ = d.tags_->id(*d.tt_, random_stop(*d.tt_, stops));
+ out << p.to_url("/api/v1/plan") << "\n";
+ }
+ }
+
+ return 0;
+}
+
+int batch(int ac, char** av) {
+ auto data_path = fs::path{"data"};
+ auto queries_path = fs::path{"queries.txt"};
+ auto responses_path = fs::path{"responses.txt"};
+
+ auto desc = po::options_description{"Options"};
+ desc.add_options() //
+ ("help", "Prints this help message") //
+ ("queries,q", po::value(&queries_path)->default_value(queries_path),
+ "queries file") //
+ ("responses,r", po::value(&responses_path)->default_value(responses_path),
+ "response file");
+ add_data_path_opt(desc, data_path);
+
+ auto vm = parse_opt(ac, av, desc);
+ if (vm.count("help")) {
+ std::cout << desc << "\n";
+ return 0;
+ }
+
+ auto queries = std::vector{};
+ {
+ auto f = cista::mmap{queries_path.generic_string().c_str(),
+ cista::mmap::protection::READ};
+ utl::for_each_token(utl::cstr{f.view()}, '\n', [&](utl::cstr s) {
+ queries.push_back(api::plan_params{boost::urls::url{s.view()}.params()});
+ });
+ }
+
+ auto const c = config::read(data_path / "config.yml");
+ utl::verify(c.timetable_.has_value(), "timetable required");
+
+ auto d = data{data_path, c};
+ utl::verify(d.tt_, "timetable required");
+
+ auto mtx = std::mutex{};
+ auto out = std::ofstream{responses_path};
+ auto total = std::atomic_uint64_t{};
+ auto const routing = utl::init_from(d).value();
+ utl::parallel_for_run(queries.size(), [&](std::size_t const id) {
+ UTL_START_TIMING(total);
+ auto response = routing(queries.at(id).to_url("/api/v1/plan"));
+ UTL_STOP_TIMING(total);
+
+ auto const timing = static_cast(UTL_TIMING_MS(total));
+ response.debugOutput_.emplace("id", id);
+ response.debugOutput_.emplace("timing", timing);
+ {
+ auto const lock = std::scoped_lock{mtx};
+ out << json::serialize(json::value_from(response)) << "\n";
+ }
+ total += timing;
+ });
+
+ std::cout << "AVG: "
+ << (static_cast(total) /
+ static_cast(queries.size()))
+ << "\n";
+
+ return 0U;
+}
+
+int compare(int ac, char** av) {
+ auto queries_path = fs::path{"queries.txt"};
+ auto responses_paths = std::vector{};
+ auto desc = po::options_description{"Options"};
+ desc.add_options() //
+ ("help", "Prints this help message") //
+ ("queries,q", po::value(&queries_path)->default_value(queries_path),
+ "queries file") //
+ ("responses,r",
+ po::value(&responses_paths)
+ ->multitoken()
+ ->default_value(responses_paths),
+ "response files");
+
+ auto vm = parse_opt(ac, av, desc);
+ if (vm.count("help")) {
+ std::cout << desc << "\n";
+ return 0;
+ }
+
+ auto const open_file = [](fs::path const& p) {
+ auto f = std::ifstream{};
+ f.exceptions(std::ios_base::failbit | std::ios_base::badbit);
+ try {
+ f.open(p);
+ } catch (std::exception const& e) {
+ throw utl::fail("could not open file \"{}\": {}", p, e.what());
+ }
+ return f;
+ };
+
+ auto const read_line = [](std::ifstream& f) -> std::optional {
+ if (f.peek() == EOF || f.eof()) {
+ return std::nullopt;
+ }
+ std::string line;
+ std::getline(f, line);
+ return line;
+ };
+
+ struct info {
+ unsigned id_;
+ std::optional params_{};
+ std::vector> responses_{};
+ };
+ auto response_buf = hash_map{};
+ auto const get = [&](unsigned const id) -> info& {
+ return utl::get_or_create(response_buf, id, [&]() {
+ auto x = info{.id_ = id};
+ x.responses_.resize(responses_paths.size());
+ return x;
+ });
+ };
+ auto const is_finished = [](info const& x) {
+ return x.params_.has_value() &&
+ utl::all_of(x.responses_, [](auto&& r) { return r.has_value(); });
+ };
+ auto const params = [](api::Itinerary const& x) {
+ return std::tie(x.startTime_, x.endTime_, x.transfers_);
+ };
+ auto const print_params = [](api::Itinerary const& x) {
+ std::cout << x.startTime_ << ", " << x.endTime_
+ << ", transfers=" << x.transfers_;
+ };
+ auto const print_none = []() { std::cout << "\t\t\t\t\t\t"; };
+ auto n_equal = 0U;
+ auto const print_differences = [&](info const& x) {
+ auto const& ref = x.responses_[0].value().itineraries_;
+ for (auto i = 1U; i < x.responses_.size(); ++i) {
+ auto const uut = x.responses_[i].value().itineraries_;
+ if (std::ranges::equal(ref | std::views::transform(params),
+ uut | std::views::transform(params))) {
+ ++n_equal;
+ continue;
+ }
+
+ std::cout << "QUERY=" << x.id_ << "\n";
+ utl::sorted_diff(
+ ref, uut,
+ [&](api::Itinerary const& a, api::Itinerary const& b) {
+ return params(a) < params(b);
+ },
+ [&](api::Itinerary const&, api::Itinerary const&) {
+ return false; // always call for equal
+ },
+ utl::overloaded{
+ [&](utl::op op, api::Itinerary const& j) {
+ if (op == utl::op::kAdd) {
+ print_none();
+ std::cout << "\t\t\t";
+ print_params(j);
+ std::cout << "\n";
+ } else {
+ print_params(j);
+ std::cout << "\t\t\t";
+ print_none();
+ std::cout << "\n";
+ }
+ },
+ [&](api::Itinerary const& a, api::Itinerary const& b) {
+ print_params(a);
+ std::cout << "\t\t\t";
+ print_params(b);
+ std::cout << "\n";
+ }});
+ std::cout << "\n\n";
+ }
+ };
+ auto n_consumed = 0U;
+ auto const consume_if_finished = [&](info const& x) {
+ if (!is_finished(x)) {
+ return;
+ }
+ print_differences(x);
+ response_buf.erase(x.id_);
+ ++n_consumed;
+ };
+
+ auto query_file = open_file(queries_path);
+ auto responses_files =
+ utl::to_vec(responses_paths, [&](auto&& p) { return open_file(p); });
+
+ auto query_id = 0U;
+ auto done = false;
+ while (!done) {
+ done = true;
+
+ if (auto const q = read_line(query_file); q.has_value()) {
+ auto& info = get(query_id++);
+ info.params_ = api::plan_params{boost::urls::url{*q}.params()};
+ consume_if_finished(info);
+ done = false;
+ }
+
+ for (auto const [i, res_file] : utl::enumerate(responses_files)) {
+ if (auto const r = read_line(res_file); r.has_value()) {
+ auto res =
+ boost::json::value_to(boost::json::parse(*r));
+ utl::sort(res.itineraries_,
+ [&](auto&& a, auto&& b) { return params(a) < params(b); });
+ auto const id = res.debugOutput_.at("id");
+ auto& info = get(static_cast(id));
+ info.responses_[i] = std::move(res);
+ consume_if_finished(info);
+ done = false;
+ }
+ }
+ }
+
+ std::cout << "consumed: " << n_consumed << "\n";
+ std::cout << "buffered: " << response_buf.size() << "\n";
+ std::cout << " equal: " << n_equal << "\n";
+
+ return 0;
+}
+
+} // namespace motis
\ No newline at end of file
diff --git a/exe/main.cc b/exe/main.cc
index c3683a66d5..b66571d53b 100644
--- a/exe/main.cc
+++ b/exe/main.cc
@@ -12,6 +12,8 @@
#include "motis/import.h"
#include "motis/server.h"
+#include "./flags.h"
+
#if !defined(MOTIS_VERSION)
#define MOTIS_VERSION "unknown"
#endif
@@ -20,89 +22,82 @@ namespace po = boost::program_options;
using namespace std::string_view_literals;
namespace fs = std::filesystem;
+namespace motis {
+int generate(int, char**);
+int batch(int, char**);
+int compare(int, char**);
+} // namespace motis
+
using namespace motis;
int main(int ac, char** av) {
- auto data_path = fs::path{"data"};
- auto config_path = fs::path{"config.yml"};
-
- auto desc = po::options_description{"Global options"};
- desc.add_options() //
- ("version", "Prints the MOTIS version") //
- ("help", "Prints this help message") //
- ("data,d", po::value(&data_path)->default_value(data_path),
- "The data path contains all preprocessed data as well as a `config.yml` "
- "and is required by `motis server`. It will be created by the `motis "
- "import` command. After the import has finished, `motis server` only "
- "needs the `data` folder and can run without the input files (such as "
- "OpenStreetMap file, GTFS datasets, tiles-profiles, etc.)") //
- ("config,c", po::value(&config_path)->default_value(config_path),
- "Configuration YAML file. Legacy INI files are still supported but this "
- "support will be dropped in the future.") //
- ("command", po::value(),
- "Command to execute:\n"
- " - \"import\": preprocesses the input data\n"
- " and creates the `data` folder.\n"
- " - \"server\": serves static files\n"
- " and all API endpoints such as\n"
- " routing, geocoding, tiles, etc.") //
- ("paths", po::value>(),
- "List of paths to import for the simple mode. File type will be "
- "determined based on extension:\n"
- " - \".osm.pbf\" will be used as\n"
- " OpenStreetMap file.\n"
- " This enables street routing,\n"
- " geocoding and map tiles\n"
- " - the rest will be interpreted as\n"
- " static timetables.\n"
- " This enables transit routing");
-
- auto const help = [&]() {
- std::cout << "MOTIS " << MOTIS_VERSION << "\n\n"
- << "Usage:\n"
- " - simple: motis [PATHS...]\n"
- " - import: motis import [-c config.yml] [-d data_dir]\n"
- " - server: motis server [-d data_dir]\n\n"
- << desc << "\n";
- };
-
- enum mode { kImport, kServer, kSimple } mode = kSimple;
- if (ac > 1) {
- auto const cmd = std::string_view{av[1]};
- switch (cista::hash(cmd)) {
- case cista::hash("import"):
- mode = kImport;
- --ac;
- ++av;
- break;
- case cista::hash("server"):
- mode = kServer;
- --ac;
- ++av;
- break;
- }
- } else {
- help();
- return 1;
- }
-
- auto pos = po::positional_options_description{}.add("paths", -1);
- auto vm = po::variables_map{};
- po::store(po::command_line_parser(ac, av).options(desc).positional(pos).run(),
- vm);
- po::notify(vm);
-
- if (vm.count("version")) {
- std::cout << MOTIS_VERSION << "\n";
+ if (ac > 1 && av[1] == "--help"sv) {
+ fmt::println(
+ "MOTIS {}\n\n"
+ "Usage:\n"
+ " --help print this help message\n"
+ " --version print program version\n\n"
+ "Commands:\n"
+ " generate generate random queries and write them to a file\n"
+ " batch run queries from a file\n"
+ " compare compare results from different batch runs\n"
+ " config generate a config file from a list of input files\n"
+ " import prepare input data, creates the data directory\n"
+ " server starts a web server serving the API\n",
+ MOTIS_VERSION);
return 0;
- } else if (vm.count("help")) {
- help();
+ } else if (ac <= 1 || (ac >= 2 && av[1] == "--version"sv)) {
+ fmt::println("{}", MOTIS_VERSION);
return 0;
}
- switch (mode) {
- case kServer:
+ // Skip program argument, quit if no command.
+ --ac;
+ ++av;
+
+ // Execute command.
+ auto const cmd = std::string_view{av[0]};
+ switch (cista::hash(cmd)) {
+ case cista::hash("generate"): return generate(ac, av);
+ case cista::hash("batch"): return batch(ac, av);
+ case cista::hash("compare"): return compare(ac, av);
+
+ case cista::hash("config"): {
+ auto paths = std::vector{};
+ for (auto i = 1; i != ac; ++i) {
+ paths.push_back(std::string{av[i]});
+ }
+ if (paths.empty() || paths.front() == "--help") {
+ fmt::println(
+ "usage: motis config [PATHS...]\n\n"
+ "Generates a config.yml file in the current working "
+ "directory.\n\n"
+ "File type will be determined based on extension:\n"
+ " - \".osm.pbf\" will be used as OpenStreetMap file.\n"
+ " This enables street routing, geocoding and map tiles\n"
+ " - the rest will be interpreted as static timetables.\n"
+ " This enables transit routing."
+ "\n\n"
+ "Example: motis config germany-latest.osm.pbf "
+ "germany.gtfs.zip\n");
+ return paths.empty() ? 1 : 0;
+ }
+ std::ofstream{"config.yml"} << config::read_simple(paths) << "\n";
+ return 0;
+ }
+
+ case cista::hash("server"):
try {
+ auto data_path = fs::path{"data"};
+
+ auto desc = po::options_description{"Server Options"};
+ add_data_path_opt(desc, data_path);
+ auto vm = parse_opt(ac, av, desc);
+ if (vm.count("help")) {
+ std::cout << desc << "\n";
+ return 0;
+ }
+
auto const c = config::read(data_path / "config.yml");
return server(data{data_path, c}, c);
} catch (std::exception const& e) {
@@ -110,9 +105,21 @@ int main(int ac, char** av) {
return 1;
}
- case kImport: {
+ case cista::hash("import"): {
auto c = config{};
try {
+ auto data_path = fs::path{"data"};
+ auto config_path = fs::path{"config.yml"};
+
+ auto desc = po::options_description{"Import Options"};
+ add_data_path_opt(desc, data_path);
+ add_config_path_opt(desc, config_path);
+ auto vm = parse_opt(ac, av, desc);
+ if (vm.count("help")) {
+ std::cout << desc << "\n";
+ return 0;
+ }
+
c = config_path.extension() == ".ini" ? config::read_legacy(config_path)
: config::read(config_path);
auto const bars = utl::global_progress_bars{false};
@@ -124,20 +131,6 @@ int main(int ac, char** av) {
return 1;
}
}
-
- case kSimple:
- try {
- auto const bars = utl::global_progress_bars{false};
- auto args = vm.count("paths")
- ? vm.at("paths").as>()
- : std::vector{};
-
- auto const c = config::read_simple(args);
- server(import(c, data_path), c);
- } catch (std::exception const& e) {
- std::cerr << "error: " << e.what() << "\n";
- }
- return 0;
}
google::protobuf::ShutdownProtobufLibrary();
diff --git a/include/motis/box_rtree.h b/include/motis/box_rtree.h
new file mode 100644
index 0000000000..adbe533162
--- /dev/null
+++ b/include/motis/box_rtree.h
@@ -0,0 +1,110 @@
+#pragma once
+
+#include
+
+#include "cista/strong.h"
+
+#include "rtree.h"
+
+#include "geo/box.h"
+#include "geo/latlng.h"
+
+namespace motis {
+
+template
+concept BoxRtreePosHandler = requires(geo::box const& b, T const x, Fn&& f) {
+ { std::forward(f)(b, x) };
+};
+
+template
+struct box_rtree {
+ box_rtree() : rtree_{rtree_new()} {}
+
+ ~box_rtree() {
+ if (rtree_ != nullptr) {
+ rtree_free(rtree_);
+ }
+ }
+
+ box_rtree(box_rtree const& o) {
+ if (this != &o) {
+ if (rtree_ != nullptr) {
+ rtree_free(rtree_);
+ }
+ rtree_ = rtree_clone(o.rtree_);
+ }
+ }
+
+ box_rtree(box_rtree&& o) {
+ if (this != &o) {
+ rtree_ = o.rtree_;
+ o.rtree_ = nullptr;
+ }
+ }
+
+ box_rtree& operator=(box_rtree const& o) {
+ if (this != &o) {
+ if (rtree_ != nullptr) {
+ rtree_free(rtree_);
+ }
+ rtree_ = rtree_clone(o.rtree_);
+ }
+ return *this;
+ }
+
+ box_rtree& operator=(box_rtree&& o) {
+ if (this != &o) {
+ rtree_ = o.rtree_;
+ o.rtree_ = nullptr;
+ }
+ return *this;
+ }
+
+ void add(geo::box const& b, T const t) {
+ auto const min_corner = b.min_.lnglat();
+ auto const max_corner = b.max_.lnglat();
+ rtree_insert(
+ rtree_, min_corner.data(), max_corner.data(),
+ reinterpret_cast(static_cast(cista::to_idx(t))));
+ }
+
+ void remove(geo::box const& b, T const t) {
+ auto const min_corner = b.min_.lnglat();
+ auto const max_corner = b.max_.lnglat();
+ rtree_delete(
+ rtree_, min_corner.data(), max_corner.data(),
+ reinterpret_cast(static_cast(cista::to_idx(t))));
+ }
+
+ template
+ void find(geo::box const& b, Fn&& fn) const {
+ auto const min = b.min_.lnglat();
+ auto const max = b.max_.lnglat();
+ rtree_search(
+ rtree_, min.data(), max.data(),
+ [](double const* min_corner, double const* max_corner, void const* item,
+ void* udata) {
+ if constexpr (BoxRtreePosHandler) {
+ (*reinterpret_cast(udata))(
+ geo::box{geo::latlng{min_corner[1], min_corner[0]},
+ geo::latlng{max_corner[1], max_corner[0]}},
+ T{static_cast>(
+ reinterpret_cast(item))});
+ } else {
+ (*reinterpret_cast(udata))(T{static_cast>(
+ reinterpret_cast(item))});
+ }
+ return true;
+ },
+ &fn);
+ }
+
+ template
+ void find(geo::latlng const& pos, Fn&& fn) const {
+ return find(geo::box{pos, pos}, std::forward(fn));
+ }
+
+ rtree* rtree_{nullptr};
+};
+
+} // namespace motis
diff --git a/include/motis/compute_footpaths.h b/include/motis/compute_footpaths.h
index e84945335b..66d244ffa1 100644
--- a/include/motis/compute_footpaths.h
+++ b/include/motis/compute_footpaths.h
@@ -17,6 +17,8 @@ elevator_footpath_map_t compute_footpaths(osr::ways const&,
osr::lookup const&,
osr::platforms const&,
nigiri::timetable&,
- bool update_coordinates);
+ bool update_coordinates,
+ bool extend_missing,
+ std::chrono::seconds max_duration);
} // namespace motis
\ No newline at end of file
diff --git a/include/motis/config.h b/include/motis/config.h
index 3cd70600d4..a5a6ef11bb 100644
--- a/include/motis/config.h
+++ b/include/motis/config.h
@@ -87,6 +87,8 @@ struct config {
unsigned update_interval_{60};
unsigned http_timeout_{10};
bool incremental_rt_update_{false};
+ bool use_osm_stop_coordinates_{false};
+ bool extend_missing_footpaths_{false};
std::uint16_t max_footpath_length_{15};
std::optional default_timezone_{};
std::map datasets_{};
@@ -114,6 +116,8 @@ struct config {
std::map default_restrictions_{};
unsigned update_interval_{60};
unsigned http_timeout_{10};
+ unsigned cache_size_{50};
+ std::optional proxy_{};
};
std::optional gbfs_{};
diff --git a/include/motis/data.h b/include/motis/data.h
index 28d5da4879..4ef25d081c 100644
--- a/include/motis/data.h
+++ b/include/motis/data.h
@@ -50,7 +50,7 @@ struct data {
friend std::ostream& operator<<(std::ostream&, data const&);
void load_osr();
- void load_tt();
+ void load_tt(std::filesystem::path const&);
void load_shapes();
void load_railviz();
void load_geocoder();
diff --git a/include/motis/endpoints/routing.h b/include/motis/endpoints/routing.h
index ded693a623..7f5175e5c2 100644
--- a/include/motis/endpoints/routing.h
+++ b/include/motis/endpoints/routing.h
@@ -1,5 +1,9 @@
#pragma once
+#include
+#include
+#include
+
#include "osr/location.h"
#include "osr/types.h"
@@ -19,10 +23,13 @@ struct routing {
osr::location const&,
osr::direction,
std::vector const&,
+ std::optional> const&,
+ std::optional> const&,
+ std::optional> const& rental_providers,
bool wheelchair,
std::chrono::seconds max,
unsigned max_matching_distance,
- gbfs::gbfs_data const*) const;
+ gbfs::gbfs_routing_data&) const;
nigiri::hash_map>
@@ -35,10 +42,13 @@ struct routing {
std::pair, nigiri::duration_t> route_direct(
elevators const*,
- gbfs::gbfs_data const*,
+ gbfs::gbfs_routing_data&,
api::Place const& from,
api::Place const& to,
std::vector const&,
+ std::optional> const&,
+ std::optional> const&,
+ std::optional> const& rental_providers,
nigiri::unixtime_t start_time,
bool wheelchair,
std::chrono::seconds max) const;
diff --git a/include/motis/fwd.h b/include/motis/fwd.h
index 3c8130c10a..dc354185b1 100644
--- a/include/motis/fwd.h
+++ b/include/motis/fwd.h
@@ -46,6 +46,10 @@ struct elevators;
namespace gbfs {
struct gbfs_data;
struct gbfs_provider;
+struct gbfs_products_ref;
+struct gbfs_routing_data;
+struct provider_routing_data;
+struct products_routing_data;
} // namespace gbfs
} // namespace motis
diff --git a/include/motis/gbfs/compression.h b/include/motis/gbfs/compression.h
new file mode 100644
index 0000000000..08fd49eee3
--- /dev/null
+++ b/include/motis/gbfs/compression.h
@@ -0,0 +1,64 @@
+#pragma once
+
+#include
+#include
+#include
+
+#include "cista/containers/bitvec.h"
+
+#include "utl/verify.h"
+
+#include "lz4.h"
+
+#include "motis/gbfs/data.h"
+
+namespace motis::gbfs {
+
+template
+inline compressed_bitvec compress_bitvec(
+ cista::basic_bitvec const& bv) {
+ auto const* original_data = reinterpret_cast(bv.blocks_.data());
+ auto const original_bytes =
+ static_cast(bv.blocks_.size() *
+ sizeof(typename cista::basic_bitvec::block_t));
+ auto const max_compressed_size = LZ4_compressBound(original_bytes);
+
+ auto cbv = compressed_bitvec{
+ .data_ =
+ std::unique_ptr{
+ static_cast(
+ std::malloc(static_cast(max_compressed_size)))},
+ .original_bytes_ = original_bytes,
+ .bitvec_size_ = bv.size_};
+ utl::verify(cbv.data_ != nullptr,
+ "could not allocate memory for compressed bitvec");
+
+ cbv.compressed_bytes_ = LZ4_compress_default(
+ original_data, cbv.data_.get(), original_bytes, max_compressed_size);
+ utl::verify(cbv.compressed_bytes_ > 0, "could not compress bitvec");
+
+ if (auto* compressed = std::realloc(
+ cbv.data_.get(), static_cast(cbv.compressed_bytes_));
+ compressed != nullptr) {
+ cbv.data_.release();
+ cbv.data_.reset(static_cast(compressed));
+ }
+ return cbv;
+}
+
+template
+inline void decompress_bitvec(compressed_bitvec const& cbv,
+ cista::basic_bitvec& bv) {
+ bv.resize(static_cast::size_type>(
+ cbv.bitvec_size_));
+ auto const decompressed_bytes = LZ4_decompress_safe(
+ cbv.data_.get(), reinterpret_cast(bv.blocks_.data()),
+ cbv.compressed_bytes_,
+ static_cast(
+ bv.blocks_.size() *
+ sizeof(typename cista::basic_bitvec::block_t)));
+ utl::verify(decompressed_bytes == cbv.original_bytes_,
+ "could not decompress bitvec");
+}
+
+} // namespace motis::gbfs
diff --git a/include/motis/gbfs/data.h b/include/motis/gbfs/data.h
index 7d14921bdf..9c1f060e2c 100644
--- a/include/motis/gbfs/data.h
+++ b/include/motis/gbfs/data.h
@@ -1,7 +1,13 @@
#pragma once
+#include
+#include
#include
+#include
+#include
+#include