Route Service
Find the fastest path between coordinates with support for alternative routes and turn-by-turn instructions.
diff --git a/docs/v26.6.1/404.html b/docs/v26.6.1/404.html new file mode 100644 index 0000000..d2c4ead --- /dev/null +++ b/docs/v26.6.1/404.html @@ -0,0 +1,22 @@ + + +
+ + +This documentation describes the technical aspects of our cucumber test suite.
Run the Cucumber tests with:
$ npm testAn OSRM configuration consists of a routing algorithm and a data load method. OSRM currently supports the routing algorithms:
ch (Contraction Hierarchy), andmld (Multi-Level-Dijkstra)and the data load methods:
directly (load the files into memory),mmap (use memory mapped files), anddatastore (use shared memory).To test all scenarios with a single OSRM configuration, say:
$ npx cucumber-js -p home -p mld -p mmap --parallel 8 --fail-fastExplanations follow:
Profiles are chosen with the -p commandline argument. Cucumber profiles allow you to change multiple configuration items with just one commandline argument. If you set more than one profile they are all merged into one configuration.
Note: Cucumber profiles should not be confused with OSRM profiles. Cucumber profiles are defined in cucumber.mjs. OSRM profiles reside in the profiles/*.lua files.
Our implementation offers following stock profiles. You should always use one base profile followed by zero or more additional profiles.
| Name | |
|---|---|
| home | Base profile to use on a developer machine |
| github | Base profile to use on the github CI server |
| ch | Additional profile that selects the CH algorithm |
| mld | Additional profile that selects the MLD algorithm |
| mmap | Additional profile that selects the mmap data load method |
| directly | Additional profile that selects the directly data load method |
| datastore | Additional profile that selects the datastore data load method |
| stress | Additional profile that selects only @stress tests |
| todo | Additional profile that selects only @todo tests |
| all | Additional profile that selects all tests |
Here is a description of all arguments you can pass to Cucumber. The interesting ones probably are: --fail-fast, --format, --parallel, and --tags.
Note: when using --parallel N make sure there are N contiguous free ports at the configured port number (eg. at ports 5000--5000+N).
We provide a shortcut to run all 6 configurations:
$ npm testThis is how the tests are run on the CI server. You can pass the same arguments as mentioned above.
To speed up subsequent runs with the same parameters, the files generated by Cucumber and the by the OSRM extraction chain are held in a cache directory. This cache is located by default in test/cache and should be cleaned periodically:
$ rm -rf test/cacheThe whole configuration is done in cucumber.mjs. You can either edit worldParameters in cucumber.mjs or use environment variables to override single defaults.
| worldParameters | Environment Variable | Defaults to | |
|---|---|---|---|
CUCUMBER_TIMEOUT | 5000 | Scenario timeout in ms. | |
httpTimeout | CUCUMBER_HTTP_TIMEOUT | 2000 | HTTP timeout in ms. |
testPath | CUCUMBER_TEST_PATH | test | The test directory |
profilesPath | CUCUMBER_PROFILES_PATH | profiles | The profiles directory |
logsPath | CUCUMBER_LOGS_PATH | test/logs | The logs directory |
cachePath | CUCUMBER_CACHE_PATH | test/cache | The cache directory |
buildPath | OSRM_BUILD_DIR | build | Path to the binaries |
loadMethod | OSRM_LOAD_METHOD | datastore | Data load method |
algorithm | OSRM_ALGORITHM | ch | Routing algorithm |
ip | OSRM_IP | 127.0.0.1 | IP Address |
port | OSRM_PORT | 5000 | IP Port |
The default Cucumber timeout can be changed by setting the environment variable CUCUMBER_TIMEOUT. This is discouraged, because the default timeout of 5 seconds is plenty for the problem sizes we are dealing with. The probable reasons for a test timing out are that osrm-routed died or that sync between osrm-datastore and osrm-routed was lost.
OSRM_RASTER_SOURCE is set by 'Given the raster source' and is supposed to be read back in your profiles/*.lua profile by os.getenv('OSRM_RASTER_SOURCE').
OSRM_PROFILE See: Pull Request #4516
Single scenarios or whole feature files can be tagged. Tag names can be selected arbitrarily although it is best to conform to the tags already used. Eg. the tag @guidance can be used to run only those tests related to the guidance feature:
$ npm test -- --tags @guidanceWe also support following special tags:
| Tag | A scenario thus tagged ... |
|---|---|
@isolated | will not run while any other scenario is running in parallel |
@with_(datastore|directly|mmap) | will be executed iff the load method matches |
@no_(datastore|directly|mmap) | will be executed unless the load method matches |
@with_(ch|mld) | will be executed iff the algorithm matches |
@no_(ch|mld) | will be executed unless the algorithm matches |
@with_(linux|darwin|win32) | will be executed iff the OS matches |
@no_(linux|darwin|win32) | will be executed unless the OS matches |
A test that calls osrm-datastore --spring-clean should not run concurrently with any other test, thus the tag @isolated should be applied. A test that runs or kills osrm-routed should not run while testing the datastore load method, and thus should be labeled with the tag @no_datastore.
When changing guidance code, it is easy to introduce problems somewhere in the network. To get a better feeling of how your changes impact the OSRM experience, we offer ways of generating geojson output to inspect (e.g. with Mapbox Studio). When you do changes, make sure to inspect a few areas for the impact of the changes.
This is a short guide to describe usage of our GeoJson debug logging mechanism. It is synchronized to guarantee thread-safe logging.
To use it, the inclusion of geojson_debug_logger.hpp geojson_debug_policies.hpp from the util directory is required.
Geojson debugging requires a few simple steps to output data into a feature collection.
A guard (ScopedGeojsonLoggerGuard) requires a logging policy. Per default we provide a way of printing out node-ids as coordinates.
The initialisation to do so looks like this: util::ScopedGeojsonLoggerGuard<util::NodeIdVectorToLineString> geojson_guard( "debug.geojson", data-for-conversion); Make sure to give the guard a name, so it actually gets a lifetime.
The field data-for-conversion can be an arbitrary long set of features and needs to match the parameters used for constructing our policy (in this case util::NodeIdVectorToLineString).
The policy itself offers a operator() accepting a vector of NodeID.
For outputting data into our file (debug.geojson), we simply need to call the matching logging routine of the guard: util::ScopedGeojsonLoggerGuard<util::NodeIdVectorToLineString>::Write(list_of_node_ids); (or guard.Write(list_of_node_ids) if you created an instance).
Think of the scopeguard as you would do of any reference. If you want to access logging during a call, the guard object must be alive and valid.
As an example: a good location to create a scopeguard to log decisions in the edge-based-graph-factory would be right before we run it (here). If you put util::ScopedGeojsonLoggerGuard<util::NodeIdVectorToLineString> geojson_guard( "debug.geojson", node_coordinate_vector); at that location, you can then print util::ScopedGeojsonLoggerGuard<util::NodeIdVectorToLineString>::Write(list_of_node_ids); anywhere within the edge-based-graph-factory.
This location would enable calls for all guidance related pre-processing which is called in the edge-based-graph-factory. Logging any turn-handler decisions, for example, would now be possible.
GeoJson debugging requires a single GeoJsonGuard (ScopedGeojsonLoggerGuard) for each desired output file. For each set of template parameters, only the most recent guard will actually produce output.
util::ScopedGeojsonLoggerGuard<util::NodeIdVectorToLineString> geojson_guard( "debug.geojson", data-for-conversion);
util::ScopedGeojsonLoggerGuard<util::NodeIdVectorToLineString> geojson_guard( "debug-2.geojson", data-for-conversion);
Will not provide a way to write into two files, but only debug-2 will actually contain features.
We cannot nest these calls.
If we want to use the same policy for multiple files, we need to use different template parameters both for the logger and the guard.
util::ScopedGeojsonLoggerGuard<util::NodeIdVectorToLineString,0> geojson_guard( "debug.geojson", data-for-conversion);
util::ScopedGeojsonLoggerGuard<util::NodeIdVectorToLineString,1> geojson_guard( "debug-2.geojson", data-for-conversion);
as well as,
util::ScopedGeojsonLoggerGuard<util::NodeIdVectorToLineString,0>::Write(list_of_node_ids);
util::ScopedGeojsonLoggerGuard<util::NodeIdVectorToLineString,1>::Write(list_of_node_ids);
The built-in HTTP server is a basic HTTP/1.0 server that supports a 'keep-alive' extension. Persistent connections are limited to 512 requests per connection and allow no more than 5 seconds between requests.
All OSRM HTTP requests use a common structure.
The following syntax applies to all services, except as noted.
| Parameter | Description |
|---|---|
service | One of the following values: route, nearest, table, match, trip, tile |
version | Version of the protocol implemented by the service. v1 for all OSRM 5.x installations |
profile | Mode of transportation, is determined statically by the Lua profile that is used to prepare the data using osrm-extract. Typically car, bike or foot if using one of the supplied profiles. |
coordinates | String of format {longitude},{latitude};{longitude},{latitude}[;{longitude},{latitude} ...] or polyline({polyline}) or polyline6({polyline6}). |
format | json or flatbuffers. This parameter is optional and defaults to json. |
Passing any option=value is optional. polyline follows Google's polyline format with precision 5 by default and can be generated using this package.
To pass parameters to each location some options support an array-like encoding:
Request options
| Option | Values | Description |
|---|---|---|
| bearings | {bearing};{bearing}[;{bearing} ...] | Limits the search to segments with given bearing in degrees towards true north in a clockwise direction. |
| radiuses | {radius};{radius}[;{radius} ...] | Limits the search to given radius in meters. |
| generate_hints | true (default), false | Adds a Hint to the response which can be used in subsequent requests, see hints parameter. |
| hints | {hint};{hint}[;{hint} ...] | Hint from previous request to derive position in street network. |
| approaches | {approach};{approach}[;{approach} ...] | Restrict the direction on the road network at a waypoint, relative to the input coordinate. |
| exclude | {class}[,{class}] | Additive list of classes to avoid, the order does not matter. |
| snapping | default (default), any | Default snapping avoids is_startpoint (see profile) edges, any will snap to any edge in the graph |
| skip_waypoints | true, false (default) | Removes waypoints from the response. Waypoints are still calculated, but not serialized. Could be useful in case you are interested in some other part of the response and do not want to transfer waste data. |
Where the elements follow the following format:
| Element | Values |
|---|---|
| bearing | {value},{range} integer 0 .. 360,integer 0 .. 180 |
| radius | double >= 0 or unlimited (default) |
| hint | Base64 string |
| approach | curb, opposite or unrestricted (default) |
| class | A class name determined by the profile or none. |
{option}={element};{element}[;{element} ... ]The number of elements must match exactly the number of locations (except for generate_hints and exclude). If you don't want to pass a value but instead use the default you can pass an empty element.
Example: 2nd location uses the default value for option:
{option}={element};;{element}# Query on Berlin with three coordinates:
+curl 'http://router.project-osrm.org/route/v1/driving/13.388860,52.517037;13.397634,52.529407;13.428555,52.523219?overview=false'
+
+# Query on Berlin excluding the usage of motorways:
+curl 'http://router.project-osrm.org/route/v1/driving/13.388860,52.517037;13.397634,52.529407?exclude=motorway'
+
+# Using polyline:
+curl 'http://router.project-osrm.org/route/v1/driving/polyline(ofp_Ik_vpAilAyu@te@g\`E)?overview=false'Every response object has a code property containing one of the strings below or a service dependent code:
| Type | Description |
|---|---|
Ok | Request could be processed as expected. |
InvalidUrl | URL string is invalid. |
InvalidService | Service name is invalid. |
InvalidVersion | Version is not found. |
InvalidOptions | Options are invalid. |
InvalidQuery | The query string is syntactically malformed. |
InvalidValue | The successfully parsed query parameters are invalid. |
NoSegment | One of the supplied input coordinates could not snap to the street segment. |
TooBig | The request size violates one of the service-specific request size restrictions. |
DisabledDataset | The request tried to access a disabled dataset. |
message is an optional human-readable error message. All other status types are service-dependent.400. Otherwise, the HTTP status code will be 200 and code will be Ok.Every response object has a data_version property containing a timestamp from the original OpenStreetMap file. This field is optional. It can be omitted if the data_version parameter was not set on the osrm-extract stage or the OSM file has not osmosis_replication_timestamp section.
{
+"code": "Ok",
+"message": "Everything worked",
+"data_version": "2017-11-17T21:43:02Z"
+}Snaps a coordinate to the street network and returns the nearest n matches.
Where coordinates only supports a single {longitude},{latitude} entry.
In addition to the general options the following options are supported for this service:
| Option | Values | Description |
|---|---|---|
| number | integer >= 1 (default 1) | Number of nearest segments that should be returned. |
As waypoints is a single thing returned by that service, using it with the option skip_waypoints set to true is quite useless, but still possible. In that case, only the code field will be returned.
Response
code if the request was successful Ok otherwise see the service dependent and general status codes.waypoints array of Waypoint objects sorted by distance to the input coordinate. Each object has at least the following additional properties: nodes: Array of OpenStreetMap node ids. Each id is a 64-bit unsigned integer (encoded as a JSON number for the json format, and as ulong for the flatbuffers format).# Querying nearest three snapped locations of \`13.388860,52.517037\` with a bearing between \`20° - 340°\`.
+curl 'http://router.project-osrm.org/nearest/v1/driving/13.388860,52.517037?number=3&bearings=0,20'{
+ "waypoints" : [
+ {
+ "nodes": [
+ 2264199819,
+ 0
+ ],
+ "hint" : "KSoKADRYroqUBAEAEAAAABkAAAAGAAAAAAAAABhnCQCLtwAA_0vMAKlYIQM8TMwArVghAwEAAQH1a66g",
+ "distance" : 4.152629,
+ "name" : "Friedrichstraße",
+ "location" : [
+ 13.388799,
+ 52.517033
+ ]
+ },
+ {
+ "nodes": [
+ 2045820592,
+ 0
+ ],
+ "hint" : "KSoKADRYroqUBAEABgAAAAAAAAAAAAAAKQAAABhnCQCLtwAA7kvMAAxZIQM8TMwArVghAwAAAQH1a66g",
+ "distance" : 11.811961,
+ "name" : "Friedrichstraße",
+ "location" : [
+ 13.388782,
+ 52.517132
+ ]
+ },
+ {
+ "nodes": [
+ 0,
+ 21487242
+ ],
+ "hint" : "KioKgDbbDgCUBAEAAAAAABoAAAAAAAAAPAAAABlnCQCLtwAA50vMADJZIQM8TMwArVghAwAAAQH1a66g",
+ "distance" : 15.872438,
+ "name" : "Friedrichstraße",
+ "location" : [
+ 13.388775,
+ 52.51717
+ ]
+ }
+ ],
+ "code" : "Ok"
+}Finds the fastest route between coordinates in the supplied order.
In addition to the general options the following options are supported for this service:
| Option | Values | Description |
|---|---|---|
| alternatives | true, false (default), or Number | Search for alternative routes. Passing a number alternatives=n searches for up to n alternative routes.* |
| steps | true, false (default) | Returned route steps for each route leg |
| annotations | true, false (default), nodes, distance, duration, datasources, weight, speed | Returns additional metadata for each coordinate along the route geometry. |
| geometries | polyline (default), polyline6, geojson | Returned route geometry format (influences overview and per step) |
| overview | simplified (default), full, false, by_legs | Add overview geometry either full, simplified according to highest zoom level it could be displayed on, not at all, or split by leg. |
| continue_straight | default (default), true, false | Forces the route to keep going straight at waypoints constraining uturns there even if it would be faster. Default value depends on the profile. |
| waypoints | {index};{index};{index}... | Treats input coordinates indicated by given indices as waypoints in returned Match object. Default is to treat all input coordinates as waypoints. |
* Please note that even if alternative routes are requested, a result cannot be guaranteed.
Response
code if the request was successful Ok otherwise see the service dependent and general status codes.waypoints: Array of Waypoint objects representing all waypoints in order:routes: An array of Route objects, ordered by descending recommendation rank.In case of error the following codes are supported in addition to the general ones:
| Type | Description |
|---|---|
NoRoute | No route found. |
All other properties might be undefined.
# Query on Berlin with three coordinates and no overview geometry returned:
+curl 'http://router.project-osrm.org/route/v1/driving/13.388860,52.517037;13.397634,52.529407;13.428555,52.523219?overview=false'Computes the duration of the fastest route between all pairs of supplied coordinates. Returns durations or distances or both between the coordinate pairs. Note that the distances are not the shortest distance between two coordinates, but rather the distances of the fastest routes. Durations are in seconds and distances are in meters.
Options
In addition to the general options the following options are supported for this service:
| Option | Values | Description |
|---|---|---|
| sources | {index};{index}[;{index} ...] or all (default) | Use location with given index as source. |
| destinations | {index};{index}[;{index} ...] or all (default) | Use location with given index as destination. |
| annotations | duration (default), distance, or duration,distance | Return the requested table or tables in response. |
| fallback_speed | double > 0 | If no route found between a source/destination pair, calculate the as-the-crow-flies distance, then use this speed to estimate duration. |
| fallback_coordinate | input (default), or snapped | When using a fallback_speed, use the user-supplied coordinate (input), or the snapped location (snapped) for calculating distances. |
| scale_factor | double > 0 | Use in conjunction with annotations=durations. Scales the table duration values by this number. |
Unlike other array encoded options, the length of sources and destinations can be smaller or equal to number of input locations;
With skip_waypoints set to true, both sources and destinations arrays will be skipped.
Example:
sources=0;5;7&destinations=5;1;4;2;3;6| Element | Values |
|---|---|
| index | 0 <= integer < #locations |
# Returns a 3x3 duration matrix:
+curl 'http://router.project-osrm.org/table/v1/driving/13.388860,52.517037;13.397634,52.529407;13.428555,52.523219'
+
+# Returns a 1x3 duration matrix
+curl 'http://router.project-osrm.org/table/v1/driving/13.388860,52.517037;13.397634,52.529407;13.428555,52.523219?sources=0'
+
+# Returns a asymmetric 3x2 duration matrix with from the polyline encoded locations \`qikdcB}~dpXkkHz\`:
+curl 'http://router.project-osrm.org/table/v1/driving/polyline(egs_Iq_aqAppHzbHulFzeMe\`EuvKpnCglA)?sources=0;1;3&destinations=2;4'
+
+# Returns a 3x3 duration matrix:
+curl 'http://router.project-osrm.org/table/v1/driving/13.388860,52.517037;13.397634,52.529407;13.428555,52.523219?annotations=duration'
+
+# Returns a 3x3 distance matrix for CH:
+curl 'http://router.project-osrm.org/table/v1/driving/13.388860,52.517037;13.397634,52.529407;13.428555,52.523219?annotations=distance'
+
+# Returns a 3x3 duration matrix and a 3x3 distance matrix for CH:
+curl 'http://router.project-osrm.org/table/v1/driving/13.388860,52.517037;13.397634,52.529407;13.428555,52.523219?annotations=distance,duration'Response
code if the request was successful Ok otherwise see the service dependent and general status codes.durations array of arrays that stores the matrix in row-major order. durations[i][j] gives the travel time from the i-th source to the j-th destination. Values are given in seconds. Can be null if no route between i and j can be found.distances array of arrays that stores the matrix in row-major order. distances[i][j] gives the travel distance from the i-th source to the j-th destination. Values are given in meters. Can be null if no route between i and j can be found.sources array of Waypoint objects describing all sources in orderdestinations array of Waypoint objects describing all destinations in orderfallback_speed_cells (optional) array of arrays containing i,j pairs indicating which cells contain estimated values based on fallback_speed. Will be absent if fallback_speed is not used.In case of error the following codes are supported in addition to the general ones:
| Type | Description |
|---|---|
NoTable | No route found. |
NotImplemented | This request is not supported |
All other properties might be undefined.
{
+ "sources": [
+ {
+ "location": [
+ 13.3888,
+ 52.517033
+ ],
+ "hint": "PAMAgEVJAoAUAAAAIAAAAAcAAAAAAAAArss0Qa7LNEHiVIRA4lSEQAoAAAAQAAAABAAAAAAAAADMAAAAAEzMAKlYIQM8TMwArVghAwEA3wps52D3",
+ "name": "Friedrichstraße"
+ },
+ {
+ "location": [
+ 13.397631,
+ 52.529432
+ ],
+ "hint": "WIQBgL6mAoAEAAAABgAAAAAAAAA7AAAAhU6PQHvHj0IAAAAAQbyYQgQAAAAGAAAAAAAAADsAAADMAAAAf27MABiJIQOCbswA_4ghAwAAXwVs52D3",
+ "name": "Torstraße"
+ },
+ {
+ "location": [
+ 13.428554,
+ 52.523239
+ ],
+ "hint": "7UcAgP___38fAAAAUQAAACYAAABTAAAAhSQKQrXq5kKRbiZCWJo_Qx8AAABRAAAAJgAAAFMAAADMAAAASufMAOdwIQNL58wA03AhAwMAvxBs52D3",
+ "name": "Platz der Vereinten Nationen"
+ }
+ ],
+ "durations": [
+ [
+ 0,
+ 192.6,
+ 382.8
+ ],
+ [
+ 199,
+ 0,
+ 283.9
+ ],
+ [
+ 344.7,
+ 222.3,
+ 0
+ ]
+ ],
+ "destinations": [
+ {
+ "location": [
+ 13.3888,
+ 52.517033
+ ],
+ "hint": "PAMAgEVJAoAUAAAAIAAAAAcAAAAAAAAArss0Qa7LNEHiVIRA4lSEQAoAAAAQAAAABAAAAAAAAADMAAAAAEzMAKlYIQM8TMwArVghAwEA3wps52D3",
+ "name": "Friedrichstraße"
+ },
+ {
+ "location": [
+ 13.397631,
+ 52.529432
+ ],
+ "hint": "WIQBgL6mAoAEAAAABgAAAAAAAAA7AAAAhU6PQHvHj0IAAAAAQbyYQgQAAAAGAAAAAAAAADsAAADMAAAAf27MABiJIQOCbswA_4ghAwAAXwVs52D3",
+ "name": "Torstraße"
+ },
+ {
+ "location": [
+ 13.428554,
+ 52.523239
+ ],
+ "hint": "7UcAgP___38fAAAAUQAAACYAAABTAAAAhSQKQrXq5kKRbiZCWJo_Qx8AAABRAAAAJgAAAFMAAADMAAAASufMAOdwIQNL58wA03AhAwMAvxBs52D3",
+ "name": "Platz der Vereinten Nationen"
+ }
+ ],
+ "code": "Ok",
+ "distances": [
+ [
+ 0,
+ 1886.89,
+ 3791.3
+ ],
+ [
+ 1824,
+ 0,
+ 2838.09
+ ],
+ [
+ 3275.36,
+ 2361.73,
+ 0
+ ]
+ ],
+ "fallback_speed_cells": [
+ [ 0, 1 ],
+ [ 1, 0 ]
+ ]
+}Map matching matches/snaps given GPS points to the road network in the most plausible way. Please note the request might result in multiple sub-traces. Large jumps in the timestamps (> 60s) or improbable transitions lead to trace splits if a complete matching could not be found. The algorithm might not be able to match all points. Outliers are removed if they can not be matched successfully.
In addition to the general options the following options are supported for this service:
| Option | Values | Description |
|---|---|---|
| steps | true, false (default) | Returned route steps for each route |
| geometries | polyline (default), polyline6, geojson | Returned route geometry format (influences overview and per step) |
| annotations | true, false (default), nodes, distance, duration, datasources, weight, speed | Returns additional metadata for each coordinate along the route geometry. |
| overview | simplified (default), full, false, by_legs | Add overview geometry either full, simplified according to highest zoom level it could be displayed on, not at all, or split by leg. |
| timestamps | {timestamp};{timestamp}[;{timestamp} ...] | Timestamps for the input locations in seconds since UNIX epoch. Timestamps need to be monotonically increasing. |
| radiuses | {radius};{radius}[;{radius} ...] | Standard deviation of GPS precision used for map matching. If applicable use GPS accuracy. |
| gaps | split (default), ignore | Allows the input track splitting based on huge timestamp gaps between points. |
| tidy | true, false (default) | Allows the input track modification to obtain better matching quality for noisy tracks. |
| waypoints | {index};{index};{index}... | Treats input coordinates indicated by given indices as waypoints in returned Match object. Default is to treat all input coordinates as waypoints. |
| Parameter | Values |
|---|---|
| timestamp | integer seconds since UNIX epoch |
| radius | double >= 0 (default 5m) |
The radius for each point should be the standard error of the location measured in meters from the true location. Use Location.getAccuracy() on Android or CLLocation.horizontalAccuracy on iOS. This value is used to determine which points should be considered as candidates (larger radius means more candidates) and how likely each candidate is (larger radius means far-away candidates are penalized less). The area to search is chosen such that the correct candidate should be considered 99.9% of the time (for more details see this ticket).
Response
code if the request was successful Ok otherwise see the service dependent and general status codes.tracepoints: Array of Waypoint objects representing all points of the trace in order. If the tracepoint was omitted by map matching because it is an outlier, the entry will be null. Each Waypoint object has the following additional properties: matchings_index: Index to the Route object in matchings the sub-trace was matched to.waypoint_index: Index of the waypoint inside the matched route.alternatives_count: Number of probable alternative matchings for this tracepoint. A value of zero indicates that this point was matched unambiguously. Split the trace at these points for incremental map matching.matchings: An array of Route objects that assemble the trace. Each Route object has the following additional properties: confidence: Confidence of the matching. float value between 0 and 1. 1 is very confident that the matching is correct.In case of error the following codes are supported in addition to the general ones:
| Type | Description |
|---|---|
NoMatch | No matchings found. |
All other properties might be undefined.
The trip plugin solves the Traveling Salesman Problem using a greedy heuristic (farthest-insertion algorithm) for 10 or more waypoints and uses brute force for less than 10 waypoints. The returned path does not have to be the fastest one. As TSP is NP-hard it only returns an approximation. Note that all input coordinates have to be connected for the trip service to work.
In addition to the general options the following options are supported for this service:
| Option | Values | Description |
|---|---|---|
| roundtrip | true (default), false | Returned route is a roundtrip (route returns to first location) |
| source | any (default), first | Returned route starts at any or first coordinate |
| destination | any (default), last | Returned route ends at any or last coordinate |
| steps | true, false (default) | Returned route instructions for each trip |
| annotations | true, false (default), nodes, distance, duration, datasources, weight, speed | Returns additional metadata for each coordinate along the route geometry. |
| geometries | polyline (default), polyline6, geojson | Returned route geometry format (influences overview and per step) |
| overview | simplified (default), full, false, by_legs | Add overview geometry either full, simplified according to highest zoom level it could be displayed on, not at all, or split by leg. |
Fixing Start and End Points
It is possible to explicitly set the start or end coordinate of the trip. When the source is set to first, the first coordinate is used as the start coordinate of the trip in the output. When the destination is set to last, the last coordinate will be used as the destination of the trip in the returned output. If you specify any, any of the coordinates can be used as the first or last coordinate in the output.
However, if source=any&destination=any the returned round-trip will still start at the first input coordinate by default.
Currently, not all combinations of roundtrip, source, and destination are supported. Right now, the following combinations are possible:
| roundtrip | source | destination | supported |
|---|---|---|---|
| true | first | last | yes |
| true | first | any | yes |
| true | any | last | yes |
| true | any | any | yes |
| false | first | last | yes |
| false | first | any | yes |
| false | any | last | yes |
| false | any | any | no |
# Round trip in Berlin with three stops:
+curl 'http://router.project-osrm.org/trip/v1/driving/13.388860,52.517037;13.397634,52.529407;13.428555,52.523219'# Round trip in Berlin with four stops, starting at the first stop, ending at the last:
+curl 'http://router.project-osrm.org/trip/v1/driving/13.388860,52.517037;13.397634,52.529407;13.428555,52.523219;13.418555,52.523215?source=first&destination=last'code: if the request was successful Ok otherwise see the service dependent and general status codes.waypoints: Array of Waypoint objects representing all waypoints in input order. Each Waypoint object has the following additional properties: trips_index: Index to trips of the sub-trip the point was matched to.waypoint_index: Index of the point in the trip.trips: An array of Route objects that assemble the trace.In case of error the following codes are supported in addition to the general ones:
| Type | Description |
|---|---|
NoTrips | No trips found because input coordinates are not connected. |
NotImplemented | This request is not supported |
All other properties might be undefined.
This service generates Mapbox Vector Tiles that can be viewed with a vector-tile capable slippy-map viewer. The tiles contain road geometries and metadata that can be used to examine the routing graph. The tiles are generated directly from the data in-memory, so are in sync with actual routing results, and let you examine which roads are actually routable, and what weights they have applied.
The x, y, and zoom values are the same as described at https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames, and are supported by vector tile viewers like Mapbox GL JS.
# This fetches a Z=13 tile for downtown San Francisco:
+curl 'http://router.project-osrm.org/tile/v1/car/tile(1310,3166,13).mvt'The response object is either a binary encoded blob with a Content-Type of application/x-protobuf, or a 404 error. Note that OSRM is hard-coded to only return tiles from zoom level 12 and higher (to avoid accidentally returning extremely large vector tiles).
Vector tiles contain two layers:
speeds layer:
| Property | Type | Description |
|---|---|---|
speed | integer | the speed on that road segment, in km/h |
is_small | boolean | whether this segment belongs to a small (< 1000 node) strongly connected component |
datasource | string | the source for the speed value (normally lua profile unless you're using the traffic update feature, in which case it contains the stem of the filename that supplied the speed value for this segment |
duration | float | how long this segment takes to traverse, in seconds. This value is to calculate the total route ETA. |
weight | integer | how long this segment takes to traverse, in units (may differ from duration when artificial biasing is applied in the Lua profiles). ACTUAL ROUTING USES THIS VALUE. |
name | string | the name of the road this segment belongs to |
rate | float | the value of length/weight - analogous to speed, but using the weight value rather than duration, rounded to the nearest integer |
is_startpoint | boolean | whether this segment can be used as a start/endpoint for routes |
turns layer:
| Property | Type | Description |
|---|---|---|
bearing_in | integer | the absolute bearing that approaches the intersection. -180 to +180, 0 = North, 90 = East |
turn_angle | integer | the angle of the turn, relative to the bearing_in. -180 to +180, 0 = straight ahead, 90 = 90-degrees to the right |
cost | float | the time we think it takes to make that turn, in seconds. May be negative, depending on how the data model is constructed (some turns get a "bonus"). |
weight | float | the weight we think it takes to make that turn. May be negative, depending on how the data model is constructed (some turns get a "bonus"). ACTUAL ROUTING USES THIS VALUE |
type | string | the type of this turn - values like turn, continue, etc. See the StepManeuver for a partial list, this field also exposes internal turn types that are never returned with an API response |
modifier | string | the direction modifier of the turn (left, sharp left, etc) |
Represents a route through (potentially multiple) waypoints.
Properties
distance: The distance traveled by the route, in float meters.duration: The estimated travel time, in float number of seconds.geometry: The whole geometry of the route value depending on overview parameter, format depending on the geometries parameter. See RouteStep's geometry property for the parameter documentation.weight: The calculated weight of the route.weight_name: The name of the weight profile used during the extraction phase.| overview | Description |
|---|---|
| simplified | Geometry is simplified according to the highest zoom level it can still be displayed in full. |
| full | Geometry is not simplified. |
| false | Geometry is not added. |
legs: The legs between the given waypoints, an array of RouteLeg objects.Three input coordinates, geometry=geojson, steps=false:
{
+ "distance": 90.0,
+ "duration": 300.0,
+ "weight": 300.0,
+ "weight_name": "duration",
+ "geometry": {"type": "LineString", "coordinates": [[120.0, 10.0], [120.1, 10.0], [120.2, 10.0], [120.3, 10.0]]},
+ "legs": [
+ {
+ "distance": 30.0,
+ "duration": 100.0,
+ "steps": []
+ },
+ {
+ "distance": 60.0,
+ "duration": 200.0,
+ "steps": []
+ }
+ ]
+}Represents a route between two waypoints.
Properties
distance: The distance traveled by this route leg, in float meters.duration: The estimated travel time, in float number of seconds.weight: The calculated weight of the route leg.summary: Summary of the route taken as string. Depends on the steps parameter:| steps | |
|---|---|
| true | Names of the two major roads used. Can be empty if the route is too short. |
| false | empty string |
steps: Depends on the steps parameter.| steps | |
|---|---|
| true | array of RouteStep objects describing the turn-by-turn instructions |
| false | empty array |
annotation: Additional details about each coordinate along with the route geometry:| annotations | |
|---|---|
| true | An Annotation object containing node ids, durations, distances, and weights. |
| false | undefined |
With steps=false and annotations=true:
{
+ "distance": 30.0,
+ "duration": 100.0,
+ "weight": 100.0,
+ "steps": [],
+ "annotation": {
+ "distance": [5,5,10,5,5],
+ "duration": [15,15,40,15,15],
+ "datasources": [1,0,0,0,1],
+ "metadata": { "datasource_names": ["traffic","lua profile","lua profile","lua profile","traffic"] },
+ "nodes": [49772551,49772552,49786799,49786800,49786801,49786802],
+ "speed": [0.3, 0.3, 0.3, 0.3, 0.3]
+ }
+}Annotation of the whole route leg with fine-grained information about each segment or node id.
Properties
distance: The distance, in meters, between each pair of coordinatesduration: The duration between each pair of coordinates, in seconds. Does not include the duration of any turns.datasources: The index of the data source for the speed between each pair of coordinates. 0 is the default profile, other values are supplied via --segment-speed-file to osrm-contract or osrm-customize. String-like names are in the metadata.datasource_names array.nodes: Array of OpenStreetMap node ids for each coordinate along the route (excluding the first/last user-supplied coordinates). Each id is a 64-bit unsigned integer (encoded as a JSON number for the json format, and as ulong for the flatbuffers format). Clients consuming flatbuffers should treat these values as 64-bit integers (JS bindings expose them as BigInt).weight: The weights between each pair of coordinates. Does not include any turn costs.speed: Convenience field, calculation of distance / duration rounded to one decimal placemetadata: Metadata related to other annotations datasource_names: The names of the data sources used for the speed between each pair of coordinates. lua profile is the default profile, other values are the filenames supplied via --segment-speed-file to osrm-contract or osrm-customize{
+ "distance": [5,5,10,5,5],
+ "duration": [15,15,40,15,15],
+ "datasources": [1,0,0,0,1],
+ "metadata": { "datasource_names": ["traffic","lua profile","lua profile","lua profile","traffic"] },
+ "nodes": [49772551,49772552,49786799,49786800,49786801,49786802],
+ "weight": [15,15,40,15,15]
+}A step consists of a maneuver such as a turn or merge, followed by a distance of travel along a single way to the subsequent step.
Properties
distance: The distance of travel from the maneuver to the subsequent step, in float meters.duration: The estimated travel time, in float number of seconds.geometry: The unsimplified geometry of the route segment, depending on the geometries parameter.weight: The calculated weight of the step.geometry | |
|---|---|
| polyline | polyline with precision 5 in [latitude,longitude] encoding |
| polyline6 | polyline with precision 6 in [latitude,longitude] encoding |
| geojson | GeoJSON LineString |
name: The name of the way along which travel proceeds.ref: A reference number or code for the way. Optionally included, if ref data is available for the given way.pronunciation: A string containing an IPA phonetic transcription indicating how to pronounce the name in the name property. This property is omitted if pronunciation data is unavailable for the step.destinations: The destinations of the way. Will be undefined if there are no destinations.exits: The exit numbers or names of the way. Will be undefined if there are no exit numbers or names.mode: A string signifying the mode of transportation.maneuver: A StepManeuver object representing the maneuver.intersections: A list of Intersection objects that are passed along the segment, the very first belonging to the StepManeuverrotary_name: The name for the rotary. Optionally included, if the step is a rotary and a rotary name is available.rotary_pronunciation: The pronunciation hint of the rotary name. Optionally included, if the step is a rotary and a rotary pronunciation is available.driving_side: The legal driving side at the location for this step. Either left or right.{
+ "geometry" : "{lu_IypwpAVrAvAdI",
+ "mode" : "driving",
+ "duration" : 15.6,
+ "weight" : 15.6,
+ "intersections" : [
+ { "bearings" : [ 10, 92, 184, 270 ],
+ "lanes" : [
+ { "indications" : [ "left", "straight" ],
+ "valid" : false },
+ { "valid" : true,
+ "indications" : [ "right" ] }
+ ],
+ "out" : 2,
+ "in" : 3,
+ "entry" : [ "true", "true", "true", "false" ],
+ "location" : [ 13.39677, 52.54366 ]
+ },
+ { "out" : 1,
+ "lanes" : [
+ { "indications" : [ "straight" ],
+ "valid" : true },
+ { "indications" : [ "right" ],
+ "valid" : false }
+ ],
+ "bearings" : [ 60, 240, 330 ],
+ "in" : 0,
+ "entry" : [ "false", "true", "true" ],
+ "location" : [ 13.394718, 52.543096 ]
+ }
+ ],
+ "name" : "Lortzingstraße",
+ "distance" : 152.3,
+ "maneuver" : {
+ "modifier" : "right",
+ "type" : "turn"
+ }
+}Properties
location: A [longitude, latitude] pair describing the location of the turn.bearing_before: The clockwise angle from true north to the direction of travel immediately before the maneuver. Range 0-359.bearing_after: The clockwise angle from true north to the direction of travel immediately after the maneuver. Range 0-359.type A string indicating the type of maneuver. new identifiers might be introduced without API change Types unknown to the client should be handled like the turn type, the existence of correct modifier values is guaranteed.type | Description |
|---|---|
turn | a basic turn into the direction of the modifier |
new name | no turn is taken/possible, but the road name changes. The road can take a turn itself, following modifier. |
depart | indicates the departure of the leg |
arrive | indicates the destination of the leg |
merge | merge onto a street (e.g. getting on the highway from a ramp, the modifier specifies the direction of the merge) |
ramp | Deprecated. Replaced by on_ramp and off_ramp. |
on ramp | take a ramp to enter a highway (direction given my modifier) |
off ramp | take a ramp to exit a highway (direction given my modifier) |
fork | take the left/right side at a fork depending on modifier |
end of road | road ends in a T intersection turn in direction of modifier |
use lane | Deprecated replaced by lanes on all intersection entries |
continue | Turn in direction of modifier to stay on the same road |
roundabout | traverse roundabout, if the route leaves the roundabout there will be an additional property exit for exit counting. The modifier specifies the direction of entering the roundabout. |
rotary | a traffic circle. While very similar to a larger version of a roundabout, it does not necessarily follow roundabout rules for right of way. It can offer rotary_name and/or rotary_pronunciation parameters (located in the RouteStep object) in addition to the exit parameter (located on the StepManeuver object). |
roundabout turn | Describes a turn at a small roundabout that should be treated as a normal turn. The modifier indicates the turn direction. Example instruction: At the roundabout turn left. |
notification | not an actual turn but a change in the driving conditions. For example the travel mode or classes. If the road takes a turn itself, the modifier describes the direction |
exit roundabout | Describes a maneuver exiting a roundabout (usually preceded by a roundabout instruction) |
exit rotary | Describes the maneuver exiting a rotary (large named roundabout) |
Please note that even though there are new name and notification instructions, the mode and name can change between all instructions. They only offer a fallback in case nothing else is to report.
modifier An optional string indicating the direction change of the maneuver.modifier | Description |
|---|---|
uturn | indicates the reversal of direction |
sharp right | a sharp right turn |
right | a normal turn to the right |
slight right | a slight turn to the right |
straight | no relevant change in direction |
slight left | a slight turn to the left |
left | a normal turn to the left |
sharp left | a sharp turn to the left |
The list of turns without a modifier is limited to: depart/arrive. If the source/target location is close enough to the depart/arrive location, no modifier will be given.
The meaning depends on the type property.
type | Description |
|---|---|
turn | modifier indicates the change in direction accomplished through the turn |
depart/arrive | modifier indicates the position of departure point and arrival point in relation to the current direction of travel |
exit An optional integer indicating the number of the exit to take. The property exists for the roundabout / rotary property: Number of the roundabout exit to take. If an exit is undefined the destination is on the roundabout.New properties (potentially depending on type) may be introduced in the future without an API version change.
A Lane represents a turn lane at the corresponding turn location.
Properties
indications: an indication (e.g. marking on the road) specifying the turn lane. A road can have multiple indications (e.g. an arrow pointing straight and left). The indications are given in an array, each containing one of the following types. Further indications might be added on without an API version change.value | Description |
|---|---|
none | No dedicated indication is shown. |
uturn | An indication signaling the possibility to reverse (i.e. fully bend arrow). |
sharp right | An indication indicating a sharp right turn (i.e. strongly bend arrow). |
right | An indication indicating a right turn (i.e. bend arrow). |
slight right | An indication indicating a slight right turn (i.e. slightly bend arrow). |
straight | No dedicated indication is shown (i.e. straight arrow). |
slight left | An indication indicating a slight left turn (i.e. slightly bend arrow). |
left | An indication indicating a left turn (i.e. bend arrow). |
sharp left | An indication indicating a sharp left turn (i.e. strongly bend arrow). |
valid: a boolean flag indicating whether the lane is a valid choice in the current maneuver{
+ "indications": ["left", "straight"],
+ "valid": false
+}An intersection gives a full representation of any cross-way the path passes by. For every step, the very first intersection (intersections[0]) corresponds to the location of the StepManeuver. Further intersections are listed for every cross-way until the next turn instruction.
Properties
location: A [longitude, latitude] pair describing the location of the turn.bearings: A list of bearing values (e.g. [0,90,180,270]) that are available at the intersection. The bearings describe all available roads at the intersection. Values are between 0-359 (0=true north)classes: An array of strings signifying the classes (as specified in the profile) of the road exiting the intersection.entry: A list of entry flags, corresponding in a 1:1 relationship to the bearings. A value of true indicates that the respective road could be entered on a valid route. false indicates that the turn onto the respective road would violate a restriction.in: index into bearings/entry array. Used to calculate the bearing just before the turn. Namely, the clockwise angle from true north to the direction of travel immediately before the maneuver/passing the intersection. Bearings are given relative to the intersection. To get the bearing in the direction of driving, the bearing has to be rotated by a value of 180. The value is not supplied for depart maneuvers.out: index into the bearings/entry array. Used to extract the bearing just after the turn. Namely, The clockwise angle from true north to the direction of travel immediately after the maneuver/passing the intersection. The value is not supplied for arrive maneuvers.lanes: Array of Lane objects that denote the available turn lanes at the intersection. If no lane information is available for an intersection, the lanes property will not be present.{
+ "location":[13.394718,52.543096],
+ "in":0,
+ "out":2,
+ "bearings":[60,150,240,330],
+ "entry":["false","true","true","true"],
+ "classes": ["toll", "restricted"],
+ "lanes":{
+ "indications": ["left", "straight"],
+ "valid": false
+ }
+}The object is used to describe the waypoint on a route.
Properties
name Name of the street the coordinate snapped tolocation Array that contains the [longitude, latitude] pair of the snapped coordinatedistance The distance, in meters, from the input coordinate to the snapped coordinatehint Unique internal identifier of the segment (ephemeral, not constant over data updates) This can be used on subsequent requests to significantly speed up the query and to connect multiple services. E.g. you can use the hint value obtained by the nearest query as hint values for route inputs.{
+ "hint" : "KSoKADRYroqUBAEAEAAAABkAAAAGAAAAAAAAABhnCQCLtwAA_0vMAKlYIQM8TMwArVghAwEAAQH1a66g",
+ "distance" : 4.152629,
+ "name" : "Friedrichstraße",
+ "location" : [
+ 13.388799,
+ 52.517033
+ ]
+}The default response format is json, but OSRM supports binary flatbuffers format, which is much faster in serialization/deserialization, comparing to json.
The format itself is described in message descriptors, located at include/engine/api/flatbuffers directory. Those descriptors could be compiled to provide protocol parsers in Go/Javascript/Typescript/Java/Dart/C#/Python/Lobster/Lua/Rust/PHP/Kotlin. Precompiled protocol parser for C++ is supplied with OSRM.
Flatbuffers format provides exactly the same data, as json format with a slightly different layout, which was optimized to minimize in-transfer size.
Root object is the only object, available from a 'raw' flatbuffers buffer. It can be constructed with a following call:
auto osrm = osrm::engine::api::fbresult::GetFBResult(some_input_buffer);
+Properties
error: bool Marks response as erroneous. An erroneous response should include the code fieldset, all the other fields may not be present.code: Error Error description object, only present, when error is truewaypoints: [Waypoint] Array of Waypoint objects. Should present for every service call, unless skip_waypoints is set to true. Table service will put sources array here.routes: [RouteObject] Array of RouteObject objects. May be empty or absent. Should present for Route/Trip/Match services call.table: Table Table object, may absent. Should be present in case of Table service call.Contains error information.
Properties
code: string Error codemessage: string Detailed error messageAlmost the same as json Waypoint object. The following properties differ:
location: Position Same as json location field, but different format.nodes: Uint64Pair Same as json nodes field, but different format.Almost the same as json Route object. The following properties differ:
polyline: string Same as json geometry.polyline or geometry.polyline6 fields. One field for both formats.coordinates: [Position] Same as json geometry.coordinates field, but different format.legs: [Leg] Array of Leg objects.Almost the same as json Leg object. The following properties differ:
annotations: Annotation Same as json annotation field, but different format.steps: [Step] Same as step annotation field, but different format.Almost the same as json Step object. The following properties differ:
polyline: string Same as json geometry.polyline or geometry.polyline6 fields. One field for both formats.coordinates: [Position] Same as json geometry.coordinates field, but different format.maneuver: StepManeuver Same as json maneuver field, but different format.type | Description |
|---|---|
Turn | a basic turn into the direction of the modifier |
NewName | no turn is taken/possible, but the road name changes. The road can take a turn itself, following modifier. |
Depart | indicates the departure of the leg |
Arrive | indicates the destination of the leg |
Merge | merge onto a street (e.g. getting on the highway from a ramp, the modifier specifies the direction of the merge) |
OnRamp | take a ramp to enter a highway (direction given my modifier) |
OffRamp | take a ramp to exit a highway (direction given my modifier) |
Fork | take the left/right side at a fork depending on modifier |
EndOfRoad | road ends in a T intersection turn in direction of modifier |
Continue | Turn in direction of modifier to stay on the same road |
Roundabout | traverse roundabout, if the route leaves the roundabout there will be an additional property exit for exit counting. The modifier specifies the direction of entering the roundabout. |
Rotary | a traffic circle. While very similar to a larger version of a roundabout, it does not necessarily follow roundabout rules for right of way. It can offer rotary_name and/or rotary_pronunciation parameters (located in the RouteStep object) in addition to the exit parameter (located on the StepManeuver object). |
RoundaboutTurn | Describes a turn at a small roundabout that should be treated as a normal turn. The modifier indicates the turn direction. Example instruction: At the roundabout turn left. |
Notification | not an actual turn but a change in the driving conditions. For example the travel mode or classes. If the road takes a turn itself, the modifier describes the direction |
ExitRoundabout | Describes a maneuver exiting a roundabout (usually preceded by a roundabout instruction) |
ExitRotary | Describes the maneuver exiting a rotary (large named roundabout) |
driving_side: bool True stands for left side driving.intersections: [Intersection] Same as json intersections field, but different format.Almost the same as json Intersection object. The following properties differ:
location: Position Same as json location property, but in a different format.lanes: [Lane] Array of Lane objects.Almost the same as json Lane object. The following properties differ:
indications: Turn Array of Turn enum values.value | Description |
|---|---|
None | No dedicated indication is shown. |
UTurn | An indication signaling the possibility to reverse (i.e. fully bend arrow). |
SharpRight | An indication indicating a sharp right turn (i.e. strongly bend arrow). |
Right | An indication indicating a right turn (i.e. bend arrow). |
SlightRight | An indication indicating a slight right turn (i.e. slightly bend arrow). |
Straight | No dedicated indication is shown (i.e. straight arrow). |
SlightLeft | An indication indicating a slight left turn (i.e. slightly bend arrow). |
Left | An indication indicating a left turn (i.e. bend arrow). |
SharpLeft | An indication indicating a sharp left turn (i.e. strongly bend arrow). |
Almost the same as json StepManeuver object. The following properties differ:
location: Position Same as json location property, but in a different format.type: ManeuverType Type of a maneuver (enum)type | Description |
|---|---|
Turn | a basic turn into the direction of the modifier |
NewName | no turn is taken/possible, but the road name changes. The road can take a turn itself, following modifier. |
Depart | indicates the departure of the leg |
Arrive | indicates the destination of the leg |
Merge | merge onto a street (e.g. getting on the highway from a ramp, the modifier specifies the direction of the merge) |
OnRamp | take a ramp to enter a highway (direction given my modifier) |
OffRamp | take a ramp to exit a highway (direction given my modifier) |
Fork | take the left/right side at a fork depending on modifier |
EndOfRoad | road ends in a T intersection turn in direction of modifier |
Continue | Turn in direction of modifier to stay on the same road |
Roundabout | traverse roundabout, if the route leaves the roundabout there will be an additional property exit for exit counting. The modifier specifies the direction of entering the roundabout. |
Rotary | a traffic circle. While very similar to a larger version of a roundabout, it does not necessarily follow roundabout rules for right of way. It can offer rotary_name and/or rotary_pronunciation parameters (located in the RouteStep object) in addition to the exit parameter (located on the StepManeuver object). |
RoundaboutTurn | Describes a turn at a small roundabout that should be treated as a normal turn. The modifier indicates the turn direction. Example instruction: At the roundabout turn left. |
Notification | not an actual turn but a change in the driving conditions. For example the travel mode or classes. If the road takes a turn itself, the modifier describes the direction |
ExitRoundabout | Describes a maneuver exiting a roundabout (usually preceded by a roundabout instruction) |
ExitRotary | Describes the maneuver exiting a rotary (large named roundabout) |
modifier: Turn Maneuver turn (enum)Almost the same as the json annotation object. Note: on the flatbuffers wire the Annotation.nodes field is a [ulong] (64-bit unsigned integers), and the generated JavaScript flatbuffers bindings will expose these values as BigInt / BigUint64Array. Clients that consume flatbuffers should handle 64-bit node ids accordingly.
A point on Earth.
Properties
longitude: float Point's longitudelatitude: float Point's latitudeA pair of long long integers. Used only by Waypoint object.
Properties
first: uint64 First pair value.second: uint64 Second pair value.Almost the same as json Table object. The main difference is that 'sources' field is absent and the root's object 'waypoints' field is used instead. All the other differences follow:
durations: [float] Flat representation of a durations matrix. Element at row;col can be addressed as [row * cols + col]distances: [float] Flat representation of a destinations matrix. Element at row;col can be addressed as [row * cols + col]destinations: [Waypoint] Array of Waypoint objects. Will be null if skip_waypoints will be set to truerows: ushort Number of rows in durations/destinations matrices.cols: ushort Number of cols in durations/destinations matrices.OSRM provides powerful routing services through both HTTP and Node.js APIs:
OSRM can be used as a library (libosrm) via C++ instead of using it through the HTTP interface and osrm-routed. This allows for fine-tuning OSRM and has much less overhead. Here is a quick introduction into how to use libosrm in the current version.
Take a look at the example code that lives in the example directory. Here is all you ever wanted to know about libosrm, that is a short description of what the types do and where to find documentation on it:
EngineConfig - for initializing an OSRM instance we can configure certain properties and constraints. E.g. the storage config is the base path such as france.osm.osrm from which we derive and load france.osm.osrm.* auxiliary files. This also lets you set constraints such as the maximum number of locations allowed for specific services.
OSRM - this is the main Routing Machine type with functions such as Route and Table. You initialize it with a EngineConfig. It does all the heavy lifting for you. Each function takes its own parameters, e.g. the Route function takes RouteParameters, and a out-reference to a JSON result that gets filled. The return value is a Status, indicating error or success.
Status - this is a type wrapping Error or Ok for indicating error or success, respectively.
TableParameters - this is an example of parameter types the Routing Machine functions expect. In this case Table expects its own parameters as TableParameters. You can see it wrapping two vectors, sources and destinations --- these are indices into your coordinates for the table service to construct a matrix from (empty sources or destinations means: use all of them). If you ask yourself where coordinates come from, you can see TableParameters inheriting from BaseParameters.
BaseParameters - this most importantly holds coordinates (and a few other optional properties that you don't need for basic usage); the specific parameter types inherit from BaseParameters to get these member attributes. That means your TableParameters type has coordinates, sources and destinations member attributes (and a few other that we ignore for now).
Coordinate - this is a wrapper around a (longitude, latitude) pair. We really don't care about (lon, lat) vs (lat, lon) but we don't want you to accidentally mix them up, so both latitude and longitude are strictly typed wrappers around integers (fixed notation such as 13423240) and floating points (floating notation such as 13.42324).
Parameters for other services - here are all other *Parameters you need for other Routing Machine services.
JSON - this is a sum type resembling JSON. The Routing Machine service functions take a out-ref to a JSON result and fill it accordingly. It is currently implemented using std::variant. There are two ways to work with this sum type: either provide a visitor that acts on each type on visitation (with std::visit) or use the std::get function in case you're sure about the structure. The JSON structure is written down in the HTTP API.
See the example folder in the OSRM repository.
OSRM instance initialized with a EngineConfigOSRM object providing service specific *ParametersThe OSRM method is the main constructor for creating an OSRM instance. An OSRM instance requires a .osrm.* dataset(.osrm.* because it contains several files), which is prepared by the OSRM toolchain. You can create such a .osrm.* dataset by running the OSRM binaries we ship in node_modules/osrm/lib/binding_napi_v8/ and default profiles (e.g. for setting speeds and determining road types to route on) in node_modules/osrm/profiles/:
node_modules/osrm/lib/binding_napi_v8/osrm-extract data.osm.pbf -p node_modules/osrm/profiles/car.lua
+node_modules/osrm/lib/binding_napi_v8/osrm-contract data.osrm
+Consult the osrm-backend documentation for further details.
Once you have a complete network.osrm.* dataset, you can calculate routes in javascript with this object.
var osrm = new OSRM('network.osrm');options (Object | String) Options for creating an OSRM object or string to the .osrm file. (optional, default {shared_memory:true})
options.algorithm String? The algorithm to use for routing. Can be 'CH', or 'MLD'. Default is 'CH'. Make sure you prepared the dataset with the correct toolchain.options.shared_memory Boolean? Connects to the persistent shared memory datastore. This requires you to run osrm-datastore prior to creating an OSRM object.options.dataset_name String? Connects to the persistent shared memory datastore defined by --dataset_name option when running osrm-datastore. This requires you to run osrm-datastore --dataset_name prior to creating an OSRM object.options.memory_file String? DEPRECATED Old behaviour: Path to a file on disk to store the memory using mmap. Current behaviour: setting this value is the same as setting mmap_memory: true.options.mmap_memory Boolean? Map on-disk files to virtual memory addresses (mmap), rather than loading into RAM.options.path String? The path to the .osrm files. This is mutually exclusive with setting {options.shared_memory} to true.options.disable_feature_dataset Array? Disables a feature dataset from being loaded into memory if not needed. Options: ROUTE_STEPS, ROUTE_GEOMETRY.options.max_locations_trip Number? Max. locations supported in trip query (default: unlimited).options.max_locations_viaroute Number? Max. locations supported in viaroute query (default: unlimited).options.max_locations_distance_table Number? Max. locations supported in distance table query (default: unlimited).options.max_locations_map_matching Number? Max. locations supported in map-matching query (default: unlimited).options.max_radius_map_matching Number? Max. radius size supported in map matching query (default: 5).options.max_results_nearest Number? Max. results supported in nearest query (default: unlimited).options.max_alternatives Number? Max. number of alternatives supported in alternative routes query (default: 3).options.default_radius Number? Default radius for queries (default: unlimited).Returns the fastest route between two or more coordinates while visiting the waypoints in order.
options Object Object literal containing parameters for the route query.
options.coordinates Array? The coordinates this request will use, coordinates as [{lon},{lat}] values, in decimal degrees.options.bearings Array? Limits the search to segments with given bearing in degrees towards true north in clockwise direction. Can be null or an array of [{value},{range}] with integer 0 .. 360,integer 0 .. 180.options.radiuses Array? Limits the coordinate snapping to streets in the given radius in meters. Can be null (unlimited, default) or double >= 0.options.hints Array? Hints for the coordinate snapping. Array of base64 encoded strings.options.exclude Array? List of classes to avoid, order does not matter.options.generate_hints Boolean Whether or not adds a Hint to the response which can be used in subsequent requests. (optional, default true)options.alternatives Boolean Search for alternative routes. (optional, default false)options.alternatives Number Search for up to this many alternative routes. Please note that even if alternative routes are requested, a result cannot be guaranteed. (optional, default 0)options.steps Boolean Return route steps for each route leg. (optional, default false)options.annotations (Array | Boolean) An array with strings of duration, nodes, distance, weight, datasources, speed or boolean for enabling/disabling all. (optional, default false)options.geometries String Returned route geometry format (influences overview and per step). Can also be geojson. (optional, default polyline)options.overview String Add overview geometry either full, simplified according to highest zoom level it could be displayed on, or not at all (false). If you want the overview for each leg, you can use by_legs. (optional, default simplified)options.continue_straight Boolean? Forces the route to keep going straight at waypoints and don't do a uturn even if it would be faster. Default value depends on the profile.options.approaches Array? Restrict the direction on the road network at a waypoint, relative to the input coordinate. Can be null (unrestricted, default), curb or opposite. null/true/falseoptions.waypoints Array? Indices to coordinates to treat as waypoints. If not supplied, all coordinates are waypoints. Must include first and last coordinate index.options.format String? Which output format to use, either json, or flatbuffers.options.snapping String? Which edges can be snapped to, either default, or any. default only snaps to edges marked by the profile as is_startpoint, any will allow snapping to any edge in the routing graph.options.skip_waypoints Boolean Removes waypoints from the response. Waypoints are still calculated, but not serialized. Could be useful in case you are interested in some other part of response and do not want to transfer waste data. (optional, default false)callback Function
var osrm = new OSRM("berlin-latest.osrm");
+osrm.route({coordinates: [[52.519930,13.438640], [52.513191,13.415852]]}, function(err, result) {
+ if(err) throw err;
+ console.log(result.waypoints); // array of Waypoint objects representing all waypoints in order
+ console.log(result.routes); // array of Route objects ordered by descending recommendation rank
+});Returns Object An array of Waypoint objects representing all waypoints in order AND an array of Route objects ordered by descending recommendation rank.
Snaps a coordinate to the street network and returns the nearest n matches.
Note: coordinates in the general options only supports a single {longitude},{latitude} entry.
options Object Object literal containing parameters for the nearest query.
options.coordinates Array? The coordinates this request will use, coordinates as [{lon},{lat}] values, in decimal degrees.options.bearings Array? Limits the search to segments with given bearing in degrees towards true north in clockwise direction. Can be null or an array of [{value},{range}] with integer 0 .. 360,integer 0 .. 180.options.radiuses Array? Limits the coordinate snapping to streets in the given radius in meters. Can be null (unlimited, default) or double >= 0.options.hints Array? Hints for the coordinate snapping. Array of base64 encoded strings.options.generate_hints Boolean Whether or not adds a Hint to the response which can be used in subsequent requests. (optional, default true)options.number Number Number of nearest segments that should be returned. Must be an integer greater than or equal to 1. (optional, default 1)options.approaches Array? Restrict the direction on the road network at a waypoint, relative to the input coordinate. Can be null (unrestricted, default), curb or opposite.options.format String? Which output format to use, either json, or flatbuffers.options.snapping String? Which edges can be snapped to, either default, or any. default only snaps to edges marked by the profile as is_startpoint, any will allow snapping to any edge in the routing graph.callback Function
var osrm = new OSRM('network.osrm');
+var options = {
+ coordinates: [[13.388860,52.517037]],
+ number: 3,
+ bearings: [[0,20]]
+};
+osrm.nearest(options, function(err, response) {
+ console.log(response.waypoints); // array of Waypoint objects
+});Returns Object containing waypoints. waypoints: array of Ẁaypoint objects sorted by distance to the input coordinate. Each object has an additional distance property, which is the distance in meters to the supplied input coordinate.
Computes duration table for the given locations. Allows for both symmetric and asymmetric tables. Optionally returns distance table.
options Object Object literal containing parameters for the table query.
options.coordinates Array? The coordinates this request will use, coordinates as [{lon},{lat}] values, in decimal degrees.options.bearings Array? Limits the search to segments with given bearing in degrees towards true north in clockwise direction. Can be null or an array of [{value},{range}] with integer 0 .. 360,integer 0 .. 180.options.radiuses Array? Limits the coordinate snapping to streets in the given radius in meters. Can be null (unlimited, default) or double >= 0.options.hints Array? Hints for the coordinate snapping. Array of base64 encoded strings.options.generate_hints Boolean Whether or not adds a Hint to the response which can be used in subsequent requests. (optional, default true)options.sources Array? An array of index elements (0 <= integer < #coordinates) to use location with given index as source. Default is to use all.options.destinations Array? An array of index elements (0 <= integer < #coordinates) to use location with given index as destination. Default is to use all.options.approaches Array? Restrict the direction on the road network at a waypoint, relative to the input coordinate.. Can be null (unrestricted, default), curb or opposite.options.fallback_speed Number? Replace null responses in result with as-the-crow-flies estimates based on fallback_speed. Value is in metres/second.options.fallback_coordinate String? Either input (default) or snapped. If using a fallback_speed, use either the user-supplied coordinate (input), or the snapped coordinate (snapped) for calculating the as-the-crow-flies distance between two points.options.scale_factor Number? Multiply the table duration values in the table by this number for more controlled input into a route optimization solver.options.snapping String? Which edges can be snapped to, either default, or any. default only snaps to edges marked by the profile as is_startpoint, any will allow snapping to any edge in the routing graph.options.annotations Array? Return the requested table or tables in response. Can be ['duration'] (return the duration matrix, default), [distance'] (return the distance matrix), or ['duration', distance'] (return both the duration matrix and the distance matrix).callback Function
var osrm = new OSRM('network.osrm');
+var options = {
+ coordinates: [
+ [13.388860,52.517037],
+ [13.397634,52.529407],
+ [13.428555,52.523219]
+ ]
+};
+osrm.table(options, function(err, response) {
+ console.log(response.durations); // array of arrays, matrix in row-major order
+ console.log(response.distances); // array of arrays, matrix in row-major order
+ console.log(response.sources); // array of Waypoint objects
+ console.log(response.destinations); // array of Waypoint objects
+});Returns Object containing durations, distances, sources, and destinations. durations: array of arrays that stores the matrix in row-major order. durations[i][j] gives the travel time from the i-th waypoint to the j-th waypoint. Values are given in seconds. distances: array of arrays that stores the matrix in row-major order. distances[i][j] gives the travel time from the i-th waypoint to the j-th waypoint. Values are given in meters. sources: array of Ẁaypoint objects describing all sources in order. destinations: array of Ẁaypoint objects describing all destinations in order. fallback_speed_cells: (optional) if fallback_speed is used, will be an array of arrays of row,column values, indicating which cells contain estimated values.
This generates Mapbox Vector Tiles that can be viewed with a vector-tile capable slippy-map viewer. The tiles contain road geometries and metadata that can be used to examine the routing graph. The tiles are generated directly from the data in-memory, so are in sync with actual routing results, and let you examine which roads are actually routable, and what weights they have applied.
ZXY Array an array consisting of x, y, and z values representing tile coordinates like wiki.openstreetmap.org/wiki/Slippy_map_tilenames and are supported by vector tile viewers like Mapbox GL JS.callback Functionvar osrm = new OSRM('network.osrm');
+osrm.tile([0, 0, 0], function(err, response) {
+ if (err) throw err;
+ fs.writeFileSync('./tile.vector.pbf', response); // write the buffer to a file
+});Returns Buffer contains a Protocol Buffer encoded vector tile.
Map matching matches given GPS points to the road network in the most plausible way. Please note the request might result multiple sub-traces. Large jumps in the timestamps (>60s) or improbable transitions lead to trace splits if a complete matching could not be found. The algorithm might not be able to match all points. Outliers are removed if they can not be matched successfully.
options Object Object literal containing parameters for the match query.
options.coordinates Array? The coordinates this request will use, coordinates as [{lon},{lat}] values, in decimal degrees.options.bearings Array? Limits the search to segments with given bearing in degrees towards true north in clockwise direction. Can be null or an array of [{value},{range}] with integer 0 .. 360,integer 0 .. 180.options.hints Array? Hints for the coordinate snapping. Array of base64 encoded strings.options.generate_hints Boolean Whether or not adds a Hint to the response which can be used in subsequent requests. (optional, default true)options.steps Boolean Return route steps for each route. (optional, default false)options.annotations (Array | Boolean) An array with strings of duration, nodes, distance, weight, datasources, speed or boolean for enabling/disabling all. (optional, default false)options.geometries String Returned route geometry format (influences overview and per step). Can also be geojson. (optional, default polyline)options.overview String Add overview geometry either full, simplified according to highest zoom level it could be display on, or not at all (false). (optional, default simplified)options.timestamps Array<Number>? Timestamp of the input location (integers, UNIX-like timestamp).options.radiuses Array? Standard deviation of GPS precision used for map matching. If applicable use GPS accuracy. Can be null for default value 5 meters or double >= 0.options.gaps String Allows the input track splitting based on huge timestamp gaps between points. Either split or ignore. (optional, default split)options.tidy Boolean Allows the input track modification to obtain better matching quality for noisy tracks. (optional, default false)options.waypoints Array? Indices to coordinates to treat as waypoints. If not supplied, all coordinates are waypoints. Must include first and last coordinate index.options.snapping String? Which edges can be snapped to, either default, or any. default only snaps to edges marked by the profile as is_startpoint, any will allow snapping to any edge in the routing graph.callback Function
var osrm = new OSRM('network.osrm');
+var options = {
+ coordinates: [[13.393252,52.542648],[13.39478,52.543079],[13.397389,52.542107]],
+ timestamps: [1424684612, 1424684616, 1424684620]
+};
+osrm.match(options, function(err, response) {
+ if (err) throw err;
+ console.log(response.tracepoints); // array of Waypoint objects
+ console.log(response.matchings); // array of Route objects
+});Returns Object containing tracepoints and matchings. tracepoints Array of Ẁaypoint objects representing all points of the trace in order. If the trace point was omitted by map matching because it is an outlier, the entry will be null. Each Waypoint object has the following additional properties, 1) matchings_index: Index to the Route object in matchings the sub-trace was matched to, 2) waypoint_index: Index of the waypoint inside the matched route. 3) alternatives_count: Number of probable alternative matchings for this trace point. A value of zero indicate that this point was matched unambiguously. Split the trace at these points for incremental map matching. matchings is an array of Route objects that assemble the trace. Each Route object has an additional confidence property, which is the confidence of the matching. float value between 0 and 1. 1 is very confident that the matching is correct.
The trip plugin solves the Traveling Salesman Problem using a greedy heuristic (farthest-insertion algorithm) for 10 or * more waypoints and uses brute force for less than 10 waypoints. The returned path does not have to be the shortest path, * as TSP is NP-hard it is only an approximation.
Note that all input coordinates have to be connected for the trip service to work. Currently, not all combinations of roundtrip, source and destination are supported. Right now, the following combinations are possible:
| roundtrip | source | destination | supported |
|---|---|---|---|
| true | first | last | yes |
| true | first | any | yes |
| true | any | last | yes |
| true | any | any | yes |
| false | first | last | yes |
| false | first | any | no |
| false | any | last | no |
| false | any | any | no |
options Object Object literal containing parameters for the trip query.
options.coordinates Array? The coordinates this request will use, coordinates as [{lon},{lat}] values, in decimal degrees.options.bearings Array? Limits the search to segments with given bearing in degrees towards true north in clockwise direction. Can be null or an array of [{value},{range}] with integer 0 .. 360,integer 0 .. 180.options.radiuses Array? Limits the coordinate snapping to streets in the given radius in meters. Can be double >= 0 or null (unlimited, default).options.hints Array? Hints for the coordinate snapping. Array of base64 encoded strings.options.generate_hints Boolean Whether or not adds a Hint to the response which can be used in subsequent requests. (optional, default true)options.steps Boolean Return route steps for each route. (optional, default false)options.annotations (Array | Boolean) An array with strings of duration, nodes, distance, weight, datasources, speed or boolean for enabling/disabling all. (optional, default false)options.geometries String Returned route geometry format (influences overview and per step). Can also be geojson. (optional, default polyline)options.overview String Add overview geometry either full, simplified, false or by_legs. (optional, default simplified)options.roundtrip Boolean Return route is a roundtrip. (optional, default true)options.source String Return route starts at any or first coordinate. (optional, default any)options.destination String Return route ends at any or last coordinate. (optional, default any)options.approaches Array? Restrict the direction on the road network at a waypoint, relative to the input coordinate. Can be null (unrestricted, default), curb or opposite.options.snapping String? Which edges can be snapped to, either default, or any. default only snaps to edges marked by the profile as is_startpoint, any will allow snapping to any edge in the routing graph.callback Function
var osrm = new OSRM('network.osrm');
+var options = {
+ coordinates: [
+ [13.36761474609375, 52.51663871100423],
+ [13.374481201171875, 52.506191342034576]
+ ],
+ source: "first",
+ destination: "last",
+ roundtrip: false
+}
+osrm.trip(options, function(err, response) {
+ if (err) throw err;
+ console.log(response.waypoints); // array of Waypoint objects
+ console.log(response.trips); // array of Route objects
+});Returns Object containing waypoints and trips. waypoints: an array of Waypoint objects representing all waypoints in input order. Each Waypoint object has the following additional properties, 1) trips_index: index to trips of the sub-trip the point was matched to, and 2) waypoint_index: index of the point in the trip. trips: an array of Route objects that assemble the trace.
All plugins support a second additional object that is available to configure some NodeJS specific behaviours.
plugin_config Object? Object literal containing parameters for the trip query.
plugin_config.format String? The format of the result object to various API calls. Valid options are object (default if options.format is json), which returns a standard Javascript object, as described above, and buffer(default if options.format is flatbuffers), which will return a NodeJS Buffer object, containing a JSON string or Flatbuffers object. The latter has the advantage that it can be immediately serialized to disk/sent over the network, and the generation of the string is performed outside the main NodeJS event loop. This option is ignored by the tile plugin. Also note that options.format set to flatbuffers cannot be used with plugin_config.format set to object. json_buffer is deprecated alias for buffer.var osrm = new OSRM('network.osrm');
+var options = {
+ coordinates: [
+ [13.36761474609375, 52.51663871100423],
+ [13.374481201171875, 52.506191342034576]
+ ]
+};
+osrm.route(options, { format: "buffer" }, function(err, response) {
+ if (err) throw err;
+ console.log(response.toString("utf-8"));
+});Represents a route through (potentially multiple) waypoints.
external documentation in osrm-backendRepresents a route between two waypoints.
external documentation in osrm-backendA step consists of a maneuver such as a turn or merge, followed by a distance of travel along a single way to the subsequent step.
external documentation in osrm-backendexternal documentation in osrm-backendObject used to describe waypoint on a route.
external documentation in osrm-backendOSRM supports "profiles". Profiles represent routing behavior for different transport modes like car, bike and foot. You can also create profiles for variations like a fastest/shortest car profile or fastest/safest/greenest bicycles profile.
A profile describes whether or not it's possible to route along a particular type of way, whether we can pass a particular node, and how quickly we'll be traveling when we do. This feeds into the way the routing graph is created and thus influences the output routes.
Out-of-the-box OSRM comes with profiles for car, bicycle and foot. You can easily modify these or create new ones if you like.
Profiles have a 'lua' extension, and are placed in 'profiles' directory.
When running OSRM preprocessing commands you specify the profile with the --profile (or the shorthand -p) option, for example:
osrm-extract --profile ../profiles/car.lua planet-latest.osm.pbf
You can extract the same OSM file with different profiles by specifying an output path:
osrm-extract --profile profiles/car.lua planet.osm.pbf --output /data/car
+osrm-extract --profile profiles/bicycle.lua planet.osm.pbf --output /data/bicycle
+osrm-extract --profile profiles/foot.lua planet.osm.pbf --output /data/footThis avoids the need to create symbolic links to the input file.
It's important to understand that profiles are used when preprocessing the OSM data, NOT at query time when routes are computed.
This means that after modifying a profile you will need to extract, contract and reload the data again and to see changes in the routing results. See Processing Flow for more.
Profiles are not just configuration files. They are scripts written in the Lua scripting language. The reason for this is that OpenStreetMap data is complex, and it's not possible to simply define tag mappings. Lua scripting offers a powerful way to handle all the possible tag combinations found in OpenStreetMap nodes and ways.
A profile will process every node and way in the OSM input data to determine what ways are routable in which direction, at what speed, etc.
A profile will typically:
A profile can also define various local functions it needs.
Looking at car.lua as an example, at the top of the file the api version is defined and then required library files are included.
Then follows the setup function, which is called once when the profile is loaded. It returns a big hash table of configurations, specifying things like what speed to use for different way types. The configurations are used later in the various processing functions. Many adjustments can be done just by modifying this configuration table.
The setup function is also where you can do other setup, like loading an elevation data source if you want to consider that when processing ways.
Then come the process_node and process_way functions, which are called for each OSM node and way when extracting OpenStreetMap data with osrm-extract.
The process_turn function processes every possible turn in the network, and sets a penalty depending on the angle and turn of the movement.
Profiles can also define a process_segment function to handle differences in speed along an OSM way, for example to handle elevation. As you can see, this is not currently used in the car profile.
At the end of the file, a table is returned with references to the setup and processing functions the profile has defined.
When computing a route from A to B there can be different measures of what is the best route. That's why there's a need for different profiles.
Because speeds vary on different types of roads, the shortest and the fastest route are typically different. But there are many other possible preferences. For example a user might prefer a bicycle route that follow parks or other green areas, even though both duration and distance are a bit longer.
To handle this, OSRM doesn't simply choose the ways with the highest speed. Instead it uses the concepts of weight and rate. The rate is an abstract measure that you can assign to ways as you like to make some ways preferable to others. Routing will prefer ways with high rate.
The weight of a way is normally computed as length / rate. The weight can be thought of as the resistance or cost when passing the way. Routing will prefer ways with low weight.
You can also set the weight of a way to a fixed value. In this case it's not calculated based on the length or rate, and the rate is ignored.
You should set the speed to your best estimate of the actual speed that will be used on a particular way. This will result in the best estimated travel times.
If you want to prefer certain ways due to other factors than the speed, adjust the rate accordingly. If you adjust the speed, the time estimation will be skewed.
If you set the same rate on all ways, the result will be shortest path routing. If you set rate = speed on all ways, the result will be fastest path routing. If you want to prioritize certain streets, increase the rate on these.
A profile should set api_version at the top of your profile. This is done to ensure that older profiles are still supported when the api changes. If api_version is not defined, 0 will be assumed. The current api version is 4.
The folder profiles/lib/ contains Lua library files for handling many common processing tasks.
| File | Notes |
|---|---|
| way_handlers.lua | Functions for processing way tags |
| tags.lua | Functions for general parsing of OSM tags |
| set.lua | Defines the Set helper for handling sets of values |
| sequence.lua | Defines the Sequence helper for handling sequences of values |
| access.lua | Function for finding relevant access tags |
| destination.lua | Function for finding relevant destination tags |
| maxspeed.lua | Function for determining maximum speed |
| guidance.lua | Function for processing guidance attributes |
They all return a table of functions when you use require to load them. You can either store this table and reference its functions later, or if you need only a single function you can store that directly.
The setup function is called once when the profile is loaded and must return a table of configurations. It's also where you can do other global setup, like loading data sources that are used during processing.
Note that processing of data is parallelized and several unconnected Lua interpreters will be running at the same time. The setup function will be called once for each. Each Lua interpreter will have its own set of globals.
The following global properties can be set under properties in the hash you return in the setup function:
| Attribute | Type | Notes |
|---|---|---|
| weight_name | String | Name used in output for the routing weight property (default 'duration') |
| weight_precision | Unsigned | Decimal precision of edge weights (default 1) |
| left_hand_driving | Boolean | Are vehicles assumed to drive on the left? (used in guidance, default false) |
| use_turn_restrictions | Boolean | Are turn restrictions followed? (default false) |
| continue_straight_at_waypoint | Boolean | Must the route continue straight on at a via point, or are U-turns allowed? (default true) |
| max_speed_for_map_matching | Float | Maximum vehicle speed to be assumed in matching (in m/s) |
| max_turn_weight | Float | Maximum turn penalty weight |
| force_split_edges | Boolean | True value forces a split of forward and backward edges of extracted ways and guarantees that process_segment will be called for all segments (default false) |
The following additional global properties can be set in the hash you return in the setup function:
| Attribute | Type | Notes |
|---|---|---|
| excludable | Sequence of Sets | Determines which class-combinations are supported by the exclude option at query time. E.g. Sequence{Set{"ferry", "motorway"}, Set{"motorway"}} will allow you to exclude ferries and motorways, or only motorways. |
| classes | Sequence | Determines the allowed classes that can be referenced using {forward,backward}_classes on the way in the process_way function. |
| restrictions | Sequence | Determines which turn restrictions will be used for this profile. |
| suffix_list | Set | List of name suffixes needed for determining if "Highway 101 NW" the same road as "Highway 101 ES". |
| relation_types | Sequence | Determines which relations should be cached for processing in this profile. It contains relations types |
Process an OSM node to determine whether this node is an obstacle, if it can be passed at all and whether passing it incurs a delay.
| Argument | Description |
|---|---|
| profile | The configuration table you returned in setup. |
| node | The input node to process (read-only). |
| result | The output that you will modify. |
| relations | Storage of relations to access relations, where node is a member. |
The following attributes can be set on result: (Note: for new code use the obstacle_map.
| Attribute | Type | Notes |
|---|---|---|
| barrier | Boolean | Is it an impassable barrier? |
| traffic_lights | Boolean | Is it a traffic light (incurs delay in process_turn)? |
A user type that represents an obstacle on the road or a place where you can turn around.
This may be a completely impassable obstacle like a barrier, a temporary obstacle like a traffic light or a stop sign, or an obstacle that just slows you down like a traffic_calming. The obstacle may be present in both directions or in one direction only.
This also represents a good turning point like a mini_roundabout, turning_loop, or turning_circle.
An object of this type is immutable once constructed.
local obs = Obstacle.new(
+ obstacle_type.traffic_signals,
+ obstacle_direction.forward,
+ 2.5,
+ 0
+)
+assert(obs.duration == 2.5)| Member | Mode | Type | Notes |
|---|---|---|---|
| type | read-only | obstacle_type | eg. obstacle_type.barrier |
| direction | read-only | obstacle_direction | eg. obstacle_direction.forward |
| duration | read-only | float | The expected delay in seconds |
| weight | read-only | float | The weight |
An enum with the following keys:
| Keys |
|---|
| none |
| barrier |
| traffic_signals |
| stop |
| give_way |
| crossing |
| traffic_calming |
| mini_roundabout |
| turning_loop |
| turning_circle |
| gate |
An enum with the following keys:
| Keys |
|---|
| none |
| forward |
| backward |
| both |
A global user type. It stores obstacles.
The canonical workflow is: to store obstacles in process_node() and retrieve them in process_turn().
Note: In the course of processing, between the process_node() stage and the process_turn() stage, the extractor switches from using OSM nodes to using internal nodes. Both types have different ids. You can only store OSM nodes and only retrieve internal nodes. This implies that, in process_node(), you cannot retrieve an obstacle you have just stored.
Call this function inside process_node() to register an obstacle on a node. You can register as many different obstacles as you wish on any given node. It is your responsibility to register the same obstacle only once.
In a following step -- likely in process_turn() -- you can retrieve all obstacles registered at any given node. This function works with OSM nodes.
| Argument | Type | Notes |
|---|---|---|
| node | OSMNode | The same node as passed to process_node. |
| obstacle | Obstacle | The obstacle |
Usage example:
function process_node(profile, node, result, relations)
+ ...
+ obstacle_map:add(node,
+ Obstacle.new(
+ obstacle_type.traffic_signal,
+ obstacle_direction.forward,
+ 2, 0))
+endReturn true if there are any obstacles at node to when coming from node from and having the type type.
You will likely call this function inside process_turn(). Note that this works only with internal nodes, not with OSM nodes.
bool obstacle_map:any(to)
+bool obstacle_map:any(from, to)
+bool obstacle_map:any(from, to, type)| Argument | Type | Notes |
|---|---|---|
| from | Node | The leading node. Optional. |
| to | Node | The node with the obstacle. |
| type | obstacle_type | The obstacle type. Defaults to all types. May be a bitwise-or combination of types. |
| returns | bool | True if there are any obstacles satisfiying the given criteria. |
Usage examples:
function process_turn(profile, turn)
+ if obstacle_map:any(turn.via) then
+ ...
+ end
+ if obstacle_map:any(turn.from, turn.via, obstacle_type.traffic_signal) then
+ turn.duration = turn.duration + 2
+ end
+endThis function retrieves all registered obstacles at node to when coming from the node from and having the type type.
You will likely call this function inside process_turn(). Note that this works only with internal nodes, not with OSM nodes.
obstacle_map:get(to)
+obstacle_map:get(from, to)
+obstacle_map:get(from, to, type)| Argument | Type | Notes |
|---|---|---|
| from | Node | The leading node. Optional. |
| to | Node | The node with the obstacle. |
| type | obstacle_type | The obstacle type. Defaults to all types. May be a bitwise-or combination of types. |
| returns | table | A table of Obstacles. |
Usage examples:
function process_turn(profile, turn)
+ for _, obs in pairs(obstacle_map:get(turn.via)) do
+ if obs.type == obstacle_type.barrier then
+ turn.duration = turn.duration + obs.duration
+ end
+ end
+ for _, obs in pairs(obstacle_map:get(
+ turn.from, turn.via, obstacle_type.traffic_signal)) do
+ turn.duration = turn.duration + obs.duration
+ end
+endGiven an OpenStreetMap way, the process_way function will either return nothing (meaning we are not going to route over this way at all), or it will set up a result hash.
| Argument | Description |
|---|---|
| profile | The configuration table you returned in setup. |
| way | The input way to process (read-only). |
| result | The output that you will modify. |
| relations | Storage of relations to access relations, where way is a member. |
Importantly it will set result.forward_mode and result.backward_mode to indicate the travel mode in each direction, as well as set result.forward_speed and result.backward_speed to integer values representing the speed for traversing the way.
It will also set a number of other attributes on result.
Using the power of the scripting language you wouldn't typically see something as simple as a result.forward_speed = 20 line within the process_way function. Instead process_way will examine the tag set on the way, process this information in various ways, calling other local functions and referencing the configuration in profile, etc., before arriving at the result.
The following attributes can be set on the result in process_way:
| Attribute | Type | Notes |
|---|---|---|
| forward_speed | Float | Speed on this way in km/h. Mandatory. |
| backward_speed | Float | "" |
| forward_rate | Float | Routing weight, expressed as meters/weight (e.g. for a fastest-route weighting, you would want this to be meters/second, so set it to forward_speed/3.6) |
| backward_rate | Float | "" |
| forward_mode | Enum | Mode of travel (e.g. car, ferry). Mandatory. Defined in include/extractor/travel_mode.hpp. |
| backward_mode | Enum | "" |
| forward_classes | Table | Mark this way as being of a specific class, e.g. result.classes["toll"] = true. This will be exposed in the API as classes on each RouteStep. |
| backward_classes | Table | "" |
| duration | Float | Alternative setter for duration of the whole way in both directions |
| weight | Float | Alternative setter for weight of the whole way in both directions |
| turn_lanes_forward | String | Directions for individual lanes (normalized OSM turn:lanes value) |
| turn_lanes_backward | String | "" |
| forward_restricted | Boolean | Is this a restricted access road? (e.g. private, or deliveries only; used to enable high turn penalty, so that way is only chosen for start/end of route) |
| backward_restricted | Boolean | "" |
| is_startpoint | Boolean | Can a journey start on this way? (e.g. ferry; if false, prevents snapping the start point to this way) |
| roundabout | Boolean | Is this part of a roundabout? |
| circular | Boolean | Is this part of a non-roundabout circular junction? |
| name | String | Name of the way |
| ref | String | Road number (equal to set forward_ref and backward_ref with one value) |
| forward_ref | String | Road number in forward way direction |
| backward_ref | String | Road number in backward way direction |
| destinations | String | The road's destinations |
| exits | String | The ramp's exit numbers or names |
| pronunciation | String | Name pronunciation |
| road_classification.motorway_class | Boolean | Guidance: way is a motorway |
| road_classification.link_class | Boolean | Guidance: way is a slip/link road |
| road_classification.road_priority_class | Enum | Guidance: order in priority list. Defined in include/extractor/road_classification.hpp |
| road_classification.may_be_ignored | Boolean | Guidance: way is non-highway |
| road_classification.num_lanes | Unsigned | Guidance: total number of lanes in way |
The WayHandlers.names function in way_handlers.lua handles extraction of way names for routing instructions. It processes the following OSM tags:
| Tag | Notes |
|---|---|
name | Primary name of the way |
name:pronunciation | Pronunciation hint for text-to-speech |
ref | Road reference number (e.g., "A1", "I-95") |
junction:ref | Exit or junction reference number |
For unnamed sidewalks and sidepaths (where highway=footway, highway=cycleway, or highway=path), the function also supports fallback name tags when the way is marked as a sidepath:
| Tag | Notes |
|---|---|
is_sidepath:of:name | Name of the street the sidepath follows (checked first) |
street:name | Alternative tag for the associated street name |
The fallback is only applied when the way has one of these sidepath markers:
footway=sidewalkcycleway=sidepathis_sidepath=yesThis allows routing instructions to show street names for separately mapped sidewalks, e.g., "Turn right onto Main Street" instead of just "Turn right".
The process_segment function is called for every segment of OSM ways. A segment is a straight line between two OSM nodes.
An OpenStreetMap way cannot have different tags on different parts of a way. Instead you would split the way into several smaller ways. However, many ways are long. For example, many ways pass over hills without any change in tags.
Processing each segment of an OSM way makes it possible to have different speeds on different parts of a way based on external data like data about elevation, pollution, noise or scenic value and adjust weight and duration of the segment accordingly.
In the process_segment function you don't have access to OSM tags. Instead you use the geographical location of the start and end point of the way to look up information from another data source, like elevation data. See rasterbot.lua for an example.
The following attributes can be read and set on the result in process_segment:
| Attribute | Read/write? | Type | Notes |
|---|---|---|---|
| source.lon | Read | Float | Co-ordinates of segment start |
| source.lat | Read | Float | "" |
| target.lon | Read | Float | Co-ordinates of segment end |
| target.lat | Read | Float | "" |
| distance | Read | Float | Length of segment |
| weight | Read/write | Float | Routing weight for this segment |
| duration | Read/write | Float | Duration for this segment |
The process_turn function is called for every possible turn in the network. Based on the angle and type of turn you assign the weight and duration of the movement.
The following attributes can be read and set on the result in process_turn:
| Attribute | Read/write? | Type | Notes |
|---|---|---|---|
| angle | Read | Float | Angle of turn in degrees ([-179, 180]: 0=straight, 180=u turn, +x=x degrees to the right, -x= x degrees to the left) |
| number_of_roads | Read | Integer | Number of ways at the intersection of the turn |
| is_u_turn | Read | Boolean | Is the turn a u-turn? |
| has_traffic_light | Read | Boolean | Is a traffic light present at this turn? |
| is_left_hand_driving | Read | Boolean | Is left-hand traffic? |
| source_restricted | Read | Boolean | Is it from a restricted access road? (See definition in process_way) |
| source_mode | Read | Enum | Travel mode before the turn. Defined in include/extractor/travel_mode.hpp |
| source_is_motorway | Read | Boolean | Is the source road a motorway? |
| source_is_link | Read | Boolean | Is the source road a link? |
| source_number_of_lanes | Read | Integer | How many lanes does the source road have? (default when not tagged: 0) |
| source_highway_turn_classification | Read | Integer | Classification based on highway tag defined by user during setup. (default when not set: 0, allowed classification values are: 0-15)) |
| source_access_turn_classification | Read | Integer | Classification based on access tag defined by user during setup. (default when not set: 0, allowed classification values are: 0-15)) |
| source_speed | Read | Integer | Speed on this source road in km/h |
| source_priority_class | Read | Enum | The type of road priority class of the source. Defined in include/extractor/road_classification.hpp |
| target_restricted | Read | Boolean | Is the target a restricted access road? (See definition in process_way) |
| target_mode | Read | Enum | Travel mode after the turn. Defined in include/extractor/travel_mode.hpp |
| target_is_motorway | Read | Boolean | Is the target road a motorway? |
| target_is_link | Read | Boolean | Is the target road a link? |
| target_number_of_lanes | Read | Integer | How many lanes does the target road have? (default when not tagged: 0) |
| target_highway_turn_classification | Read | Integer | Classification based on highway tag defined by user during setup. (default when not set: 0, allowed classification values are: 0-15)) |
| target_access_turn_classification | Read | Integer | Classification based on access tag defined by user during setup. (default when not set: 0, allowed classification values are: 0-15)) |
| target_speed | Read | Integer | Speed on this target road in km/h |
| target_priority_class | Read | Enum | The type of road priority class of the target. Defined in include/extractor/road_classification.hpp |
| from | Read | NodeID | The leading node |
| via | Read | NodeID | The intersection node |
| to | Read | NodeID | The trailing node |
| source_road | Read | ExtractionTurnLeg | The incoming road |
| target_road | Read | ExtractionTurnLeg | The outgoing road |
| roads_on_the_right | Read | Vector<ExtractionTurnLeg> | Vector with information about other roads on the right of the turn that are also connected at the intersection |
| roads_on_the_left | Read | Vector<ExtractionTurnLeg> | Vector with information about other roads on the left of the turn that are also connected at the intersection. If turn is a u turn, this is empty. |
| weight | Read/write | Float | Penalty to be applied for this turn (routing weight) |
| duration | Read/write | Float | Penalty to be applied for this turn (duration in deciseconds) |
from, via, and to Use these node IDs to retrieve obstacles. See: obstacle_map:get.
source_road, target_road, roads_on_the_right, and roads_on_the_left The information of source_road, target_road, roads_on_the_right, and roads_on_the_left that can be read are as follows:
| Attribute | Read/write? | Type | Notes |
|---|---|---|---|
| is_restricted | Read | Boolean | Is it a restricted access road? (See definition in process_way) |
| mode | Read | Enum | Travel mode before the turn. Defined in include/extractor/travel_mode.hpp |
| is_motorway | Read | Boolean | Is the road a motorway? |
| is_link | Read | Boolean | Is the road a link? |
| number_of_lanes | Read | Integer | How many lanes does the road have? (default when not tagged: 0) |
| highway_turn_classification | Read | Integer | Classification based on highway tag defined by user during setup. (default when not set: 0, allowed classification values are: 0-15) |
| access_turn_classification | Read | Integer | Classification based on access tag defined by user during setup. (default when not set: 0, allowed classification values are: 0-15) |
| speed | Read | Integer | Speed on this road in km/h |
| distance | Read | Double | The length of the road edge |
| priority_class | Read | Enum | The type of road priority class of the leg. Defined in include/extractor/road_classification.hpp |
| is_incoming | Read | Boolean | Is the road an incoming road of the intersection |
| is_outgoing | Read | Boolean | Is the road an outgoing road of the intersection |
The order of the roads in roads_on_the_right and roads_on_the_left are counter clockwise. If the turn is a u turn, all other connected roads will be in roads_on_the_right.
Example
c e
+ | /
+ | /
+ a ---- x ---- b
+ /|
+ / |
+ f dWhen turning from a to b via x,
roads_on_the_right[1] is the road xfroads_on_the_right[2] is the road xdroads_on_the_left[1] is the road xeroads_on_the_left[2] is the road xcNote that indices of arrays in Lua are 1-based.
highway_turn_classification and access_turn_classification When setting appropriate turn weights and duration, information about the highway and access tags of roads that are involved in the turn are necessary. The Lua turn function process_turn does not have access to the original OSM tags anymore. However, highway_turn_classification and access_turn_classification can be set during setup. The classification set during setup can be later used in process_turn.
Example
In the following example we use highway_turn_classification to set the turn weight to 10 if the turn is on a highway and to 5 if the turn is on a primary.
function setup()
+ return {
+ highway_turn_classification = {
+ ['motorway'] = 2,
+ ['primary'] = 1
+ }
+ }
+end
+
+function process_turn(profile, turn) {
+ if turn.source_highway_turn_classification == 2 and turn.target_highway_turn_classification == 2 then
+ turn.weight = 10
+ end
+ if turn.source_highway_turn_classification == 1 and turn.target_highway_turn_classification == 1 then
+ turn.weight = 5
+ end
+}The guidance parameters in profiles are currently a work in progress. They can and will change. Please be aware of this when using guidance configuration possibilities.
Guidance uses road classes to decide on when/if to emit specific instructions and to discover which road is obvious when following a route. Classification uses three flags and a priority-category. The flags indicate whether a road is a motorway (required for on/off ramps), a link type (the ramps itself, if also a motorway) and whether a road may be omitted in considerations (is considered purely for connectivity). The priority-category influences the decision which road is considered the obvious choice and which roads can be seen as fork. Forks can be emitted between roads of similar priority category only. Obvious choices follow a major priority road, if the priority difference is large.
OSRM has built-in support for loading an interpolating raster data in ASCII format. This can be used e.g. for factoring in elevation when computing routes.
Use raster:load() in your setup function to load data and store the source in your configuration hash:
function setup()
+ return {
+ raster_source = raster:load(
+ "rastersource.asc", -- file to load
+ 0, -- longitude min
+ 0.1, -- longitude max
+ 0, -- latitude min
+ 0.1, -- latitude max
+ 5, -- number of rows
+ 4 -- number of columns
+ )
+ }
+endThe input data must be an ASCII file with rows of integers, e.g.:
0 0 0 0
+0 0 0 250
+0 0 250 500
+0 0 0 250
+0 0 0 0In your segment_function you can then access the raster source and use raster:query() to query to find the nearest data point, or raster:interpolate() to interpolate a value based on nearby data points.
You must check whether the result is valid before using it.
Example:
function process_segment (profile, segment)
+ local sourceData = raster:query(profile.raster_source, segment.source.lon, segment.source.lat)
+ local targetData = raster:query(profile.raster_source, segment.target.lon, segment.target.lat)
+
+ local invalid = sourceData.invalid_data()
+ if sourceData.datum ~= invalid and targetData.datum ~= invalid then
+ -- use values to adjust weight and duration
+ [...]
+endSee rasterbot.lua and rasterbotinterp.lua for examples.
There are a few helper functions defined in the global scope that profiles can use:
durationIsValidparseDurationtrimLaneStringapplyAccessTokenscanonicalizeStringListThe Python bindings provide access to OSRM's routing services through the osrm package. Install with pip install osrm-bindings.
The OSRM class is the main entry point. It requires a .osrm.* dataset prepared by the OSRM toolchain.
import osrm
+
+# From file
+engine = osrm.OSRM("path/to/data.osrm")
+
+# With keyword arguments
+engine = osrm.OSRM(
+ storage_config="path/to/data.osrm",
+ algorithm="CH", # or "MLD"
+ use_shared_memory=False,
+ max_locations_trip=3,
+ max_locations_viaroute=3,
+ max_locations_distance_table=3,
+ max_locations_map_matching=3,
+ max_results_nearest=1,
+ max_alternatives=1,
+ default_radius="unlimited",
+)
+
+# Using shared memory (requires osrm-datastore)
+engine = osrm.OSRM(use_shared_memory=True)storage_config str - Path to the .osrm dataset.algorithm str - Routing algorithm: "CH" or "MLD". Default: "CH".use_shared_memory bool - Connect to shared memory datastore. Default: True.dataset_name str - Named shared memory dataset (requires osrm-datastore --dataset_name).memory_file str - Deprecated. Equivalent to use_mmap=True.use_mmap bool - Memory-map files instead of loading into RAM.max_locations_trip int - Max locations in trip queries.max_locations_viaroute int - Max locations in route queries.max_locations_distance_table int - Max locations in table queries.max_locations_map_matching int - Max locations in match queries.max_results_nearest int - Max results in nearest queries.max_alternatives int - Max alternative routes.default_radius float | "unlimited" - Default search radius in meters.All service methods take a parameters object and return a dict-like Object:
result = engine.Route(route_params)
+print(result["routes"])
+print(result["waypoints"])Finds the fastest route between two or more coordinates.
params = osrm.RouteParameters(
+ coordinates=[(7.41337, 43.72956), (7.41546, 43.73077)],
+ steps=True,
+ alternatives=2,
+ annotations=["speed", "duration"],
+ geometries="geojson",
+ overview="full",
+)
+result = engine.Route(params)Inherits all BaseParameters.
steps bool - Return route steps for each leg. Default: False.alternatives int - Number of alternative routes to search for. Default: 0.annotations list[str] - Additional metadata: "none", "duration", "nodes", "distance", "weight", "datasources", "speed", "all". Default: [].geometries str - Geometry format: "polyline", "polyline6", "geojson". Default: "polyline".overview str - Overview geometry: "simplified", "full", "false". Default: "simplified".continue_straight bool | None - Force route to continue straight at waypoints.waypoints list[int] - Indices of coordinates to treat as waypoints. Must include first and last.Computes duration/distance matrices between coordinates.
params = osrm.TableParameters(
+ coordinates=[(7.41337, 43.72956), (7.41546, 43.73077), (7.41862, 43.73216)],
+ sources=[0],
+ destinations=[1, 2],
+ annotations=["duration", "distance"],
+)
+result = engine.Table(params)Inherits all BaseParameters.
sources list[int] - Indices of source coordinates. Default: all.destinations list[int] - Indices of destination coordinates. Default: all.annotations list[str] - "duration", "distance", "all". Default: ["duration"].fallback_speed float - Speed for crow-flies fallback when no route found.fallback_coordinate_type str - "input" or "snapped".scale_factor float - Scales duration values. Default: 1.0.Finds the nearest street segment for a coordinate.
params = osrm.NearestParameters(
+ coordinates=[(7.41337, 43.72956)],
+ number_of_results=3,
+)
+result = engine.Nearest(params)Inherits all BaseParameters.
number_of_results int - Number of nearest segments to return. Default: 1.Snaps noisy GPS traces to the road network.
params = osrm.MatchParameters(
+ coordinates=[(7.41337, 43.72956), (7.41546, 43.73077), (7.41862, 43.73216)],
+ timestamps=[1424684612, 1424684616, 1424684620],
+ radiuses=[5.0, 5.0, 5.0],
+ annotations=["speed"],
+ geometries="geojson",
+)
+result = engine.Match(params)Inherits all RouteParameters and BaseParameters.
timestamps list[int] - UNIX timestamps for each coordinate.gaps str - Gap handling: "split" or "ignore". Default: "split".tidy bool - Remove duplicates. Default: False.waypoints list[int] - Indices of coordinates to treat as waypoints.Solves the Traveling Salesman Problem for the given coordinates.
params = osrm.TripParameters(
+ coordinates=[(7.41337, 43.72956), (7.41546, 43.73077), (7.41862, 43.73216)],
+ source="first",
+ destination="last",
+ roundtrip=True,
+ annotations=["duration"],
+ geometries="geojson",
+)
+result = engine.Trip(params)Inherits all RouteParameters and BaseParameters.
source str - "any" or "first". Default: "any".destination str - "any" or "last". Default: "any".roundtrip bool - Return to first location. Default: True.Generates vector tiles with internal routing graph data.
params = osrm.TileParameters(x=17059, y=11948, z=15)
+result = engine.Tile(params) # returns bytesx int - Tile x coordinate.y int - Tile y coordinate.z int - Tile zoom level.Shared parameters inherited by Nearest, Table, Route, Match, and Trip.
coordinates list[tuple[float, float]] - List of (longitude, latitude) pairs.hints list[str | None] - Base64-encoded hints from previous requests.radiuses list[float | None] - Search radius per coordinate in meters. None for unlimited.bearings list[tuple[int, int] | None] - (bearing, range) pairs in degrees. None for unrestricted.approaches list[str | None] - "curb", "unrestricted", or None.generate_hints bool - Include hints in response. Default: True.exclude list[str] - Road classes to avoid (e.g. ["motorway"]).snapping str - "default" or "any". Default: "default".coord = osrm.Coordinate((7.41337, 43.72956))
+print(coord.lon, coord.lat)bearing = osrm.Bearing((200, 180))
+print(bearing.bearing, bearing.range)Service results are returned as Object (dict-like) and Array (list-like) wrappers around OSRM's internal JSON types. They support [], len(), in, and iteration.
result = engine.Route(params)
+for route in result["routes"]:
+ print(route["distance"], route["duration"])The package also installs OSRM command-line tools, accessible via python -m osrm:
python -m osrm extract data.osm.pbf -p profiles/car.lua
+python -m osrm contract data.osrm
+python -m osrm partition data.osrm
+python -m osrm customize data.osrm
+python -m osrm datastore data.osrm
+python -m osrm routed data.osrmPre-built wheels are published to PyPI for Linux (x86_64), macOS (x86_64), and Windows (amd64). They use the CPython 3.12 stable ABI (cp312-abi3) and therefore install on Python 3.12+:
pip install osrm-bindingsThe package itself supports Python 3.10+ when built from source — needed for 3.10/3.11, aarch64 Linux, arm64 macOS, or any platform without a pre-built wheel:
pip install osrm-bindings --no-binary osrm-bindingsSource builds compile the full OSRM C++ library — this takes a long time. See platform-specific notes for prerequisites.
Clone the repo and install in editable mode with dev dependencies:
git clone https://github.com/Project-OSRM/osrm-backend
+cd osrm-backend
+pip install -e ".[dev]"Install pre-commit hooks:
pre-commit installCI wheel builds run inside a custom manylinux image (nilsnolde/manylinux, branch osrm_python) that ships vcpkg pre-bootstrapped at the SHA pinned in vcpkg-configuration.json, plus a pre-warmed vcpkg binary cache compiled against this repo's vcpkg.json. The wheel build's own vcpkg install hits that cache instead of recompiling boost/tbb/etc. from source.
The image needs rebuilding when this repo's vcpkg.json, the baseline SHA in vcpkg-configuration.json, or any file under vcpkg-overlay-ports/ changes — otherwise the wheel build either misses the cache (slow) or fails on a missing port. The manylinux repo's Build workflow takes an osrmRef input for that purpose; see its README.
For local source builds outside the manylinux image, install vcpkg yourself, point CMake at its toolchain, and use the release-only triplet to match the cache:
git clone https://github.com/microsoft/vcpkg
+./vcpkg/bootstrap-vcpkg.sh
+export VCPKG_ROOT=$PWD/vcpkg
+export CMAKE_ARGS="-DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-linux-release"Install OSRM's C++ dependencies via Homebrew (the same set the cibuildwheel macOS before-all uses; all ship CMake config files so the find_package(... CONFIG REQUIRED) calls in CMakeLists.txt resolve without a toolchain file):
brew install lua tbb boost@1.90 fmt rapidjson sol2 flatbuffers \\
+ protozero libosmium
+brew link boost@1.90Windows uses vcpkg in manifest mode for OSRM's C++ dependencies. Clone and bootstrap it, then export VCPKG_ROOT:
git clone https://github.com/microsoft/vcpkg
+.\\vcpkg\\bootstrap-vcpkg.bat
+$env:VCPKG_ROOT = "$PWD\\vcpkg"Pass the toolchain to CMake at build time via CMAKE_ARGS (see below).
A standard pip install -e . works, but by default pip uses PEP 517 isolated builds — each invocation creates a temporary directory, compiles everything, then discards it. This means OSRM is recompiled from scratch every time.
Use --no-build-isolation to make scikit-build-core reuse the persistent build directory (build/{wheel_tag}/) across runs:
# Linux / macOS
+pip install -e . --no-build-isolation
+
+# Windows (PowerShell) — VCPKG_ROOT must be set, see Platform-specific
+# build requirements
+$env:CMAKE_ARGS = "-DCMAKE_TOOLCHAIN_FILE=$env:VCPKG_ROOT\\scripts\\buildsystems\\vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static-md"
+pip install -e . --no-build-isolationThe first run is slow (full OSRM compile). Subsequent runs only recompile changed binding files.
Keep config flags identical across runs
scikit-build-core hashes its configuration to detect changes. If the flags differ between runs, it wipes the build directory and starts from scratch.
Generator mismatch
CMake records the generator in CMakeCache.txt. If you ever see Does not match the generator used previously, delete the build directory and rebuild from scratch:
Remove-Item -Recurse -Force build/cp312-abi3-win_amd64After the editable install has compiled everything, produce a wheel without recompiling:
# Linux / macOS
+pip wheel . --no-build-isolation -w dist
+
+# Windows (PowerShell) — uses the same CMAKE_ARGS as the editable install above
+pip wheel . --no-build-isolation -w distCMake finds the existing artifacts in the build directory and skips recompilation. The wheel lands in dist/.
Locally built wheels link against system shared libraries and are tagged as linux_x86_64 (not manylinux). To make them portable or to inspect their dependencies, use the platform-specific repair tools:
Linux — auditwheel:
pip install auditwheel
+auditwheel show dist/*.whl # inspect shared library dependencies
+auditwheel repair -w dist dist/*.whl # bundle libs and retag as manylinuxmacOS — delocate:
pip install delocate
+delocate-listdeps dist/*.whl # inspect dependencies
+delocate-wheel -w dist dist/*.whl # bundle dylibsWindows — delvewheel:
pip install delvewheel
+delvewheel show dist/*.whl # inspect dependencies
+delvewheel repair -w dist dist/*.whlOn Windows, vcpkg's shared DLLs (tbb12.dll, hwloc.dll — TBB is shared even under the static-md triplet) live in build\\<wheel-tag>\\vcpkg_installed\\x64-windows-static-md\\bin\\. Pass that to delvewheel via --add-path so it can resolve and bundle them:
delvewheel repair --analyze-existing-exes \`
+ --add-path build\\cp312-abi3-win_amd64\\vcpkg_installed\\x64-windows-static-md\\bin \`
+ --add-dll hwloc.dll --no-mangle tbb12.dll --no-mangle hwloc.dll \`
+ -w dist dist\\*.whlTIP
cibuildwheel runs wheel repair automatically in CI. You only need these commands when building wheels locally for distribution.
On Linux and macOS, ccache is used automatically (pre-installed in the manylinux image; installed via Homebrew for macOS CI).
On Windows, scikit-build-core defaults to the Visual Studio generator, which does not support CMAKE_CXX_COMPILER_LAUNCHER. The build dir reuse from --no-build-isolation is the main speed optimisation for local Windows development.
Build the test data (requires the package to be installed so the osrm executables are available):
# Linux / macOS
+cd test/data && make
+
+# Windows
+cd test\\data && windows-build-test-data.batLoad the shared memory datastore:
python -m osrm datastore test/data/ch/monacoRun the test suite:
pytest test/python/cibuildwheel builds wheels inside isolated environments that closely match CI. Install it with:
pip install cibuildwheelBuild for the current platform:
cibuildwheel --platform linux # requires Docker on non-Linux hosts
+cibuildwheel --platform macos
+cibuildwheel --platform windowsWheels land in wheelhouse/.
Windows note: the toolchain wiring (CMAKE_TOOLCHAIN_FILE, VCPKG_TARGET_TRIPLET) lives in [tool.cibuildwheel.windows].environment in pyproject.toml, where $VCPKG_ROOT is expanded at build time from the host environment. Make sure VCPKG_ROOT is set in your shell before invoking cibuildwheel.
Linux note: the wheel build inside the manylinux container reads VCPKG_ROOT and VCPKG_DEFAULT_BINARY_CACHE from the image's ENV, so no toolchain forwarding is needed from the host. If you override CIBW_ENVIRONMENT_LINUX to mount a host ccache, remember it replaces (not merges with) [tool.cibuildwheel.linux].environment in pyproject.toml — re-include LD_LIBRARY_PATH and the CMAKE_ARGS line verbatim:
CIBW_CONTAINER_ENGINE="docker; create_args: --volume /tmp/ccache:/ccache" \\
+CIBW_ENVIRONMENT_LINUX='LD_LIBRARY_PATH=/usr/local/lib64:\${LD_LIBRARY_PATH} CCACHE_DIR=/ccache CMAKE_ARGS="-DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-linux-release"' \\
+cibuildwheel --platform linuxsrc/python/osrm/osrm_ext.pyi is auto-generated by nanobind_add_stub() at build time and committed to the repository so documentation tools can work without compiling the extension.
After changing C++ bindings, rebuild and commit the updated stub:
pip install -e . --no-build-isolation # regenerates the .pyi
+git add src/python/osrm/osrm_ext.pyiTo regenerate manually without a full rebuild:
pip install nanobind ruff
+python -m nanobind.stubgen -m osrm.osrm_ext -o src/python/osrm/osrm_ext.pyi
+ruff format src/python/osrm/osrm_ext.pyiReleases are driven by the monthly release workflow (.github/workflows/release-monthly.yml), not by pushing a tag by hand. The workflow bumps the version, creates the tag, drives CI, downloads the built wheels, and publishes to both PyPI and npm in one shot.
A cron on the 1st of each month at 08:00 UTC runs the workflow against master:
(YYYY-2000).M.patchlevel (e.g. 26.4.0).package.json + package-lock.json, commit, create annotated tag v<version>, push branch and tag.osrm-backend.yml on the tag. That run builds wheels + sdist via cibuildwheel and uploads them as wheels-* artifacts.success.publish job: download every wheels-* artifact into dist/, publish to PyPI via trusted publisher (OIDC), then npm publish.If PyPI fails, the npm publish still runs (the npm steps have if: \${{ !cancelled() }}), and the overall job is marked failed so the PyPI problem stays visible.
Trigger the workflow from the Actions UI or gh workflow run release-monthly.yml with optional inputs:
version_override — set the version explicitly (e.g. 26.4.1) instead of using the (YYYY-2000).M.patchlevel calculation.branch — release from a branch other than master.After the run finishes, check:
v<version> exists and the GitHub Release is published.pyproject.toml uses setuptools-scm with local_scheme = "no-local-version". On a tag checkout (e.g. v26.4.0), the Python version resolves cleanly to 26.4.0, matching the package.json version that release-monthly.yml committed when creating the tag.
We use a unified semver versioning scheme for monthly releases: (YYYY-2000).M.patchlevel
X.M.patchlevel where X = year - 2000, M = month (1-12, no leading zeros)26.4.0 represents April 2026, first releasev (e.g., v26.4.0)26.4.0)Previous scheme (ended 2025): Traditional semantic versioning (v6.0, v6.0.1, v6.0.2, etc.)
New scheme (started 2026): Monthly date-based versioning with automated releases
Git tags: vX.M.patchlevel where X = year - 2000, M = 1-12
npm packages: X.M.patchlevel (same as git tag without the v prefix)
Git tags and npm versions for the same release:
v26.4.0, npm 26.4.0v26.4.1, npm 26.4.1v26.5.0, npm 26.5.0v27.1.0, npm 27.1.0Pull request titles must follow Conventional Commits format with types: feat, fix, docs, style, refactor, perf, test, ci, chore, build. This is validated in CI and helps organize the commit history.
Breaking changes should be indicated with the ! suffix in the PR title (e.g., feat!: remove deprecated API) so they are called out in release notes.
master branch is for development and should always be greenReleases are created automatically every month on a scheduled basis:
(YYYY-2000).M.patchlevel with M = 1-12 (no leading zeros)package.json and package-lock.json versions are updatedv26.4.0)26.4.0 without v prefix)You can also trigger a release manually on any branch:
master)X.M.patchlevel with M = 1-12, e.g., 26.4.0)This is useful for:
When releasing (automated or manual):
X.M.patchlevel with month 1-12, e.g., 26.4.0)No additional manual steps are required. The automated workflow handles:
package.jsonFor non-automated releases, monitor:
If the SHM_LOCK_DIR environment variable is set, OSRM will use it as the directory for shared memory lock files instead of the system temporary directory. This is useful in containerized environments (Docker/Kubernetes) where the lock file directory should persist across container restarts when loading from shared memory.
If the SIGNAL_PARENT_WHEN_READY environment variable is set osrm-routed will send the USR1 signal to its parent when it will be running and waiting for requests. This could be used to upgrade osrm-routed to a new binary on the fly without any service downtime - no incoming requests will be lost.
If the DISABLE_ACCESS_LOGGING environment variable is set osrm-routed will not log any http requests to standard output. This can be useful in high traffic setup.
',7)])])}const m=t(i,[["render",o]]);export{u as __pageData,m as default}; diff --git a/docs/v26.6.1/assets/routed.md.BBY2HH7-.lean.js b/docs/v26.6.1/assets/routed.md.BBY2HH7-.lean.js new file mode 100644 index 0000000..b1c3939 --- /dev/null +++ b/docs/v26.6.1/assets/routed.md.BBY2HH7-.lean.js @@ -0,0 +1 @@ +import{_ as t,o as a,c as r,ag as n}from"./chunks/framework.D4c47gkQ.js";const u=JSON.parse('{"title":"","description":"","frontmatter":{},"headers":[],"relativePath":"routed.md","filePath":"routed.md"}'),i={name:"routed.md"};function o(s,e,l,d,h,_){return a(),r("div",null,[...e[0]||(e[0]=[n("",7)])])}const m=t(i,[["render",o]]);export{u as __pageData,m as default}; diff --git a/docs/v26.6.1/assets/style.CPXwcXL8.css b/docs/v26.6.1/assets/style.CPXwcXL8.css new file mode 100644 index 0000000..d51e101 --- /dev/null +++ b/docs/v26.6.1/assets/style.CPXwcXL8.css @@ -0,0 +1 @@ +@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/docs/v26.6.1/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/docs/v26.6.1/assets/inter-roman-cyrillic.C5lxZ8CY.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/docs/v26.6.1/assets/inter-roman-greek-ext.CqjqNYQ-.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/docs/v26.6.1/assets/inter-roman-greek.BBVDIX6e.woff2) format("woff2");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/docs/v26.6.1/assets/inter-roman-vietnamese.BjW4sHH5.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/docs/v26.6.1/assets/inter-roman-latin-ext.4ZJIpNVo.woff2) format("woff2");unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/docs/v26.6.1/assets/inter-roman-latin.Di8DUHzh.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/docs/v26.6.1/assets/inter-italic-cyrillic-ext.r48I6akx.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/docs/v26.6.1/assets/inter-italic-cyrillic.By2_1cv3.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/docs/v26.6.1/assets/inter-italic-greek-ext.1u6EdAuj.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/docs/v26.6.1/assets/inter-italic-greek.DJ8dCoTZ.woff2) format("woff2");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/docs/v26.6.1/assets/inter-italic-vietnamese.BSbpV94h.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/docs/v26.6.1/assets/inter-italic-latin-ext.CN1xVJS-.woff2) format("woff2");unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/docs/v26.6.1/assets/inter-italic-latin.C2AdPX0b.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Punctuation SC;font-weight:400;src:local("PingFang SC Regular"),local("Noto Sans CJK SC"),local("Microsoft YaHei");unicode-range:U+201C,U+201D,U+2018,U+2019,U+2E3A,U+2014,U+2013,U+2026,U+00B7,U+007E,U+002F}@font-face{font-family:Punctuation SC;font-weight:500;src:local("PingFang SC Medium"),local("Noto Sans CJK SC"),local("Microsoft YaHei");unicode-range:U+201C,U+201D,U+2018,U+2019,U+2E3A,U+2014,U+2013,U+2026,U+00B7,U+007E,U+002F}@font-face{font-family:Punctuation SC;font-weight:600;src:local("PingFang SC Semibold"),local("Noto Sans CJK SC Bold"),local("Microsoft YaHei Bold");unicode-range:U+201C,U+201D,U+2018,U+2019,U+2E3A,U+2014,U+2013,U+2026,U+00B7,U+007E,U+002F}@font-face{font-family:Punctuation SC;font-weight:700;src:local("PingFang SC Semibold"),local("Noto Sans CJK SC Bold"),local("Microsoft YaHei Bold");unicode-range:U+201C,U+201D,U+2018,U+2019,U+2E3A,U+2014,U+2013,U+2026,U+00B7,U+007E,U+002F}:root{--vp-c-white: #ffffff;--vp-c-black: #000000;--vp-c-neutral: var(--vp-c-black);--vp-c-neutral-inverse: var(--vp-c-white)}.dark{--vp-c-neutral: var(--vp-c-white);--vp-c-neutral-inverse: var(--vp-c-black)}:root{--vp-c-gray-1: #dddde3;--vp-c-gray-2: #e4e4e9;--vp-c-gray-3: #ebebef;--vp-c-gray-soft: rgba(142, 150, 170, .14);--vp-c-indigo-1: #3451b2;--vp-c-indigo-2: #3a5ccc;--vp-c-indigo-3: #5672cd;--vp-c-indigo-soft: rgba(100, 108, 255, .14);--vp-c-purple-1: #6f42c1;--vp-c-purple-2: #7e4cc9;--vp-c-purple-3: #8e5cd9;--vp-c-purple-soft: rgba(159, 122, 234, .14);--vp-c-green-1: #18794e;--vp-c-green-2: #299764;--vp-c-green-3: #30a46c;--vp-c-green-soft: rgba(16, 185, 129, .14);--vp-c-yellow-1: #915930;--vp-c-yellow-2: #946300;--vp-c-yellow-3: #9f6a00;--vp-c-yellow-soft: rgba(234, 179, 8, .14);--vp-c-red-1: #b8272c;--vp-c-red-2: #d5393e;--vp-c-red-3: #e0575b;--vp-c-red-soft: rgba(244, 63, 94, .14);--vp-c-sponsor: #db2777}.dark{--vp-c-gray-1: #515c67;--vp-c-gray-2: #414853;--vp-c-gray-3: #32363f;--vp-c-gray-soft: rgba(101, 117, 133, .16);--vp-c-indigo-1: #a8b1ff;--vp-c-indigo-2: #5c73e7;--vp-c-indigo-3: #3e63dd;--vp-c-indigo-soft: rgba(100, 108, 255, .16);--vp-c-purple-1: #c8abfa;--vp-c-purple-2: #a879e6;--vp-c-purple-3: #8e5cd9;--vp-c-purple-soft: rgba(159, 122, 234, .16);--vp-c-green-1: #3dd68c;--vp-c-green-2: #30a46c;--vp-c-green-3: #298459;--vp-c-green-soft: rgba(16, 185, 129, .16);--vp-c-yellow-1: #f9b44e;--vp-c-yellow-2: #da8b17;--vp-c-yellow-3: #a46a0a;--vp-c-yellow-soft: rgba(234, 179, 8, .16);--vp-c-red-1: #f66f81;--vp-c-red-2: #f14158;--vp-c-red-3: #b62a3c;--vp-c-red-soft: rgba(244, 63, 94, .16)}:root{--vp-c-bg: #ffffff;--vp-c-bg-alt: #f6f6f7;--vp-c-bg-elv: #ffffff;--vp-c-bg-soft: #f6f6f7}.dark{--vp-c-bg: #1b1b1f;--vp-c-bg-alt: #161618;--vp-c-bg-elv: #202127;--vp-c-bg-soft: #202127}:root{--vp-c-border: #c2c2c4;--vp-c-divider: #e2e2e3;--vp-c-gutter: #e2e2e3}.dark{--vp-c-border: #3c3f44;--vp-c-divider: #2e2e32;--vp-c-gutter: #000000}:root{--vp-c-text-1: #3c3c43;--vp-c-text-2: #67676c;--vp-c-text-3: #929295}.dark{--vp-c-text-1: #dfdfd6;--vp-c-text-2: #98989f;--vp-c-text-3: #6a6a71}:root{--vp-c-default-1: var(--vp-c-gray-1);--vp-c-default-2: var(--vp-c-gray-2);--vp-c-default-3: var(--vp-c-gray-3);--vp-c-default-soft: var(--vp-c-gray-soft);--vp-c-brand-1: var(--vp-c-indigo-1);--vp-c-brand-2: var(--vp-c-indigo-2);--vp-c-brand-3: var(--vp-c-indigo-3);--vp-c-brand-soft: var(--vp-c-indigo-soft);--vp-c-brand: var(--vp-c-brand-1);--vp-c-tip-1: var(--vp-c-brand-1);--vp-c-tip-2: var(--vp-c-brand-2);--vp-c-tip-3: var(--vp-c-brand-3);--vp-c-tip-soft: var(--vp-c-brand-soft);--vp-c-note-1: var(--vp-c-brand-1);--vp-c-note-2: var(--vp-c-brand-2);--vp-c-note-3: var(--vp-c-brand-3);--vp-c-note-soft: var(--vp-c-brand-soft);--vp-c-success-1: var(--vp-c-green-1);--vp-c-success-2: var(--vp-c-green-2);--vp-c-success-3: var(--vp-c-green-3);--vp-c-success-soft: var(--vp-c-green-soft);--vp-c-important-1: var(--vp-c-purple-1);--vp-c-important-2: var(--vp-c-purple-2);--vp-c-important-3: var(--vp-c-purple-3);--vp-c-important-soft: var(--vp-c-purple-soft);--vp-c-warning-1: var(--vp-c-yellow-1);--vp-c-warning-2: var(--vp-c-yellow-2);--vp-c-warning-3: var(--vp-c-yellow-3);--vp-c-warning-soft: var(--vp-c-yellow-soft);--vp-c-danger-1: var(--vp-c-red-1);--vp-c-danger-2: var(--vp-c-red-2);--vp-c-danger-3: var(--vp-c-red-3);--vp-c-danger-soft: var(--vp-c-red-soft);--vp-c-caution-1: var(--vp-c-red-1);--vp-c-caution-2: var(--vp-c-red-2);--vp-c-caution-3: var(--vp-c-red-3);--vp-c-caution-soft: var(--vp-c-red-soft)}:root{--vp-font-family-base: "Inter", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--vp-font-family-mono: ui-monospace, "Menlo", "Monaco", "Consolas", "Liberation Mono", "Courier New", monospace;font-optical-sizing:auto}:root:where(:lang(zh)){--vp-font-family-base: "Punctuation SC", "Inter", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"}:root{--vp-shadow-1: 0 1px 2px rgba(0, 0, 0, .04), 0 1px 2px rgba(0, 0, 0, .06);--vp-shadow-2: 0 3px 12px rgba(0, 0, 0, .07), 0 1px 4px rgba(0, 0, 0, .07);--vp-shadow-3: 0 12px 32px rgba(0, 0, 0, .1), 0 2px 6px rgba(0, 0, 0, .08);--vp-shadow-4: 0 14px 44px rgba(0, 0, 0, .12), 0 3px 9px rgba(0, 0, 0, .12);--vp-shadow-5: 0 18px 56px rgba(0, 0, 0, .16), 0 4px 12px rgba(0, 0, 0, .16)}:root{--vp-z-index-footer: 10;--vp-z-index-local-nav: 20;--vp-z-index-nav: 30;--vp-z-index-layout-top: 40;--vp-z-index-backdrop: 50;--vp-z-index-sidebar: 60}@media(min-width:960px){:root{--vp-z-index-sidebar: 25}}:root{--vp-layout-max-width: 1440px}:root{--vp-header-anchor-symbol: "#"}:root{--vp-code-line-height: 1.7;--vp-code-font-size: .875em;--vp-code-color: var(--vp-c-brand-1);--vp-code-link-color: var(--vp-c-brand-1);--vp-code-link-hover-color: var(--vp-c-brand-2);--vp-code-bg: var(--vp-c-default-soft);--vp-code-block-color: var(--vp-c-text-2);--vp-code-block-bg: var(--vp-c-bg-alt);--vp-code-block-divider-color: var(--vp-c-gutter);--vp-code-lang-color: var(--vp-c-text-3);--vp-code-line-highlight-color: var(--vp-c-default-soft);--vp-code-line-number-color: var(--vp-c-text-3);--vp-code-line-diff-add-color: var(--vp-c-success-soft);--vp-code-line-diff-add-symbol-color: var(--vp-c-success-1);--vp-code-line-diff-remove-color: var(--vp-c-danger-soft);--vp-code-line-diff-remove-symbol-color: var(--vp-c-danger-1);--vp-code-line-warning-color: var(--vp-c-warning-soft);--vp-code-line-error-color: var(--vp-c-danger-soft);--vp-code-copy-code-border-color: var(--vp-c-divider);--vp-code-copy-code-bg: var(--vp-c-bg-soft);--vp-code-copy-code-hover-border-color: var(--vp-c-divider);--vp-code-copy-code-hover-bg: var(--vp-c-bg);--vp-code-copy-code-active-text: var(--vp-c-text-2);--vp-code-copy-copied-text-content: "Copied";--vp-code-tab-divider: var(--vp-code-block-divider-color);--vp-code-tab-text-color: var(--vp-c-text-2);--vp-code-tab-bg: var(--vp-code-block-bg);--vp-code-tab-hover-text-color: var(--vp-c-text-1);--vp-code-tab-active-text-color: var(--vp-c-text-1);--vp-code-tab-active-bar-color: var(--vp-c-brand-1)}:lang(es),:lang(pt){--vp-code-copy-copied-text-content: "Copiado"}:lang(fa){--vp-code-copy-copied-text-content: "کپی شد"}:lang(ko){--vp-code-copy-copied-text-content: "복사됨"}:lang(ru){--vp-code-copy-copied-text-content: "Скопировано"}:lang(zh){--vp-code-copy-copied-text-content: "已复制"}:root{--vp-button-brand-border: transparent;--vp-button-brand-text: var(--vp-c-white);--vp-button-brand-bg: var(--vp-c-brand-3);--vp-button-brand-hover-border: transparent;--vp-button-brand-hover-text: var(--vp-c-white);--vp-button-brand-hover-bg: var(--vp-c-brand-2);--vp-button-brand-active-border: transparent;--vp-button-brand-active-text: var(--vp-c-white);--vp-button-brand-active-bg: var(--vp-c-brand-1);--vp-button-alt-border: transparent;--vp-button-alt-text: var(--vp-c-text-1);--vp-button-alt-bg: var(--vp-c-default-3);--vp-button-alt-hover-border: transparent;--vp-button-alt-hover-text: var(--vp-c-text-1);--vp-button-alt-hover-bg: var(--vp-c-default-2);--vp-button-alt-active-border: transparent;--vp-button-alt-active-text: var(--vp-c-text-1);--vp-button-alt-active-bg: var(--vp-c-default-1);--vp-button-sponsor-border: var(--vp-c-text-2);--vp-button-sponsor-text: var(--vp-c-text-2);--vp-button-sponsor-bg: transparent;--vp-button-sponsor-hover-border: var(--vp-c-sponsor);--vp-button-sponsor-hover-text: var(--vp-c-sponsor);--vp-button-sponsor-hover-bg: transparent;--vp-button-sponsor-active-border: var(--vp-c-sponsor);--vp-button-sponsor-active-text: var(--vp-c-sponsor);--vp-button-sponsor-active-bg: transparent}:root{--vp-custom-block-font-size: 14px;--vp-custom-block-code-font-size: 13px;--vp-custom-block-info-border: transparent;--vp-custom-block-info-text: var(--vp-c-text-1);--vp-custom-block-info-bg: var(--vp-c-default-soft);--vp-custom-block-info-code-bg: var(--vp-c-default-soft);--vp-custom-block-note-border: transparent;--vp-custom-block-note-text: var(--vp-c-text-1);--vp-custom-block-note-bg: var(--vp-c-default-soft);--vp-custom-block-note-code-bg: var(--vp-c-default-soft);--vp-custom-block-tip-border: transparent;--vp-custom-block-tip-text: var(--vp-c-text-1);--vp-custom-block-tip-bg: var(--vp-c-tip-soft);--vp-custom-block-tip-code-bg: var(--vp-c-tip-soft);--vp-custom-block-important-border: transparent;--vp-custom-block-important-text: var(--vp-c-text-1);--vp-custom-block-important-bg: var(--vp-c-important-soft);--vp-custom-block-important-code-bg: var(--vp-c-important-soft);--vp-custom-block-warning-border: transparent;--vp-custom-block-warning-text: var(--vp-c-text-1);--vp-custom-block-warning-bg: var(--vp-c-warning-soft);--vp-custom-block-warning-code-bg: var(--vp-c-warning-soft);--vp-custom-block-danger-border: transparent;--vp-custom-block-danger-text: var(--vp-c-text-1);--vp-custom-block-danger-bg: var(--vp-c-danger-soft);--vp-custom-block-danger-code-bg: var(--vp-c-danger-soft);--vp-custom-block-caution-border: transparent;--vp-custom-block-caution-text: var(--vp-c-text-1);--vp-custom-block-caution-bg: var(--vp-c-caution-soft);--vp-custom-block-caution-code-bg: var(--vp-c-caution-soft);--vp-custom-block-details-border: var(--vp-custom-block-info-border);--vp-custom-block-details-text: var(--vp-custom-block-info-text);--vp-custom-block-details-bg: var(--vp-custom-block-info-bg);--vp-custom-block-details-code-bg: var(--vp-custom-block-info-code-bg)}:root{--vp-input-border-color: var(--vp-c-border);--vp-input-bg-color: var(--vp-c-bg-alt);--vp-input-switch-bg-color: var(--vp-c-default-soft)}:root{--vp-nav-height: 64px;--vp-nav-bg-color: var(--vp-c-bg);--vp-nav-screen-bg-color: var(--vp-c-bg);--vp-nav-logo-height: 24px}.hide-nav{--vp-nav-height: 0px}.hide-nav .VPSidebar{--vp-nav-height: 22px}:root{--vp-local-nav-bg-color: var(--vp-c-bg)}:root{--vp-sidebar-width: 272px;--vp-sidebar-bg-color: var(--vp-c-bg-alt)}:root{--vp-backdrop-bg-color: rgba(0, 0, 0, .6)}:root{--vp-home-hero-name-color: var(--vp-c-brand-1);--vp-home-hero-name-background: transparent;--vp-home-hero-image-background-image: none;--vp-home-hero-image-filter: none}:root{--vp-badge-info-border: transparent;--vp-badge-info-text: var(--vp-c-text-2);--vp-badge-info-bg: var(--vp-c-default-soft);--vp-badge-tip-border: transparent;--vp-badge-tip-text: var(--vp-c-tip-1);--vp-badge-tip-bg: var(--vp-c-tip-soft);--vp-badge-warning-border: transparent;--vp-badge-warning-text: var(--vp-c-warning-1);--vp-badge-warning-bg: var(--vp-c-warning-soft);--vp-badge-danger-border: transparent;--vp-badge-danger-text: var(--vp-c-danger-1);--vp-badge-danger-bg: var(--vp-c-danger-soft)}:root{--vp-carbon-ads-text-color: var(--vp-c-text-1);--vp-carbon-ads-poweredby-color: var(--vp-c-text-2);--vp-carbon-ads-bg-color: var(--vp-c-bg-soft);--vp-carbon-ads-hover-text-color: var(--vp-c-brand-1);--vp-carbon-ads-hover-poweredby-color: var(--vp-c-text-1)}:root{--vp-local-search-bg: var(--vp-c-bg);--vp-local-search-result-bg: var(--vp-c-bg);--vp-local-search-result-border: var(--vp-c-divider);--vp-local-search-result-selected-bg: var(--vp-c-bg);--vp-local-search-result-selected-border: var(--vp-c-brand-1);--vp-local-search-highlight-bg: var(--vp-c-brand-1);--vp-local-search-highlight-text: var(--vp-c-neutral-inverse)}@media(prefers-reduced-motion:reduce){*,:before,:after{animation-delay:-1ms!important;animation-duration:1ms!important;animation-iteration-count:1!important;background-attachment:initial!important;scroll-behavior:auto!important;transition-duration:0s!important;transition-delay:0s!important}}*,:before,:after{box-sizing:border-box}html{line-height:1.4;font-size:16px;-webkit-text-size-adjust:100%}html.dark{color-scheme:dark}body{margin:0;width:100%;min-width:320px;min-height:100vh;line-height:24px;font-family:var(--vp-font-family-base);font-size:16px;font-weight:400;color:var(--vp-c-text-1);background-color:var(--vp-c-bg);font-synthesis:style;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}main{display:block}h1,h2,h3,h4,h5,h6{margin:0;line-height:24px;font-size:16px;font-weight:400}p{margin:0}strong,b{font-weight:600}a,area,button,[role=button],input,label,select,summary,textarea{touch-action:manipulation}a{color:inherit;text-decoration:inherit}ol,ul{list-style:none;margin:0;padding:0}blockquote{margin:0}pre,code,kbd,samp{font-family:var(--vp-font-family-mono)}img,svg,video,canvas,audio,iframe,embed,object{display:block}figure{margin:0}img,video{max-width:100%;height:auto}button,input,optgroup,select,textarea{border:0;padding:0;line-height:inherit;color:inherit}button{padding:0;font-family:inherit;background-color:transparent;background-image:none}button:enabled,[role=button]:enabled{cursor:pointer}button:focus,button:focus-visible{outline:1px dotted;outline:4px auto -webkit-focus-ring-color}button:focus:not(:focus-visible){outline:none!important}input:focus,textarea:focus,select:focus{outline:none}table{border-collapse:collapse}input{background-color:transparent}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:var(--vp-c-text-3)}input::-ms-input-placeholder,textarea::-ms-input-placeholder{color:var(--vp-c-text-3)}input::placeholder,textarea::placeholder{color:var(--vp-c-text-3)}input::-webkit-outer-spin-button,input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}input[type=number]{-moz-appearance:textfield}textarea{resize:vertical}select{-webkit-appearance:none}fieldset{margin:0;padding:0}h1,h2,h3,h4,h5,h6,li,p{overflow-wrap:break-word}vite-error-overlay{z-index:9999}mjx-container{overflow-x:auto}mjx-container>svg{display:inline-block;margin:auto}[class^=vpi-],[class*=" vpi-"],.vp-icon{width:1em;height:1em}[class^=vpi-].bg,[class*=" vpi-"].bg,.vp-icon.bg{background-size:100% 100%;background-color:transparent}[class^=vpi-]:not(.bg),[class*=" vpi-"]:not(.bg),.vp-icon:not(.bg){-webkit-mask:var(--icon) no-repeat;mask:var(--icon) no-repeat;-webkit-mask-size:100% 100%;mask-size:100% 100%;background-color:currentColor;color:inherit}.vpi-align-left{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M21 6H3M15 12H3M17 18H3'/%3E%3C/svg%3E")}.vpi-arrow-right,.vpi-arrow-down,.vpi-arrow-left,.vpi-arrow-up{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M5 12h14M12 5l7 7-7 7'/%3E%3C/svg%3E")}.vpi-chevron-right,.vpi-chevron-down,.vpi-chevron-left,.vpi-chevron-up{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='m9 18 6-6-6-6'/%3E%3C/svg%3E")}.vpi-chevron-down,.vpi-arrow-down{transform:rotate(90deg)}.vpi-chevron-left,.vpi-arrow-left{transform:rotate(180deg)}.vpi-chevron-up,.vpi-arrow-up{transform:rotate(-90deg)}.vpi-square-pen{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7'/%3E%3Cpath d='M18.375 2.625a2.121 2.121 0 1 1 3 3L12 15l-4 1 1-4Z'/%3E%3C/svg%3E")}.vpi-plus{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M5 12h14M12 5v14'/%3E%3C/svg%3E")}.vpi-sun{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='4'/%3E%3Cpath d='M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41'/%3E%3C/svg%3E")}.vpi-moon{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z'/%3E%3C/svg%3E")}.vpi-more-horizontal{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='1'/%3E%3Ccircle cx='19' cy='12' r='1'/%3E%3Ccircle cx='5' cy='12' r='1'/%3E%3C/svg%3E")}.vpi-languages{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='m5 8 6 6M4 14l6-6 2-3M2 5h12M7 2h1M22 22l-5-10-5 10M14 18h6'/%3E%3C/svg%3E")}.vpi-heart{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z'/%3E%3C/svg%3E")}.vpi-search{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cpath d='m21 21-4.3-4.3'/%3E%3C/svg%3E")}.vpi-layout-list{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Crect width='7' height='7' x='3' y='3' rx='1'/%3E%3Crect width='7' height='7' x='3' y='14' rx='1'/%3E%3Cpath d='M14 4h7M14 9h7M14 15h7M14 20h7'/%3E%3C/svg%3E")}.vpi-delete{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M20 5H9l-7 7 7 7h11a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2ZM18 9l-6 6M12 9l6 6'/%3E%3C/svg%3E")}.vpi-corner-down-left{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='m9 10-5 5 5 5'/%3E%3Cpath d='M20 4v7a4 4 0 0 1-4 4H4'/%3E%3C/svg%3E")}:root{--vp-icon-copy: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='rgba(128,128,128,1)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Crect width='8' height='4' x='8' y='2' rx='1' ry='1'/%3E%3Cpath d='M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2'/%3E%3C/svg%3E");--vp-icon-copied: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='rgba(128,128,128,1)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Crect width='8' height='4' x='8' y='2' rx='1' ry='1'/%3E%3Cpath d='M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2'/%3E%3Cpath d='m9 14 2 2 4-4'/%3E%3C/svg%3E")}.visually-hidden{position:absolute;width:1px;height:1px;white-space:nowrap;clip:rect(0 0 0 0);clip-path:inset(50%);overflow:hidden}.custom-block{border:1px solid transparent;border-radius:8px;padding:16px 16px 8px;line-height:24px;font-size:var(--vp-custom-block-font-size);color:var(--vp-c-text-2)}.custom-block.info{border-color:var(--vp-custom-block-info-border);color:var(--vp-custom-block-info-text);background-color:var(--vp-custom-block-info-bg)}.custom-block.info a,.custom-block.info code{color:var(--vp-c-brand-1)}.custom-block.info a:hover,.custom-block.info a:hover>code{color:var(--vp-c-brand-2)}.custom-block.info code{background-color:var(--vp-custom-block-info-code-bg)}.custom-block.note{border-color:var(--vp-custom-block-note-border);color:var(--vp-custom-block-note-text);background-color:var(--vp-custom-block-note-bg)}.custom-block.note a,.custom-block.note code{color:var(--vp-c-brand-1)}.custom-block.note a:hover,.custom-block.note a:hover>code{color:var(--vp-c-brand-2)}.custom-block.note code{background-color:var(--vp-custom-block-note-code-bg)}.custom-block.tip{border-color:var(--vp-custom-block-tip-border);color:var(--vp-custom-block-tip-text);background-color:var(--vp-custom-block-tip-bg)}.custom-block.tip a,.custom-block.tip code{color:var(--vp-c-tip-1)}.custom-block.tip a:hover,.custom-block.tip a:hover>code{color:var(--vp-c-tip-2)}.custom-block.tip code{background-color:var(--vp-custom-block-tip-code-bg)}.custom-block.important{border-color:var(--vp-custom-block-important-border);color:var(--vp-custom-block-important-text);background-color:var(--vp-custom-block-important-bg)}.custom-block.important a,.custom-block.important code{color:var(--vp-c-important-1)}.custom-block.important a:hover,.custom-block.important a:hover>code{color:var(--vp-c-important-2)}.custom-block.important code{background-color:var(--vp-custom-block-important-code-bg)}.custom-block.warning{border-color:var(--vp-custom-block-warning-border);color:var(--vp-custom-block-warning-text);background-color:var(--vp-custom-block-warning-bg)}.custom-block.warning a,.custom-block.warning code{color:var(--vp-c-warning-1)}.custom-block.warning a:hover,.custom-block.warning a:hover>code{color:var(--vp-c-warning-2)}.custom-block.warning code{background-color:var(--vp-custom-block-warning-code-bg)}.custom-block.danger{border-color:var(--vp-custom-block-danger-border);color:var(--vp-custom-block-danger-text);background-color:var(--vp-custom-block-danger-bg)}.custom-block.danger a,.custom-block.danger code{color:var(--vp-c-danger-1)}.custom-block.danger a:hover,.custom-block.danger a:hover>code{color:var(--vp-c-danger-2)}.custom-block.danger code{background-color:var(--vp-custom-block-danger-code-bg)}.custom-block.caution{border-color:var(--vp-custom-block-caution-border);color:var(--vp-custom-block-caution-text);background-color:var(--vp-custom-block-caution-bg)}.custom-block.caution a,.custom-block.caution code{color:var(--vp-c-caution-1)}.custom-block.caution a:hover,.custom-block.caution a:hover>code{color:var(--vp-c-caution-2)}.custom-block.caution code{background-color:var(--vp-custom-block-caution-code-bg)}.custom-block.details{border-color:var(--vp-custom-block-details-border);color:var(--vp-custom-block-details-text);background-color:var(--vp-custom-block-details-bg)}.custom-block.details a{color:var(--vp-c-brand-1)}.custom-block.details a:hover,.custom-block.details a:hover>code{color:var(--vp-c-brand-2)}.custom-block.details code{background-color:var(--vp-custom-block-details-code-bg)}.custom-block-title{font-weight:600}.custom-block p+p{margin:8px 0}.custom-block.details summary{margin:0 0 8px;font-weight:700;cursor:pointer;-webkit-user-select:none;user-select:none}.custom-block.details summary+p{margin:8px 0}.custom-block a{color:inherit;font-weight:600;text-decoration:underline;text-underline-offset:2px;transition:opacity .25s}.custom-block a:hover{opacity:.75}.custom-block code{font-size:var(--vp-custom-block-code-font-size)}.custom-block.custom-block th,.custom-block.custom-block blockquote>p{font-size:var(--vp-custom-block-font-size);color:inherit}.dark .vp-code span{color:var(--shiki-dark, inherit)}html:not(.dark) .vp-code span{color:var(--shiki-light, inherit)}.vp-code-group{margin-top:16px}.vp-code-group .tabs{position:relative;display:flex;margin-right:-24px;margin-left:-24px;padding:0 12px;background-color:var(--vp-code-tab-bg);overflow-x:auto;overflow-y:hidden;box-shadow:inset 0 -1px var(--vp-code-tab-divider)}@media(min-width:640px){.vp-code-group .tabs{margin-right:0;margin-left:0;border-radius:8px 8px 0 0}}.vp-code-group .tabs input{position:fixed;opacity:0;pointer-events:none}.vp-code-group .tabs label{position:relative;display:inline-block;border-bottom:1px solid transparent;padding:0 12px;line-height:48px;font-size:14px;font-weight:500;color:var(--vp-code-tab-text-color);white-space:nowrap;cursor:pointer;transition:color .25s}.vp-code-group .tabs label:after{position:absolute;right:8px;bottom:-1px;left:8px;z-index:1;height:2px;border-radius:2px;content:"";background-color:transparent;transition:background-color .25s}.vp-code-group label:hover{color:var(--vp-code-tab-hover-text-color)}.vp-code-group input:checked+label{color:var(--vp-code-tab-active-text-color)}.vp-code-group input:checked+label:after{background-color:var(--vp-code-tab-active-bar-color)}.vp-code-group div[class*=language-],.vp-block{display:none;margin-top:0!important;border-top-left-radius:0!important;border-top-right-radius:0!important}.vp-code-group div[class*=language-].active,.vp-block.active{display:block}.vp-block{padding:20px 24px}.vp-doc h1,.vp-doc h2,.vp-doc h3,.vp-doc h4,.vp-doc h5,.vp-doc h6{position:relative;font-weight:600;outline:none}.vp-doc h1{letter-spacing:-.02em;line-height:40px;font-size:28px}.vp-doc h2{margin:48px 0 16px;border-top:1px solid var(--vp-c-divider);padding-top:24px;letter-spacing:-.02em;line-height:32px;font-size:24px}.vp-doc h3{margin:32px 0 0;letter-spacing:-.01em;line-height:28px;font-size:20px}.vp-doc h4{margin:24px 0 0;letter-spacing:-.01em;line-height:24px;font-size:18px}.vp-doc .header-anchor{position:absolute;top:0;left:0;margin-left:-.87em;font-weight:500;-webkit-user-select:none;user-select:none;opacity:0;text-decoration:none;transition:color .25s,opacity .25s}.vp-doc .header-anchor:before{content:var(--vp-header-anchor-symbol)}.vp-doc h1:hover .header-anchor,.vp-doc h1 .header-anchor:focus,.vp-doc h2:hover .header-anchor,.vp-doc h2 .header-anchor:focus,.vp-doc h3:hover .header-anchor,.vp-doc h3 .header-anchor:focus,.vp-doc h4:hover .header-anchor,.vp-doc h4 .header-anchor:focus,.vp-doc h5:hover .header-anchor,.vp-doc h5 .header-anchor:focus,.vp-doc h6:hover .header-anchor,.vp-doc h6 .header-anchor:focus{opacity:1}@media(min-width:768px){.vp-doc h1{letter-spacing:-.02em;line-height:40px;font-size:32px}}.vp-doc h2 .header-anchor{top:24px}.vp-doc p,.vp-doc summary{margin:16px 0}.vp-doc p{line-height:28px}.vp-doc blockquote{margin:16px 0;border-left:2px solid var(--vp-c-divider);padding-left:16px;transition:border-color .5s;color:var(--vp-c-text-2)}.vp-doc blockquote>p{margin:0;font-size:16px;transition:color .5s}.vp-doc a{font-weight:500;color:var(--vp-c-brand-1);text-decoration:underline;text-underline-offset:2px;transition:color .25s,opacity .25s}.vp-doc a:hover{color:var(--vp-c-brand-2)}.vp-doc strong{font-weight:600}.vp-doc ul,.vp-doc ol{padding-left:1.25rem;margin:16px 0}.vp-doc ul{list-style:disc}.vp-doc ol{list-style:decimal}.vp-doc li+li{margin-top:8px}.vp-doc li>ol,.vp-doc li>ul{margin:8px 0 0}.vp-doc table{display:block;border-collapse:collapse;margin:20px 0;overflow-x:auto}.vp-doc tr{background-color:var(--vp-c-bg);border-top:1px solid var(--vp-c-divider);transition:background-color .5s}.vp-doc tr:nth-child(2n){background-color:var(--vp-c-bg-soft)}.vp-doc th,.vp-doc td{border:1px solid var(--vp-c-divider);padding:8px 16px}.vp-doc th{text-align:left;font-size:14px;font-weight:600;color:var(--vp-c-text-2);background-color:var(--vp-c-bg-soft)}.vp-doc td{font-size:14px}.vp-doc hr{margin:16px 0;border:none;border-top:1px solid var(--vp-c-divider)}.vp-doc .custom-block{margin:16px 0}.vp-doc .custom-block p{margin:8px 0;line-height:24px}.vp-doc .custom-block p:first-child{margin:0}.vp-doc .custom-block div[class*=language-]{margin:8px 0;border-radius:8px}.vp-doc .custom-block div[class*=language-] code{font-weight:400;background-color:transparent}.vp-doc .custom-block .vp-code-group .tabs{margin:0;border-radius:8px 8px 0 0}.vp-doc :not(pre,h1,h2,h3,h4,h5,h6)>code{font-size:var(--vp-code-font-size);color:var(--vp-code-color)}.vp-doc :not(pre)>code{border-radius:4px;padding:3px 6px;background-color:var(--vp-code-bg);transition:color .25s,background-color .5s}.vp-doc a>code{color:var(--vp-code-link-color)}.vp-doc a:hover>code{color:var(--vp-code-link-hover-color)}.vp-doc h1>code,.vp-doc h2>code,.vp-doc h3>code,.vp-doc h4>code{font-size:.9em}.vp-doc div[class*=language-],.vp-block{position:relative;margin:16px -24px;background-color:var(--vp-code-block-bg);overflow-x:auto;transition:background-color .5s}@media(min-width:640px){.vp-doc div[class*=language-],.vp-block{border-radius:8px;margin:16px 0}}@media(max-width:639px){.vp-doc li div[class*=language-]{border-radius:8px 0 0 8px}}.vp-doc div[class*=language-]+div[class*=language-],.vp-doc div[class$=-api]+div[class*=language-],.vp-doc div[class*=language-]+div[class$=-api]>div[class*=language-]{margin-top:-8px}.vp-doc [class*=language-] pre,.vp-doc [class*=language-] code{direction:ltr;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}.vp-doc [class*=language-] pre{position:relative;z-index:1;margin:0;padding:20px 0;background:transparent;overflow-x:auto}.vp-doc [class*=language-] code{display:block;padding:0 24px;width:fit-content;min-width:100%;line-height:var(--vp-code-line-height);font-size:var(--vp-code-font-size);color:var(--vp-code-block-color);transition:color .5s}.vp-doc [class*=language-] code .highlighted{background-color:var(--vp-code-line-highlight-color);transition:background-color .5s;margin:0 -24px;padding:0 24px;width:calc(100% + 48px);display:inline-block}.vp-doc [class*=language-] code .highlighted.error{background-color:var(--vp-code-line-error-color)}.vp-doc [class*=language-] code .highlighted.warning{background-color:var(--vp-code-line-warning-color)}.vp-doc [class*=language-] code .diff{transition:background-color .5s;margin:0 -24px;padding:0 24px;width:calc(100% + 48px);display:inline-block}.vp-doc [class*=language-] code .diff:before{position:absolute;left:10px}.vp-doc [class*=language-] .has-focused-lines .line:not(.has-focus){filter:blur(.095rem);opacity:.4;transition:filter .35s,opacity .35s}.vp-doc [class*=language-] .has-focused-lines .line:not(.has-focus){opacity:.7;transition:filter .35s,opacity .35s}.vp-doc [class*=language-]:hover .has-focused-lines .line:not(.has-focus){filter:blur(0);opacity:1}.vp-doc [class*=language-] code .diff.remove{background-color:var(--vp-code-line-diff-remove-color);opacity:.7}.vp-doc [class*=language-] code .diff.remove:before{content:"-";color:var(--vp-code-line-diff-remove-symbol-color)}.vp-doc [class*=language-] code .diff.add{background-color:var(--vp-code-line-diff-add-color)}.vp-doc [class*=language-] code .diff.add:before{content:"+";color:var(--vp-code-line-diff-add-symbol-color)}.vp-doc div[class*=language-].line-numbers-mode{padding-left:32px}.vp-doc .line-numbers-wrapper{position:absolute;top:0;bottom:0;left:0;z-index:3;border-right:1px solid var(--vp-code-block-divider-color);padding-top:20px;width:32px;text-align:center;font-family:var(--vp-font-family-mono);line-height:var(--vp-code-line-height);font-size:var(--vp-code-font-size);color:var(--vp-code-line-number-color);transition:border-color .5s,color .5s}.vp-doc [class*=language-]>button.copy{direction:ltr;position:absolute;top:12px;right:12px;z-index:3;border:1px solid var(--vp-code-copy-code-border-color);border-radius:4px;width:40px;height:40px;background-color:var(--vp-code-copy-code-bg);opacity:0;cursor:pointer;background-image:var(--vp-icon-copy);background-position:50%;background-size:20px;background-repeat:no-repeat;transition:border-color .25s,background-color .25s,opacity .25s}.vp-doc [class*=language-]:hover>button.copy,.vp-doc [class*=language-]>button.copy:focus{opacity:1}.vp-doc [class*=language-]>button.copy:hover,.vp-doc [class*=language-]>button.copy.copied{border-color:var(--vp-code-copy-code-hover-border-color);background-color:var(--vp-code-copy-code-hover-bg)}.vp-doc [class*=language-]>button.copy.copied,.vp-doc [class*=language-]>button.copy:hover.copied{border-radius:0 4px 4px 0;background-color:var(--vp-code-copy-code-hover-bg);background-image:var(--vp-icon-copied)}.vp-doc [class*=language-]>button.copy.copied:before,.vp-doc [class*=language-]>button.copy:hover.copied:before{position:relative;top:-1px;transform:translate(calc(-100% - 1px));display:flex;justify-content:center;align-items:center;border:1px solid var(--vp-code-copy-code-hover-border-color);border-right:0;border-radius:4px 0 0 4px;padding:0 10px;width:fit-content;height:40px;text-align:center;font-size:12px;font-weight:500;color:var(--vp-code-copy-code-active-text);background-color:var(--vp-code-copy-code-hover-bg);white-space:nowrap;content:var(--vp-code-copy-copied-text-content)}.vp-doc [class*=language-]>span.lang{position:absolute;top:2px;right:8px;z-index:2;font-size:12px;font-weight:500;-webkit-user-select:none;user-select:none;color:var(--vp-code-lang-color);transition:color .4s,opacity .4s}.vp-doc [class*=language-]:hover>button.copy+span.lang,.vp-doc [class*=language-]>button.copy:focus+span.lang{opacity:0}.vp-doc .VPTeamMembers{margin-top:24px}.vp-doc .VPTeamMembers.small.count-1 .container{margin:0!important;max-width:calc((100% - 24px)/2)!important}.vp-doc .VPTeamMembers.small.count-2 .container,.vp-doc .VPTeamMembers.small.count-3 .container{max-width:100%!important}.vp-doc .VPTeamMembers.medium.count-1 .container{margin:0!important;max-width:calc((100% - 24px)/2)!important}:is(.vp-external-link-icon,.vp-doc a[href*="://"],.vp-doc a[target=_blank]):not(:is(.no-icon,svg a,:has(img,svg))):after{display:inline-block;margin-top:-1px;margin-left:4px;width:11px;height:11px;background:currentColor;color:var(--vp-c-text-3);flex-shrink:0;--icon: url("data:image/svg+xml, %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' %3E%3Cpath d='M0 0h24v24H0V0z' fill='none' /%3E%3Cpath d='M9 5v2h6.59L4 18.59 5.41 20 17 8.41V15h2V5H9z' /%3E%3C/svg%3E");-webkit-mask-image:var(--icon);mask-image:var(--icon)}.vp-external-link-icon:after{content:""}.external-link-icon-enabled :is(.vp-doc a[href*="://"],.vp-doc a[target=_blank]):not(:is(.no-icon,svg a,:has(img,svg))):after{content:"";color:currentColor}.vp-sponsor{border-radius:16px;overflow:hidden}.vp-sponsor.aside{border-radius:12px}.vp-sponsor-section+.vp-sponsor-section{margin-top:4px}.vp-sponsor-tier{margin:0 0 4px!important;text-align:center;letter-spacing:1px!important;line-height:24px;width:100%;font-weight:600;color:var(--vp-c-text-2);background-color:var(--vp-c-bg-soft)}.vp-sponsor.normal .vp-sponsor-tier{padding:13px 0 11px;font-size:14px}.vp-sponsor.aside .vp-sponsor-tier{padding:9px 0 7px;font-size:12px}.vp-sponsor-grid+.vp-sponsor-tier{margin-top:4px}.vp-sponsor-grid{display:flex;flex-wrap:wrap;gap:4px}.vp-sponsor-grid.xmini .vp-sponsor-grid-link{height:64px}.vp-sponsor-grid.xmini .vp-sponsor-grid-image{max-width:64px;max-height:22px}.vp-sponsor-grid.mini .vp-sponsor-grid-link{height:72px}.vp-sponsor-grid.mini .vp-sponsor-grid-image{max-width:96px;max-height:24px}.vp-sponsor-grid.small .vp-sponsor-grid-link{height:96px}.vp-sponsor-grid.small .vp-sponsor-grid-image{max-width:96px;max-height:24px}.vp-sponsor-grid.medium .vp-sponsor-grid-link{height:112px}.vp-sponsor-grid.medium .vp-sponsor-grid-image{max-width:120px;max-height:36px}.vp-sponsor-grid.big .vp-sponsor-grid-link{height:184px}.vp-sponsor-grid.big .vp-sponsor-grid-image{max-width:192px;max-height:56px}.vp-sponsor-grid[data-vp-grid="2"] .vp-sponsor-grid-item{width:calc((100% - 4px)/2)}.vp-sponsor-grid[data-vp-grid="3"] .vp-sponsor-grid-item{width:calc((100% - 4px * 2) / 3)}.vp-sponsor-grid[data-vp-grid="4"] .vp-sponsor-grid-item{width:calc((100% - 12px)/4)}.vp-sponsor-grid[data-vp-grid="5"] .vp-sponsor-grid-item{width:calc((100% - 16px)/5)}.vp-sponsor-grid[data-vp-grid="6"] .vp-sponsor-grid-item{width:calc((100% - 4px * 5) / 6)}.vp-sponsor-grid-item{flex-shrink:0;width:100%;background-color:var(--vp-c-bg-soft);transition:background-color .25s}.vp-sponsor-grid-item:hover{background-color:var(--vp-c-default-soft)}.vp-sponsor-grid-item:hover .vp-sponsor-grid-image{filter:grayscale(0) invert(0)}.vp-sponsor-grid-item.empty:hover{background-color:var(--vp-c-bg-soft)}.dark .vp-sponsor-grid-item:hover{background-color:var(--vp-c-white)}.dark .vp-sponsor-grid-item.empty:hover{background-color:var(--vp-c-bg-soft)}.vp-sponsor-grid-link{display:flex}.vp-sponsor-grid-box{display:flex;justify-content:center;align-items:center;width:100%}.vp-sponsor-grid-image{max-width:100%;filter:grayscale(1);transition:filter .25s}.dark .vp-sponsor-grid-image{filter:grayscale(1) invert(1)}.VPBadge{display:inline-block;margin-left:2px;border:1px solid transparent;border-radius:12px;padding:0 10px;line-height:22px;font-size:12px;font-weight:500;transform:translateY(-2px)}.VPBadge.small{padding:0 6px;line-height:18px;font-size:10px;transform:translateY(-8px)}.VPDocFooter .VPBadge{display:none}.vp-doc h1>.VPBadge{margin-top:4px;vertical-align:top}.vp-doc h2>.VPBadge{margin-top:3px;padding:0 8px;vertical-align:top}.vp-doc h3>.VPBadge{vertical-align:middle}.vp-doc h4>.VPBadge,.vp-doc h5>.VPBadge,.vp-doc h6>.VPBadge{vertical-align:middle;line-height:18px}.VPBadge.info{border-color:var(--vp-badge-info-border);color:var(--vp-badge-info-text);background-color:var(--vp-badge-info-bg)}.VPBadge.tip{border-color:var(--vp-badge-tip-border);color:var(--vp-badge-tip-text);background-color:var(--vp-badge-tip-bg)}.VPBadge.warning{border-color:var(--vp-badge-warning-border);color:var(--vp-badge-warning-text);background-color:var(--vp-badge-warning-bg)}.VPBadge.danger{border-color:var(--vp-badge-danger-border);color:var(--vp-badge-danger-text);background-color:var(--vp-badge-danger-bg)}.VPBackdrop[data-v-c79a1216]{position:fixed;top:0;right:0;bottom:0;left:0;z-index:var(--vp-z-index-backdrop);background:var(--vp-backdrop-bg-color);transition:opacity .5s}.VPBackdrop.fade-enter-from[data-v-c79a1216],.VPBackdrop.fade-leave-to[data-v-c79a1216]{opacity:0}.VPBackdrop.fade-leave-active[data-v-c79a1216]{transition-duration:.25s}@media(min-width:1280px){.VPBackdrop[data-v-c79a1216]{display:none}}.NotFound[data-v-d6be1790]{padding:64px 24px 96px;text-align:center}@media(min-width:768px){.NotFound[data-v-d6be1790]{padding:96px 32px 168px}}.code[data-v-d6be1790]{line-height:64px;font-size:64px;font-weight:600}.title[data-v-d6be1790]{padding-top:12px;letter-spacing:2px;line-height:20px;font-size:20px;font-weight:700}.divider[data-v-d6be1790]{margin:24px auto 18px;width:64px;height:1px;background-color:var(--vp-c-divider)}.quote[data-v-d6be1790]{margin:0 auto;max-width:256px;font-size:14px;font-weight:500;color:var(--vp-c-text-2)}.action[data-v-d6be1790]{padding-top:20px}.link[data-v-d6be1790]{display:inline-block;border:1px solid var(--vp-c-brand-1);border-radius:16px;padding:3px 16px;font-size:14px;font-weight:500;color:var(--vp-c-brand-1);transition:border-color .25s,color .25s}.link[data-v-d6be1790]:hover{border-color:var(--vp-c-brand-2);color:var(--vp-c-brand-2)}.root[data-v-b933a997]{position:relative;z-index:1}.nested[data-v-b933a997]{padding-right:16px;padding-left:16px}.outline-link[data-v-b933a997]{display:block;line-height:32px;font-size:14px;font-weight:400;color:var(--vp-c-text-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:color .5s}.outline-link[data-v-b933a997]:hover,.outline-link.active[data-v-b933a997]{color:var(--vp-c-text-1);transition:color .25s}.outline-link.nested[data-v-b933a997]{padding-left:13px}.VPDocAsideOutline[data-v-a5bbad30]{display:none}.VPDocAsideOutline.has-outline[data-v-a5bbad30]{display:block}.content[data-v-a5bbad30]{position:relative;border-left:1px solid var(--vp-c-divider);padding-left:16px;font-size:13px;font-weight:500}.outline-marker[data-v-a5bbad30]{position:absolute;top:32px;left:-1px;z-index:0;opacity:0;width:2px;border-radius:2px;height:18px;background-color:var(--vp-c-brand-1);transition:top .25s cubic-bezier(0,1,.5,1),background-color .5s,opacity .25s}.outline-title[data-v-a5bbad30]{line-height:32px;font-size:14px;font-weight:600}.VPDocAside[data-v-3f215769]{display:flex;flex-direction:column;flex-grow:1}.spacer[data-v-3f215769]{flex-grow:1}.VPDocAside[data-v-3f215769] .spacer+.VPDocAsideSponsors,.VPDocAside[data-v-3f215769] .spacer+.VPDocAsideCarbonAds{margin-top:24px}.VPDocAside[data-v-3f215769] .VPDocAsideSponsors+.VPDocAsideCarbonAds{margin-top:16px}.VPLastUpdated[data-v-e98dd255]{line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-2)}@media(min-width:640px){.VPLastUpdated[data-v-e98dd255]{line-height:32px;font-size:14px;font-weight:500}}.VPDocFooter[data-v-e257564d]{margin-top:64px}.edit-info[data-v-e257564d]{padding-bottom:18px}@media(min-width:640px){.edit-info[data-v-e257564d]{display:flex;justify-content:space-between;align-items:center;padding-bottom:14px}}.edit-link-button[data-v-e257564d]{display:flex;align-items:center;border:0;line-height:32px;font-size:14px;font-weight:500;color:var(--vp-c-brand-1);transition:color .25s}.edit-link-button[data-v-e257564d]:hover{color:var(--vp-c-brand-2)}.edit-link-icon[data-v-e257564d]{margin-right:8px}.prev-next[data-v-e257564d]{border-top:1px solid var(--vp-c-divider);padding-top:24px;display:grid;grid-row-gap:8px}@media(min-width:640px){.prev-next[data-v-e257564d]{grid-template-columns:repeat(2,1fr);grid-column-gap:16px}}.pager-link[data-v-e257564d]{display:block;border:1px solid var(--vp-c-divider);border-radius:8px;padding:11px 16px 13px;width:100%;height:100%;transition:border-color .25s}.pager-link[data-v-e257564d]:hover{border-color:var(--vp-c-brand-1)}.pager-link.next[data-v-e257564d]{margin-left:auto;text-align:right}.desc[data-v-e257564d]{display:block;line-height:20px;font-size:12px;font-weight:500;color:var(--vp-c-text-2)}.title[data-v-e257564d]{display:block;line-height:20px;font-size:14px;font-weight:500;color:var(--vp-c-brand-1);transition:color .25s}.VPDoc[data-v-39a288b8]{padding:32px 24px 96px;width:100%}@media(min-width:768px){.VPDoc[data-v-39a288b8]{padding:48px 32px 128px}}@media(min-width:960px){.VPDoc[data-v-39a288b8]{padding:48px 32px 0}.VPDoc:not(.has-sidebar) .container[data-v-39a288b8]{display:flex;justify-content:center;max-width:992px}.VPDoc:not(.has-sidebar) .content[data-v-39a288b8]{max-width:752px}}@media(min-width:1280px){.VPDoc .container[data-v-39a288b8]{display:flex;justify-content:center}.VPDoc .aside[data-v-39a288b8]{display:block}}@media(min-width:1440px){.VPDoc:not(.has-sidebar) .content[data-v-39a288b8]{max-width:784px}.VPDoc:not(.has-sidebar) .container[data-v-39a288b8]{max-width:1104px}}.container[data-v-39a288b8]{margin:0 auto;width:100%}.aside[data-v-39a288b8]{position:relative;display:none;order:2;flex-grow:1;padding-left:32px;width:100%;max-width:256px}.left-aside[data-v-39a288b8]{order:1;padding-left:unset;padding-right:32px}.aside-container[data-v-39a288b8]{position:fixed;top:0;padding-top:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + var(--vp-doc-top-height, 0px) + 48px);width:224px;height:100vh;overflow-x:hidden;overflow-y:auto;scrollbar-width:none}.aside-container[data-v-39a288b8]::-webkit-scrollbar{display:none}.aside-curtain[data-v-39a288b8]{position:fixed;bottom:0;z-index:10;width:224px;height:32px;background:linear-gradient(transparent,var(--vp-c-bg) 70%)}.aside-content[data-v-39a288b8]{display:flex;flex-direction:column;min-height:calc(100vh - (var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 48px));padding-bottom:32px}.content[data-v-39a288b8]{position:relative;margin:0 auto;width:100%}@media(min-width:960px){.content[data-v-39a288b8]{padding:0 32px 128px}}@media(min-width:1280px){.content[data-v-39a288b8]{order:1;margin:0;min-width:640px}}.content-container[data-v-39a288b8]{margin:0 auto}.VPDoc.has-aside .content-container[data-v-39a288b8]{max-width:688px}.VPButton[data-v-fa7799d5]{display:inline-block;border:1px solid transparent;text-align:center;font-weight:600;white-space:nowrap;transition:color .25s,border-color .25s,background-color .25s}.VPButton[data-v-fa7799d5]:active{transition:color .1s,border-color .1s,background-color .1s}.VPButton.medium[data-v-fa7799d5]{border-radius:20px;padding:0 20px;line-height:38px;font-size:14px}.VPButton.big[data-v-fa7799d5]{border-radius:24px;padding:0 24px;line-height:46px;font-size:16px}.VPButton.brand[data-v-fa7799d5]{border-color:var(--vp-button-brand-border);color:var(--vp-button-brand-text);background-color:var(--vp-button-brand-bg)}.VPButton.brand[data-v-fa7799d5]:hover{border-color:var(--vp-button-brand-hover-border);color:var(--vp-button-brand-hover-text);background-color:var(--vp-button-brand-hover-bg)}.VPButton.brand[data-v-fa7799d5]:active{border-color:var(--vp-button-brand-active-border);color:var(--vp-button-brand-active-text);background-color:var(--vp-button-brand-active-bg)}.VPButton.alt[data-v-fa7799d5]{border-color:var(--vp-button-alt-border);color:var(--vp-button-alt-text);background-color:var(--vp-button-alt-bg)}.VPButton.alt[data-v-fa7799d5]:hover{border-color:var(--vp-button-alt-hover-border);color:var(--vp-button-alt-hover-text);background-color:var(--vp-button-alt-hover-bg)}.VPButton.alt[data-v-fa7799d5]:active{border-color:var(--vp-button-alt-active-border);color:var(--vp-button-alt-active-text);background-color:var(--vp-button-alt-active-bg)}.VPButton.sponsor[data-v-fa7799d5]{border-color:var(--vp-button-sponsor-border);color:var(--vp-button-sponsor-text);background-color:var(--vp-button-sponsor-bg)}.VPButton.sponsor[data-v-fa7799d5]:hover{border-color:var(--vp-button-sponsor-hover-border);color:var(--vp-button-sponsor-hover-text);background-color:var(--vp-button-sponsor-hover-bg)}.VPButton.sponsor[data-v-fa7799d5]:active{border-color:var(--vp-button-sponsor-active-border);color:var(--vp-button-sponsor-active-text);background-color:var(--vp-button-sponsor-active-bg)}html:not(.dark) .VPImage.dark[data-v-8426fc1a]{display:none}.dark .VPImage.light[data-v-8426fc1a]{display:none}.VPHero[data-v-4f9c455b]{margin-top:calc((var(--vp-nav-height) + var(--vp-layout-top-height, 0px)) * -1);padding:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 48px) 24px 48px}@media(min-width:640px){.VPHero[data-v-4f9c455b]{padding:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 80px) 48px 64px}}@media(min-width:960px){.VPHero[data-v-4f9c455b]{padding:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 80px) 64px 64px}}.container[data-v-4f9c455b]{display:flex;flex-direction:column;margin:0 auto;max-width:1152px}@media(min-width:960px){.container[data-v-4f9c455b]{flex-direction:row}}.main[data-v-4f9c455b]{position:relative;z-index:10;order:2;flex-grow:1;flex-shrink:0}.VPHero.has-image .container[data-v-4f9c455b]{text-align:center}@media(min-width:960px){.VPHero.has-image .container[data-v-4f9c455b]{text-align:left}}@media(min-width:960px){.main[data-v-4f9c455b]{order:1;width:calc((100% / 3) * 2)}.VPHero.has-image .main[data-v-4f9c455b]{max-width:592px}}.heading[data-v-4f9c455b]{display:flex;flex-direction:column}.name[data-v-4f9c455b],.text[data-v-4f9c455b]{width:fit-content;max-width:392px;letter-spacing:-.4px;line-height:40px;font-size:32px;font-weight:700;white-space:pre-wrap}.VPHero.has-image .name[data-v-4f9c455b],.VPHero.has-image .text[data-v-4f9c455b]{margin:0 auto}.name[data-v-4f9c455b]{color:var(--vp-home-hero-name-color)}.clip[data-v-4f9c455b]{background:var(--vp-home-hero-name-background);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:var(--vp-home-hero-name-color)}@media(min-width:640px){.name[data-v-4f9c455b],.text[data-v-4f9c455b]{max-width:576px;line-height:56px;font-size:48px}}@media(min-width:960px){.name[data-v-4f9c455b],.text[data-v-4f9c455b]{line-height:64px;font-size:56px}.VPHero.has-image .name[data-v-4f9c455b],.VPHero.has-image .text[data-v-4f9c455b]{margin:0}}.tagline[data-v-4f9c455b]{padding-top:8px;max-width:392px;line-height:28px;font-size:18px;font-weight:500;white-space:pre-wrap;color:var(--vp-c-text-2)}.VPHero.has-image .tagline[data-v-4f9c455b]{margin:0 auto}@media(min-width:640px){.tagline[data-v-4f9c455b]{padding-top:12px;max-width:576px;line-height:32px;font-size:20px}}@media(min-width:960px){.tagline[data-v-4f9c455b]{line-height:36px;font-size:24px}.VPHero.has-image .tagline[data-v-4f9c455b]{margin:0}}.actions[data-v-4f9c455b]{display:flex;flex-wrap:wrap;margin:-6px;padding-top:24px}.VPHero.has-image .actions[data-v-4f9c455b]{justify-content:center}@media(min-width:640px){.actions[data-v-4f9c455b]{padding-top:32px}}@media(min-width:960px){.VPHero.has-image .actions[data-v-4f9c455b]{justify-content:flex-start}}.action[data-v-4f9c455b]{flex-shrink:0;padding:6px}.image[data-v-4f9c455b]{order:1;margin:-76px -24px -48px}@media(min-width:640px){.image[data-v-4f9c455b]{margin:-108px -24px -48px}}@media(min-width:960px){.image[data-v-4f9c455b]{flex-grow:1;order:2;margin:0;min-height:100%}}.image-container[data-v-4f9c455b]{position:relative;margin:0 auto;width:320px;height:320px}@media(min-width:640px){.image-container[data-v-4f9c455b]{width:392px;height:392px}}@media(min-width:960px){.image-container[data-v-4f9c455b]{display:flex;justify-content:center;align-items:center;width:100%;height:100%;transform:translate(-32px,-32px)}}.image-bg[data-v-4f9c455b]{position:absolute;top:50%;left:50%;border-radius:50%;width:192px;height:192px;background-image:var(--vp-home-hero-image-background-image);filter:var(--vp-home-hero-image-filter);transform:translate(-50%,-50%)}@media(min-width:640px){.image-bg[data-v-4f9c455b]{width:256px;height:256px}}@media(min-width:960px){.image-bg[data-v-4f9c455b]{width:320px;height:320px}}[data-v-4f9c455b] .image-src{position:absolute;top:50%;left:50%;max-width:192px;max-height:192px;transform:translate(-50%,-50%)}@media(min-width:640px){[data-v-4f9c455b] .image-src{max-width:256px;max-height:256px}}@media(min-width:960px){[data-v-4f9c455b] .image-src{max-width:320px;max-height:320px}}.VPFeature[data-v-a3976bdc]{display:block;border:1px solid var(--vp-c-bg-soft);border-radius:12px;height:100%;background-color:var(--vp-c-bg-soft);transition:border-color .25s,background-color .25s}.VPFeature.link[data-v-a3976bdc]:hover{border-color:var(--vp-c-brand-1)}.box[data-v-a3976bdc]{display:flex;flex-direction:column;padding:24px;height:100%}.box[data-v-a3976bdc]>.VPImage{margin-bottom:20px}.icon[data-v-a3976bdc]{display:flex;justify-content:center;align-items:center;margin-bottom:20px;border-radius:6px;background-color:var(--vp-c-default-soft);width:48px;height:48px;font-size:24px;transition:background-color .25s}.title[data-v-a3976bdc]{line-height:24px;font-size:16px;font-weight:600}.details[data-v-a3976bdc]{flex-grow:1;padding-top:8px;line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-2)}.link-text[data-v-a3976bdc]{padding-top:8px}.link-text-value[data-v-a3976bdc]{display:flex;align-items:center;font-size:14px;font-weight:500;color:var(--vp-c-brand-1)}.link-text-icon[data-v-a3976bdc]{margin-left:6px}.VPFeatures[data-v-a6181336]{position:relative;padding:0 24px}@media(min-width:640px){.VPFeatures[data-v-a6181336]{padding:0 48px}}@media(min-width:960px){.VPFeatures[data-v-a6181336]{padding:0 64px}}.container[data-v-a6181336]{margin:0 auto;max-width:1152px}.items[data-v-a6181336]{display:flex;flex-wrap:wrap;margin:-8px}.item[data-v-a6181336]{padding:8px;width:100%}@media(min-width:640px){.item.grid-2[data-v-a6181336],.item.grid-4[data-v-a6181336],.item.grid-6[data-v-a6181336]{width:50%}}@media(min-width:768px){.item.grid-2[data-v-a6181336],.item.grid-4[data-v-a6181336]{width:50%}.item.grid-3[data-v-a6181336],.item.grid-6[data-v-a6181336]{width:calc(100% / 3)}}@media(min-width:960px){.item.grid-4[data-v-a6181336]{width:25%}}.container[data-v-8e2d4988]{margin:auto;width:100%;max-width:1280px;padding:0 24px}@media(min-width:640px){.container[data-v-8e2d4988]{padding:0 48px}}@media(min-width:960px){.container[data-v-8e2d4988]{width:100%;padding:0 64px}}.vp-doc[data-v-8e2d4988] .VPHomeSponsors,.vp-doc[data-v-8e2d4988] .VPTeamPage{margin-left:var(--vp-offset, calc(50% - 50vw) );margin-right:var(--vp-offset, calc(50% - 50vw) )}.vp-doc[data-v-8e2d4988] .VPHomeSponsors h2{border-top:none;letter-spacing:normal}.vp-doc[data-v-8e2d4988] .VPHomeSponsors a,.vp-doc[data-v-8e2d4988] .VPTeamPage a{text-decoration:none}.VPHome[data-v-8b561e3d]{margin-bottom:96px}@media(min-width:768px){.VPHome[data-v-8b561e3d]{margin-bottom:128px}}.VPContent[data-v-1428d186]{flex-grow:1;flex-shrink:0;margin:var(--vp-layout-top-height, 0px) auto 0;width:100%}.VPContent.is-home[data-v-1428d186]{width:100%;max-width:100%}.VPContent.has-sidebar[data-v-1428d186]{margin:0}@media(min-width:960px){.VPContent[data-v-1428d186]{padding-top:var(--vp-nav-height)}.VPContent.has-sidebar[data-v-1428d186]{margin:var(--vp-layout-top-height, 0px) 0 0;padding-left:var(--vp-sidebar-width)}}@media(min-width:1440px){.VPContent.has-sidebar[data-v-1428d186]{padding-right:calc((100vw - var(--vp-layout-max-width)) / 2);padding-left:calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width))}}.VPFooter[data-v-e315a0ad]{position:relative;z-index:var(--vp-z-index-footer);border-top:1px solid var(--vp-c-gutter);padding:32px 24px;background-color:var(--vp-c-bg)}.VPFooter.has-sidebar[data-v-e315a0ad]{display:none}.VPFooter[data-v-e315a0ad] a{text-decoration-line:underline;text-underline-offset:2px;transition:color .25s}.VPFooter[data-v-e315a0ad] a:hover{color:var(--vp-c-text-1)}@media(min-width:768px){.VPFooter[data-v-e315a0ad]{padding:32px}}.container[data-v-e315a0ad]{margin:0 auto;max-width:var(--vp-layout-max-width);text-align:center}.message[data-v-e315a0ad],.copyright[data-v-e315a0ad]{line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-2)}.VPLocalNavOutlineDropdown[data-v-8a42e2b4]{padding:12px 20px 11px}@media(min-width:960px){.VPLocalNavOutlineDropdown[data-v-8a42e2b4]{padding:12px 36px 11px}}.VPLocalNavOutlineDropdown button[data-v-8a42e2b4]{display:block;font-size:12px;font-weight:500;line-height:24px;color:var(--vp-c-text-2);transition:color .5s;position:relative}.VPLocalNavOutlineDropdown button[data-v-8a42e2b4]:hover{color:var(--vp-c-text-1);transition:color .25s}.VPLocalNavOutlineDropdown button.open[data-v-8a42e2b4]{color:var(--vp-c-text-1)}.icon[data-v-8a42e2b4]{display:inline-block;vertical-align:middle;margin-left:2px;font-size:14px;transform:rotate(0);transition:transform .25s}@media(min-width:960px){.VPLocalNavOutlineDropdown button[data-v-8a42e2b4]{font-size:14px}.icon[data-v-8a42e2b4]{font-size:16px}}.open>.icon[data-v-8a42e2b4]{transform:rotate(90deg)}.items[data-v-8a42e2b4]{position:absolute;top:40px;right:16px;left:16px;display:grid;gap:1px;border:1px solid var(--vp-c-border);border-radius:8px;background-color:var(--vp-c-gutter);max-height:calc(var(--vp-vh, 100vh) - 86px);overflow:hidden auto;box-shadow:var(--vp-shadow-3)}@media(min-width:960px){.items[data-v-8a42e2b4]{right:auto;left:calc(var(--vp-sidebar-width) + 32px);width:320px}}.header[data-v-8a42e2b4]{background-color:var(--vp-c-bg-soft)}.top-link[data-v-8a42e2b4]{display:block;padding:0 16px;line-height:48px;font-size:14px;font-weight:500;color:var(--vp-c-brand-1)}.outline[data-v-8a42e2b4]{padding:8px 0;background-color:var(--vp-c-bg-soft)}.flyout-enter-active[data-v-8a42e2b4]{transition:all .2s ease-out}.flyout-leave-active[data-v-8a42e2b4]{transition:all .15s ease-in}.flyout-enter-from[data-v-8a42e2b4],.flyout-leave-to[data-v-8a42e2b4]{opacity:0;transform:translateY(-16px)}.VPLocalNav[data-v-a6f0e41e]{position:sticky;top:0;left:0;z-index:var(--vp-z-index-local-nav);border-bottom:1px solid var(--vp-c-gutter);padding-top:var(--vp-layout-top-height, 0px);width:100%;background-color:var(--vp-local-nav-bg-color)}.VPLocalNav.fixed[data-v-a6f0e41e]{position:fixed}@media(min-width:960px){.VPLocalNav[data-v-a6f0e41e]{top:var(--vp-nav-height)}.VPLocalNav.has-sidebar[data-v-a6f0e41e]{padding-left:var(--vp-sidebar-width)}.VPLocalNav.empty[data-v-a6f0e41e]{display:none}}@media(min-width:1280px){.VPLocalNav[data-v-a6f0e41e]{display:none}}@media(min-width:1440px){.VPLocalNav.has-sidebar[data-v-a6f0e41e]{padding-left:calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width))}}.container[data-v-a6f0e41e]{display:flex;justify-content:space-between;align-items:center}.menu[data-v-a6f0e41e]{display:flex;align-items:center;padding:12px 24px 11px;line-height:24px;font-size:12px;font-weight:500;color:var(--vp-c-text-2);transition:color .5s}.menu[data-v-a6f0e41e]:hover{color:var(--vp-c-text-1);transition:color .25s}@media(min-width:768px){.menu[data-v-a6f0e41e]{padding:0 32px}}@media(min-width:960px){.menu[data-v-a6f0e41e]{display:none}}.menu-icon[data-v-a6f0e41e]{margin-right:8px;font-size:14px}.VPOutlineDropdown[data-v-a6f0e41e]{padding:12px 24px 11px}@media(min-width:768px){.VPOutlineDropdown[data-v-a6f0e41e]{padding:12px 32px 11px}}.VPSwitch[data-v-1d5665e3]{position:relative;border-radius:11px;display:block;width:40px;height:22px;flex-shrink:0;border:1px solid var(--vp-input-border-color);background-color:var(--vp-input-switch-bg-color);transition:border-color .25s!important}.VPSwitch[data-v-1d5665e3]:hover{border-color:var(--vp-c-brand-1)}.check[data-v-1d5665e3]{position:absolute;top:1px;left:1px;width:18px;height:18px;border-radius:50%;background-color:var(--vp-c-neutral-inverse);box-shadow:var(--vp-shadow-1);transition:transform .25s!important}.icon[data-v-1d5665e3]{position:relative;display:block;width:18px;height:18px;border-radius:50%;overflow:hidden}.icon[data-v-1d5665e3] [class^=vpi-]{position:absolute;top:3px;left:3px;width:12px;height:12px;color:var(--vp-c-text-2)}.dark .icon[data-v-1d5665e3] [class^=vpi-]{color:var(--vp-c-text-1);transition:opacity .25s!important}.sun[data-v-5337faa4]{opacity:1}.moon[data-v-5337faa4],.dark .sun[data-v-5337faa4]{opacity:0}.dark .moon[data-v-5337faa4]{opacity:1}.dark .VPSwitchAppearance[data-v-5337faa4] .check{transform:translate(18px)}.VPNavBarAppearance[data-v-6c893767]{display:none}@media(min-width:1280px){.VPNavBarAppearance[data-v-6c893767]{display:flex;align-items:center}}.VPMenuGroup+.VPMenuLink[data-v-35975db6]{margin:12px -12px 0;border-top:1px solid var(--vp-c-divider);padding:12px 12px 0}.link[data-v-35975db6]{display:block;border-radius:6px;padding:0 12px;line-height:32px;font-size:14px;font-weight:500;color:var(--vp-c-text-1);white-space:nowrap;transition:background-color .25s,color .25s}.link[data-v-35975db6]:hover{color:var(--vp-c-brand-1);background-color:var(--vp-c-default-soft)}.link.active[data-v-35975db6]{color:var(--vp-c-brand-1)}.VPMenuGroup[data-v-69e747b5]{margin:12px -12px 0;border-top:1px solid var(--vp-c-divider);padding:12px 12px 0}.VPMenuGroup[data-v-69e747b5]:first-child{margin-top:0;border-top:0;padding-top:0}.VPMenuGroup+.VPMenuGroup[data-v-69e747b5]{margin-top:12px;border-top:1px solid var(--vp-c-divider)}.title[data-v-69e747b5]{padding:0 12px;line-height:32px;font-size:14px;font-weight:600;color:var(--vp-c-text-2);white-space:nowrap;transition:color .25s}.VPMenu[data-v-b98bc113]{border-radius:12px;padding:12px;min-width:128px;border:1px solid var(--vp-c-divider);background-color:var(--vp-c-bg-elv);box-shadow:var(--vp-shadow-3);transition:background-color .5s;max-height:calc(100vh - var(--vp-nav-height));overflow-y:auto}.VPMenu[data-v-b98bc113] .group{margin:0 -12px;padding:0 12px 12px}.VPMenu[data-v-b98bc113] .group+.group{border-top:1px solid var(--vp-c-divider);padding:11px 12px 12px}.VPMenu[data-v-b98bc113] .group:last-child{padding-bottom:0}.VPMenu[data-v-b98bc113] .group+.item{border-top:1px solid var(--vp-c-divider);padding:11px 16px 0}.VPMenu[data-v-b98bc113] .item{padding:0 16px;white-space:nowrap}.VPMenu[data-v-b98bc113] .label{flex-grow:1;line-height:28px;font-size:12px;font-weight:500;color:var(--vp-c-text-2);transition:color .5s}.VPMenu[data-v-b98bc113] .action{padding-left:24px}.VPFlyout[data-v-cf11d7a2]{position:relative}.VPFlyout[data-v-cf11d7a2]:hover{color:var(--vp-c-brand-1);transition:color .25s}.VPFlyout:hover .text[data-v-cf11d7a2]{color:var(--vp-c-text-2)}.VPFlyout:hover .icon[data-v-cf11d7a2]{fill:var(--vp-c-text-2)}.VPFlyout.active .text[data-v-cf11d7a2]{color:var(--vp-c-brand-1)}.VPFlyout.active:hover .text[data-v-cf11d7a2]{color:var(--vp-c-brand-2)}.button[aria-expanded=false]+.menu[data-v-cf11d7a2]{opacity:0;visibility:hidden;transform:translateY(0)}.VPFlyout:hover .menu[data-v-cf11d7a2],.button[aria-expanded=true]+.menu[data-v-cf11d7a2]{opacity:1;visibility:visible;transform:translateY(0)}.button[data-v-cf11d7a2]{display:flex;align-items:center;padding:0 12px;height:var(--vp-nav-height);color:var(--vp-c-text-1);transition:color .5s}.text[data-v-cf11d7a2]{display:flex;align-items:center;line-height:var(--vp-nav-height);font-size:14px;font-weight:500;color:var(--vp-c-text-1);transition:color .25s}.option-icon[data-v-cf11d7a2]{margin-right:0;font-size:16px}.text-icon[data-v-cf11d7a2]{margin-left:4px;font-size:14px}.icon[data-v-cf11d7a2]{font-size:20px;transition:fill .25s}.menu[data-v-cf11d7a2]{position:absolute;top:calc(var(--vp-nav-height) / 2 + 20px);right:0;opacity:0;visibility:hidden;transition:opacity .25s,visibility .25s,transform .25s}.VPSocialLink[data-v-bd121fe5]{display:flex;justify-content:center;align-items:center;width:36px;height:36px;color:var(--vp-c-text-2);transition:color .5s}.VPSocialLink[data-v-bd121fe5]:hover{color:var(--vp-c-text-1);transition:color .25s}.VPSocialLink[data-v-bd121fe5]>svg,.VPSocialLink[data-v-bd121fe5]>[class^=vpi-social-]{width:20px;height:20px;fill:currentColor}.VPSocialLinks[data-v-7bc22406]{display:flex;justify-content:center}.VPNavBarExtra[data-v-bb2aa2f0]{display:none;margin-right:-12px}@media(min-width:768px){.VPNavBarExtra[data-v-bb2aa2f0]{display:block}}@media(min-width:1280px){.VPNavBarExtra[data-v-bb2aa2f0]{display:none}}.trans-title[data-v-bb2aa2f0]{padding:0 24px 0 12px;line-height:32px;font-size:14px;font-weight:700;color:var(--vp-c-text-1)}.item.appearance[data-v-bb2aa2f0],.item.social-links[data-v-bb2aa2f0]{display:flex;align-items:center;padding:0 12px}.item.appearance[data-v-bb2aa2f0]{min-width:176px}.appearance-action[data-v-bb2aa2f0]{margin-right:-2px}.social-links-list[data-v-bb2aa2f0]{margin:-4px -8px}.VPNavBarHamburger[data-v-e5dd9c1c]{display:flex;justify-content:center;align-items:center;width:48px;height:var(--vp-nav-height)}@media(min-width:768px){.VPNavBarHamburger[data-v-e5dd9c1c]{display:none}}.container[data-v-e5dd9c1c]{position:relative;width:16px;height:14px;overflow:hidden}.VPNavBarHamburger:hover .top[data-v-e5dd9c1c]{top:0;left:0;transform:translate(4px)}.VPNavBarHamburger:hover .middle[data-v-e5dd9c1c]{top:6px;left:0;transform:translate(0)}.VPNavBarHamburger:hover .bottom[data-v-e5dd9c1c]{top:12px;left:0;transform:translate(8px)}.VPNavBarHamburger.active .top[data-v-e5dd9c1c]{top:6px;transform:translate(0) rotate(225deg)}.VPNavBarHamburger.active .middle[data-v-e5dd9c1c]{top:6px;transform:translate(16px)}.VPNavBarHamburger.active .bottom[data-v-e5dd9c1c]{top:6px;transform:translate(0) rotate(135deg)}.VPNavBarHamburger.active:hover .top[data-v-e5dd9c1c],.VPNavBarHamburger.active:hover .middle[data-v-e5dd9c1c],.VPNavBarHamburger.active:hover .bottom[data-v-e5dd9c1c]{background-color:var(--vp-c-text-2);transition:top .25s,background-color .25s,transform .25s}.top[data-v-e5dd9c1c],.middle[data-v-e5dd9c1c],.bottom[data-v-e5dd9c1c]{position:absolute;width:16px;height:2px;background-color:var(--vp-c-text-1);transition:top .25s,background-color .5s,transform .25s}.top[data-v-e5dd9c1c]{top:0;left:0;transform:translate(0)}.middle[data-v-e5dd9c1c]{top:6px;left:0;transform:translate(8px)}.bottom[data-v-e5dd9c1c]{top:12px;left:0;transform:translate(4px)}.VPNavBarMenuLink[data-v-e56f3d57]{display:flex;align-items:center;padding:0 12px;line-height:var(--vp-nav-height);font-size:14px;font-weight:500;color:var(--vp-c-text-1);transition:color .25s}.VPNavBarMenuLink.active[data-v-e56f3d57],.VPNavBarMenuLink[data-v-e56f3d57]:hover{color:var(--vp-c-brand-1)}.VPNavBarMenu[data-v-dc692963]{display:none}@media(min-width:768px){.VPNavBarMenu[data-v-dc692963]{display:flex}}/*! @docsearch/css 3.8.2 | MIT License | © Algolia, Inc. and contributors | https://docsearch.algolia.com */:root{--docsearch-primary-color:#5468ff;--docsearch-text-color:#1c1e21;--docsearch-spacing:12px;--docsearch-icon-stroke-width:1.4;--docsearch-highlight-color:var(--docsearch-primary-color);--docsearch-muted-color:#969faf;--docsearch-container-background:rgba(101,108,133,.8);--docsearch-logo-color:#5468ff;--docsearch-modal-width:560px;--docsearch-modal-height:600px;--docsearch-modal-background:#f5f6f7;--docsearch-modal-shadow:inset 1px 1px 0 0 hsla(0,0%,100%,.5),0 3px 8px 0 #555a64;--docsearch-searchbox-height:56px;--docsearch-searchbox-background:#ebedf0;--docsearch-searchbox-focus-background:#fff;--docsearch-searchbox-shadow:inset 0 0 0 2px var(--docsearch-primary-color);--docsearch-hit-height:56px;--docsearch-hit-color:#444950;--docsearch-hit-active-color:#fff;--docsearch-hit-background:#fff;--docsearch-hit-shadow:0 1px 3px 0 #d4d9e1;--docsearch-key-gradient:linear-gradient(-225deg,#d5dbe4,#f8f8f8);--docsearch-key-shadow:inset 0 -2px 0 0 #cdcde6,inset 0 0 1px 1px #fff,0 1px 2px 1px rgba(30,35,90,.4);--docsearch-key-pressed-shadow:inset 0 -2px 0 0 #cdcde6,inset 0 0 1px 1px #fff,0 1px 1px 0 rgba(30,35,90,.4);--docsearch-footer-height:44px;--docsearch-footer-background:#fff;--docsearch-footer-shadow:0 -1px 0 0 #e0e3e8,0 -3px 6px 0 rgba(69,98,155,.12)}html[data-theme=dark]{--docsearch-text-color:#f5f6f7;--docsearch-container-background:rgba(9,10,17,.8);--docsearch-modal-background:#15172a;--docsearch-modal-shadow:inset 1px 1px 0 0 #2c2e40,0 3px 8px 0 #000309;--docsearch-searchbox-background:#090a11;--docsearch-searchbox-focus-background:#000;--docsearch-hit-color:#bec3c9;--docsearch-hit-shadow:none;--docsearch-hit-background:#090a11;--docsearch-key-gradient:linear-gradient(-26.5deg,#565872,#31355b);--docsearch-key-shadow:inset 0 -2px 0 0 #282d55,inset 0 0 1px 1px #51577d,0 2px 2px 0 rgba(3,4,9,.3);--docsearch-key-pressed-shadow:inset 0 -2px 0 0 #282d55,inset 0 0 1px 1px #51577d,0 1px 1px 0 #0304094d;--docsearch-footer-background:#1e2136;--docsearch-footer-shadow:inset 0 1px 0 0 rgba(73,76,106,.5),0 -4px 8px 0 rgba(0,0,0,.2);--docsearch-logo-color:#fff;--docsearch-muted-color:#7f8497}.DocSearch-Button{align-items:center;background:var(--docsearch-searchbox-background);border:0;border-radius:40px;color:var(--docsearch-muted-color);cursor:pointer;display:flex;font-weight:500;height:36px;justify-content:space-between;margin:0 0 0 16px;padding:0 8px;-webkit-user-select:none;user-select:none}.DocSearch-Button:active,.DocSearch-Button:focus,.DocSearch-Button:hover{background:var(--docsearch-searchbox-focus-background);box-shadow:var(--docsearch-searchbox-shadow);color:var(--docsearch-text-color);outline:none}.DocSearch-Button-Container{align-items:center;display:flex}.DocSearch-Search-Icon{stroke-width:1.6}.DocSearch-Button .DocSearch-Search-Icon{color:var(--docsearch-text-color)}.DocSearch-Button-Placeholder{font-size:1rem;padding:0 12px 0 6px}.DocSearch-Button-Keys{display:flex;min-width:calc(40px + .8em)}.DocSearch-Button-Key{align-items:center;background:var(--docsearch-key-gradient);border:0;border-radius:3px;box-shadow:var(--docsearch-key-shadow);color:var(--docsearch-muted-color);display:flex;height:18px;justify-content:center;margin-right:.4em;padding:0 0 2px;position:relative;top:-1px;width:20px}.DocSearch-Button-Key--pressed{box-shadow:var(--docsearch-key-pressed-shadow);transform:translate3d(0,1px,0)}@media(max-width:768px){.DocSearch-Button-Keys,.DocSearch-Button-Placeholder{display:none}}.DocSearch--active{overflow:hidden!important}.DocSearch-Container,.DocSearch-Container *{box-sizing:border-box}.DocSearch-Container{background-color:var(--docsearch-container-background);height:100vh;left:0;position:fixed;top:0;width:100vw;z-index:200}.DocSearch-Container a{text-decoration:none}.DocSearch-Link{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;color:var(--docsearch-highlight-color);cursor:pointer;font:inherit;margin:0;padding:0}.DocSearch-Modal{background:var(--docsearch-modal-background);border-radius:6px;box-shadow:var(--docsearch-modal-shadow);flex-direction:column;margin:60px auto auto;max-width:var(--docsearch-modal-width);position:relative}.DocSearch-SearchBar{display:flex;padding:var(--docsearch-spacing) var(--docsearch-spacing) 0}.DocSearch-Form{align-items:center;background:var(--docsearch-searchbox-focus-background);border-radius:4px;box-shadow:var(--docsearch-searchbox-shadow);display:flex;height:var(--docsearch-searchbox-height);margin:0;padding:0 var(--docsearch-spacing);position:relative;width:100%}.DocSearch-Input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:0;color:var(--docsearch-text-color);flex:1;font:inherit;font-size:1.2em;height:100%;outline:none;padding:0 0 0 8px;width:80%}.DocSearch-Input::placeholder{color:var(--docsearch-muted-color);opacity:1}.DocSearch-Input::-webkit-search-cancel-button,.DocSearch-Input::-webkit-search-decoration,.DocSearch-Input::-webkit-search-results-button,.DocSearch-Input::-webkit-search-results-decoration{display:none}.DocSearch-LoadingIndicator,.DocSearch-MagnifierLabel,.DocSearch-Reset{margin:0;padding:0}.DocSearch-MagnifierLabel,.DocSearch-Reset{align-items:center;color:var(--docsearch-highlight-color);display:flex;justify-content:center}.DocSearch-Container--Stalled .DocSearch-MagnifierLabel,.DocSearch-LoadingIndicator{display:none}.DocSearch-Container--Stalled .DocSearch-LoadingIndicator{align-items:center;color:var(--docsearch-highlight-color);display:flex;justify-content:center}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Reset{animation:none;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:50%;color:var(--docsearch-icon-color);cursor:pointer;right:0;stroke-width:var(--docsearch-icon-stroke-width)}}.DocSearch-Reset{animation:fade-in .1s ease-in forwards;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:50%;color:var(--docsearch-icon-color);cursor:pointer;padding:2px;right:0;stroke-width:var(--docsearch-icon-stroke-width)}.DocSearch-Reset[hidden]{display:none}.DocSearch-Reset:hover{color:var(--docsearch-highlight-color)}.DocSearch-LoadingIndicator svg,.DocSearch-MagnifierLabel svg{height:24px;width:24px}.DocSearch-Cancel{display:none}.DocSearch-Dropdown{max-height:calc(var(--docsearch-modal-height) - var(--docsearch-searchbox-height) - var(--docsearch-spacing) - var(--docsearch-footer-height));min-height:var(--docsearch-spacing);overflow-y:auto;overflow-y:overlay;padding:0 var(--docsearch-spacing);scrollbar-color:var(--docsearch-muted-color) var(--docsearch-modal-background);scrollbar-width:thin}.DocSearch-Dropdown::-webkit-scrollbar{width:12px}.DocSearch-Dropdown::-webkit-scrollbar-track{background:transparent}.DocSearch-Dropdown::-webkit-scrollbar-thumb{background-color:var(--docsearch-muted-color);border:3px solid var(--docsearch-modal-background);border-radius:20px}.DocSearch-Dropdown ul{list-style:none;margin:0;padding:0}.DocSearch-Label{font-size:.75em;line-height:1.6em}.DocSearch-Help,.DocSearch-Label{color:var(--docsearch-muted-color)}.DocSearch-Help{font-size:.9em;margin:0;-webkit-user-select:none;user-select:none}.DocSearch-Title{font-size:1.2em}.DocSearch-Logo a{display:flex}.DocSearch-Logo svg{color:var(--docsearch-logo-color);margin-left:8px}.DocSearch-Hits:last-of-type{margin-bottom:24px}.DocSearch-Hits mark{background:none;color:var(--docsearch-highlight-color)}.DocSearch-HitsFooter{color:var(--docsearch-muted-color);display:flex;font-size:.85em;justify-content:center;margin-bottom:var(--docsearch-spacing);padding:var(--docsearch-spacing)}.DocSearch-HitsFooter a{border-bottom:1px solid;color:inherit}.DocSearch-Hit{border-radius:4px;display:flex;padding-bottom:4px;position:relative}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit--deleting{transition:none}}.DocSearch-Hit--deleting{opacity:0;transition:all .25s linear}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit--favoriting{transition:none}}.DocSearch-Hit--favoriting{transform:scale(0);transform-origin:top center;transition:all .25s linear;transition-delay:.25s}.DocSearch-Hit a{background:var(--docsearch-hit-background);border-radius:4px;box-shadow:var(--docsearch-hit-shadow);display:block;padding-left:var(--docsearch-spacing);width:100%}.DocSearch-Hit-source{background:var(--docsearch-modal-background);color:var(--docsearch-highlight-color);font-size:.85em;font-weight:600;line-height:32px;margin:0 -4px;padding:8px 4px 0;position:sticky;top:0;z-index:10}.DocSearch-Hit-Tree{color:var(--docsearch-muted-color);height:var(--docsearch-hit-height);opacity:.5;stroke-width:var(--docsearch-icon-stroke-width);width:24px}.DocSearch-Hit[aria-selected=true] a{background-color:var(--docsearch-highlight-color)}.DocSearch-Hit[aria-selected=true] mark{text-decoration:underline}.DocSearch-Hit-Container{align-items:center;color:var(--docsearch-hit-color);display:flex;flex-direction:row;height:var(--docsearch-hit-height);padding:0 var(--docsearch-spacing) 0 0}.DocSearch-Hit-icon{height:20px;width:20px}.DocSearch-Hit-action,.DocSearch-Hit-icon{color:var(--docsearch-muted-color);stroke-width:var(--docsearch-icon-stroke-width)}.DocSearch-Hit-action{align-items:center;display:flex;height:22px;width:22px}.DocSearch-Hit-action svg{display:block;height:18px;width:18px}.DocSearch-Hit-action+.DocSearch-Hit-action{margin-left:6px}.DocSearch-Hit-action-button{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:50%;color:inherit;cursor:pointer;padding:2px}svg.DocSearch-Hit-Select-Icon{display:none}.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-Select-Icon{display:block}.DocSearch-Hit-action-button:focus,.DocSearch-Hit-action-button:hover{background:#0003;transition:background-color .1s ease-in}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit-action-button:focus,.DocSearch-Hit-action-button:hover{transition:none}}.DocSearch-Hit-action-button:focus path,.DocSearch-Hit-action-button:hover path{fill:#fff}.DocSearch-Hit-content-wrapper{display:flex;flex:1 1 auto;flex-direction:column;font-weight:500;justify-content:center;line-height:1.2em;margin:0 8px;overflow-x:hidden;position:relative;text-overflow:ellipsis;white-space:nowrap;width:80%}.DocSearch-Hit-title{font-size:.9em}.DocSearch-Hit-path{color:var(--docsearch-muted-color);font-size:.75em}.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-Tree,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-action,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-icon,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-path,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-text,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-title,.DocSearch-Hit[aria-selected=true] mark{color:var(--docsearch-hit-active-color)!important}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit-action-button:focus,.DocSearch-Hit-action-button:hover{background:#0003;transition:none}}.DocSearch-ErrorScreen,.DocSearch-NoResults,.DocSearch-StartScreen{font-size:.9em;margin:0 auto;padding:36px 0;text-align:center;width:80%}.DocSearch-Screen-Icon{color:var(--docsearch-muted-color);padding-bottom:12px}.DocSearch-NoResults-Prefill-List{display:inline-block;padding-bottom:24px;text-align:left}.DocSearch-NoResults-Prefill-List ul{display:inline-block;padding:8px 0 0}.DocSearch-NoResults-Prefill-List li{list-style-position:inside;list-style-type:"» "}.DocSearch-Prefill{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:1em;color:var(--docsearch-highlight-color);cursor:pointer;display:inline-block;font-size:1em;font-weight:700;padding:0}.DocSearch-Prefill:focus,.DocSearch-Prefill:hover{outline:none;text-decoration:underline}.DocSearch-Footer{align-items:center;background:var(--docsearch-footer-background);border-radius:0 0 8px 8px;box-shadow:var(--docsearch-footer-shadow);display:flex;flex-direction:row-reverse;flex-shrink:0;height:var(--docsearch-footer-height);justify-content:space-between;padding:0 var(--docsearch-spacing);position:relative;-webkit-user-select:none;user-select:none;width:100%;z-index:300}.DocSearch-Commands{color:var(--docsearch-muted-color);display:flex;list-style:none;margin:0;padding:0}.DocSearch-Commands li{align-items:center;display:flex}.DocSearch-Commands li:not(:last-of-type){margin-right:.8em}.DocSearch-Commands-Key{align-items:center;background:var(--docsearch-key-gradient);border:0;border-radius:2px;box-shadow:var(--docsearch-key-shadow);color:var(--docsearch-muted-color);display:flex;height:18px;justify-content:center;margin-right:.4em;padding:0 0 1px;width:20px}.DocSearch-VisuallyHiddenForAccessibility{clip:rect(0 0 0 0);clip-path:inset(50%);height:1px;overflow:hidden;position:absolute;white-space:nowrap;width:1px}@media(max-width:768px){:root{--docsearch-spacing:10px;--docsearch-footer-height:40px}.DocSearch-Dropdown{height:100%}.DocSearch-Container{height:100vh;height:-webkit-fill-available;height:calc(var(--docsearch-vh, 1vh)*100);position:absolute}.DocSearch-Footer{border-radius:0;bottom:0;position:absolute}.DocSearch-Hit-content-wrapper{display:flex;position:relative;width:80%}.DocSearch-Modal{border-radius:0;box-shadow:none;height:100vh;height:-webkit-fill-available;height:calc(var(--docsearch-vh, 1vh)*100);margin:0;max-width:100%;width:100%}.DocSearch-Dropdown{max-height:calc(var(--docsearch-vh, 1vh)*100 - var(--docsearch-searchbox-height) - var(--docsearch-spacing) - var(--docsearch-footer-height))}.DocSearch-Cancel{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;color:var(--docsearch-highlight-color);cursor:pointer;display:inline-block;flex:none;font:inherit;font-size:1em;font-weight:500;margin-left:var(--docsearch-spacing);outline:none;overflow:hidden;padding:0;-webkit-user-select:none;user-select:none;white-space:nowrap}.DocSearch-Commands,.DocSearch-Hit-Tree{display:none}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}[class*=DocSearch]{--docsearch-primary-color: var(--vp-c-brand-1);--docsearch-highlight-color: var(--docsearch-primary-color);--docsearch-text-color: var(--vp-c-text-1);--docsearch-muted-color: var(--vp-c-text-2);--docsearch-searchbox-shadow: none;--docsearch-searchbox-background: transparent;--docsearch-searchbox-focus-background: transparent;--docsearch-key-gradient: transparent;--docsearch-key-shadow: none;--docsearch-modal-background: var(--vp-c-bg-soft);--docsearch-footer-background: var(--vp-c-bg)}.dark [class*=DocSearch]{--docsearch-modal-shadow: none;--docsearch-footer-shadow: none;--docsearch-logo-color: var(--vp-c-text-2);--docsearch-hit-background: var(--vp-c-default-soft);--docsearch-hit-color: var(--vp-c-text-2);--docsearch-hit-shadow: none}.DocSearch-Button{display:flex;justify-content:center;align-items:center;margin:0;padding:0;width:48px;height:55px;background:transparent;transition:border-color .25s}.DocSearch-Button:hover{background:transparent}.DocSearch-Button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}.DocSearch-Button-Key--pressed{transform:none;box-shadow:none}.DocSearch-Button:focus:not(:focus-visible){outline:none!important}@media(min-width:768px){.DocSearch-Button{justify-content:flex-start;border:1px solid transparent;border-radius:8px;padding:0 10px 0 12px;width:100%;height:40px;background-color:var(--vp-c-bg-alt)}.DocSearch-Button:hover{border-color:var(--vp-c-brand-1);background:var(--vp-c-bg-alt)}}.DocSearch-Button .DocSearch-Button-Container{display:flex;align-items:center}.DocSearch-Button .DocSearch-Search-Icon{position:relative;width:16px;height:16px;color:var(--vp-c-text-1);fill:currentColor;transition:color .5s}.DocSearch-Button:hover .DocSearch-Search-Icon{color:var(--vp-c-text-1)}@media(min-width:768px){.DocSearch-Button .DocSearch-Search-Icon{top:1px;margin-right:8px;width:14px;height:14px;color:var(--vp-c-text-2)}}.DocSearch-Button .DocSearch-Button-Placeholder{display:none;margin-top:2px;padding:0 16px 0 0;font-size:13px;font-weight:500;color:var(--vp-c-text-2);transition:color .5s}.DocSearch-Button:hover .DocSearch-Button-Placeholder{color:var(--vp-c-text-1)}@media(min-width:768px){.DocSearch-Button .DocSearch-Button-Placeholder{display:inline-block}}.DocSearch-Button .DocSearch-Button-Keys{direction:ltr;display:none;min-width:auto}@media(min-width:768px){.DocSearch-Button .DocSearch-Button-Keys{display:flex;align-items:center}}.DocSearch-Button .DocSearch-Button-Key{display:block;margin:2px 0 0;border:1px solid var(--vp-c-divider);border-right:none;border-radius:4px 0 0 4px;padding-left:6px;min-width:0;width:auto;height:22px;line-height:22px;font-family:var(--vp-font-family-base);font-size:12px;font-weight:500;transition:color .5s,border-color .5s}.DocSearch-Button .DocSearch-Button-Key+.DocSearch-Button-Key{border-right:1px solid var(--vp-c-divider);border-left:none;border-radius:0 4px 4px 0;padding-left:2px;padding-right:6px}.DocSearch-Button .DocSearch-Button-Key:first-child{font-size:0!important}.DocSearch-Button .DocSearch-Button-Key:first-child:after{content:"Ctrl";font-size:12px;letter-spacing:normal;color:var(--docsearch-muted-color)}.mac .DocSearch-Button .DocSearch-Button-Key:first-child:after{content:"⌘"}.DocSearch-Button .DocSearch-Button-Key:first-child>*{display:none}.DocSearch-Search-Icon{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke-width='1.6' viewBox='0 0 20 20'%3E%3Cpath fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' d='m14.386 14.386 4.088 4.088-4.088-4.088A7.533 7.533 0 1 1 3.733 3.733a7.533 7.533 0 0 1 10.653 10.653z'/%3E%3C/svg%3E")}.VPNavBarSearch{display:flex;align-items:center}@media(min-width:768px){.VPNavBarSearch{flex-grow:1;padding-left:24px}}@media(min-width:960px){.VPNavBarSearch{padding-left:32px}}.dark .DocSearch-Footer{border-top:1px solid var(--vp-c-divider)}.DocSearch-Form{border:1px solid var(--vp-c-brand-1);background-color:var(--vp-c-white)}.dark .DocSearch-Form{background-color:var(--vp-c-default-soft)}.DocSearch-Screen-Icon>svg{margin:auto}.VPNavBarSocialLinks[data-v-0394ad82]{display:none}@media(min-width:1280px){.VPNavBarSocialLinks[data-v-0394ad82]{display:flex;align-items:center}}.title[data-v-1168a8e4]{display:flex;align-items:center;border-bottom:1px solid transparent;width:100%;height:var(--vp-nav-height);font-size:16px;font-weight:600;color:var(--vp-c-text-1);transition:opacity .25s}@media(min-width:960px){.title[data-v-1168a8e4]{flex-shrink:0}.VPNavBarTitle.has-sidebar .title[data-v-1168a8e4]{border-bottom-color:var(--vp-c-divider)}}[data-v-1168a8e4] .logo{margin-right:8px;height:var(--vp-nav-logo-height)}.VPNavBarTranslations[data-v-88af2de4]{display:none}@media(min-width:1280px){.VPNavBarTranslations[data-v-88af2de4]{display:flex;align-items:center}}.title[data-v-88af2de4]{padding:0 24px 0 12px;line-height:32px;font-size:14px;font-weight:700;color:var(--vp-c-text-1)}.VPNavBar[data-v-6aa21345]{position:relative;height:var(--vp-nav-height);pointer-events:none;white-space:nowrap;transition:background-color .25s}.VPNavBar.screen-open[data-v-6aa21345]{transition:none;background-color:var(--vp-nav-bg-color);border-bottom:1px solid var(--vp-c-divider)}.VPNavBar[data-v-6aa21345]:not(.home){background-color:var(--vp-nav-bg-color)}@media(min-width:960px){.VPNavBar[data-v-6aa21345]:not(.home){background-color:transparent}.VPNavBar[data-v-6aa21345]:not(.has-sidebar):not(.home.top){background-color:var(--vp-nav-bg-color)}}.wrapper[data-v-6aa21345]{padding:0 8px 0 24px}@media(min-width:768px){.wrapper[data-v-6aa21345]{padding:0 32px}}@media(min-width:960px){.VPNavBar.has-sidebar .wrapper[data-v-6aa21345]{padding:0}}.container[data-v-6aa21345]{display:flex;justify-content:space-between;margin:0 auto;max-width:calc(var(--vp-layout-max-width) - 64px);height:var(--vp-nav-height);pointer-events:none}.container>.title[data-v-6aa21345],.container>.content[data-v-6aa21345]{pointer-events:none}.container[data-v-6aa21345] *{pointer-events:auto}@media(min-width:960px){.VPNavBar.has-sidebar .container[data-v-6aa21345]{max-width:100%}}.title[data-v-6aa21345]{flex-shrink:0;height:calc(var(--vp-nav-height) - 1px);transition:background-color .5s}@media(min-width:960px){.VPNavBar.has-sidebar .title[data-v-6aa21345]{position:absolute;top:0;left:0;z-index:2;padding:0 32px;width:var(--vp-sidebar-width);height:var(--vp-nav-height);background-color:transparent}}@media(min-width:1440px){.VPNavBar.has-sidebar .title[data-v-6aa21345]{padding-left:max(32px,calc((100% - (var(--vp-layout-max-width) - 64px)) / 2));width:calc((100% - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) - 32px)}}.content[data-v-6aa21345]{flex-grow:1}@media(min-width:960px){.VPNavBar.has-sidebar .content[data-v-6aa21345]{position:relative;z-index:1;padding-right:32px;padding-left:var(--vp-sidebar-width)}}@media(min-width:1440px){.VPNavBar.has-sidebar .content[data-v-6aa21345]{padding-right:calc((100vw - var(--vp-layout-max-width)) / 2 + 32px);padding-left:calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width))}}.content-body[data-v-6aa21345]{display:flex;justify-content:flex-end;align-items:center;height:var(--vp-nav-height);transition:background-color .5s}@media(min-width:960px){.VPNavBar:not(.home.top) .content-body[data-v-6aa21345]{position:relative;background-color:var(--vp-nav-bg-color)}.VPNavBar:not(.has-sidebar):not(.home.top) .content-body[data-v-6aa21345]{background-color:transparent}}@media(max-width:767px){.content-body[data-v-6aa21345]{column-gap:.5rem}}.menu+.translations[data-v-6aa21345]:before,.menu+.appearance[data-v-6aa21345]:before,.menu+.social-links[data-v-6aa21345]:before,.translations+.appearance[data-v-6aa21345]:before,.appearance+.social-links[data-v-6aa21345]:before{margin-right:8px;margin-left:8px;width:1px;height:24px;background-color:var(--vp-c-divider);content:""}.menu+.appearance[data-v-6aa21345]:before,.translations+.appearance[data-v-6aa21345]:before{margin-right:16px}.appearance+.social-links[data-v-6aa21345]:before{margin-left:16px}.social-links[data-v-6aa21345]{margin-right:-8px}.divider[data-v-6aa21345]{width:100%;height:1px}@media(min-width:960px){.VPNavBar.has-sidebar .divider[data-v-6aa21345]{padding-left:var(--vp-sidebar-width)}}@media(min-width:1440px){.VPNavBar.has-sidebar .divider[data-v-6aa21345]{padding-left:calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width))}}.divider-line[data-v-6aa21345]{width:100%;height:1px;transition:background-color .5s}.VPNavBar:not(.home) .divider-line[data-v-6aa21345]{background-color:var(--vp-c-gutter)}@media(min-width:960px){.VPNavBar:not(.home.top) .divider-line[data-v-6aa21345]{background-color:var(--vp-c-gutter)}.VPNavBar:not(.has-sidebar):not(.home.top) .divider[data-v-6aa21345]{background-color:var(--vp-c-gutter)}}.VPNavScreenAppearance[data-v-b44890b2]{display:flex;justify-content:space-between;align-items:center;border-radius:8px;padding:12px 14px 12px 16px;background-color:var(--vp-c-bg-soft)}.text[data-v-b44890b2]{line-height:24px;font-size:12px;font-weight:500;color:var(--vp-c-text-2)}.VPNavScreenMenuLink[data-v-df37e6dd]{display:block;border-bottom:1px solid var(--vp-c-divider);padding:12px 0 11px;line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-1);transition:border-color .25s,color .25s}.VPNavScreenMenuLink[data-v-df37e6dd]:hover{color:var(--vp-c-brand-1)}.VPNavScreenMenuGroupLink[data-v-3e9c20e4]{display:block;margin-left:12px;line-height:32px;font-size:14px;font-weight:400;color:var(--vp-c-text-1);transition:color .25s}.VPNavScreenMenuGroupLink[data-v-3e9c20e4]:hover{color:var(--vp-c-brand-1)}.VPNavScreenMenuGroupSection[data-v-8133b170]{display:block}.title[data-v-8133b170]{line-height:32px;font-size:13px;font-weight:700;color:var(--vp-c-text-2);transition:color .25s}.VPNavScreenMenuGroup[data-v-b9ab8c58]{border-bottom:1px solid var(--vp-c-divider);height:48px;overflow:hidden;transition:border-color .5s}.VPNavScreenMenuGroup .items[data-v-b9ab8c58]{visibility:hidden}.VPNavScreenMenuGroup.open .items[data-v-b9ab8c58]{visibility:visible}.VPNavScreenMenuGroup.open[data-v-b9ab8c58]{padding-bottom:10px;height:auto}.VPNavScreenMenuGroup.open .button[data-v-b9ab8c58]{padding-bottom:6px;color:var(--vp-c-brand-1)}.VPNavScreenMenuGroup.open .button-icon[data-v-b9ab8c58]{transform:rotate(45deg)}.button[data-v-b9ab8c58]{display:flex;justify-content:space-between;align-items:center;padding:12px 4px 11px 0;width:100%;line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-1);transition:color .25s}.button[data-v-b9ab8c58]:hover{color:var(--vp-c-brand-1)}.button-icon[data-v-b9ab8c58]{transition:transform .25s}.group[data-v-b9ab8c58]:first-child{padding-top:0}.group+.group[data-v-b9ab8c58],.group+.item[data-v-b9ab8c58]{padding-top:4px}.VPNavScreenTranslations[data-v-858fe1a4]{height:24px;overflow:hidden}.VPNavScreenTranslations.open[data-v-858fe1a4]{height:auto}.title[data-v-858fe1a4]{display:flex;align-items:center;font-size:14px;font-weight:500;color:var(--vp-c-text-1)}.icon[data-v-858fe1a4]{font-size:16px}.icon.lang[data-v-858fe1a4]{margin-right:8px}.icon.chevron[data-v-858fe1a4]{margin-left:4px}.list[data-v-858fe1a4]{padding:4px 0 0 24px}.link[data-v-858fe1a4]{line-height:32px;font-size:13px;color:var(--vp-c-text-1)}.VPNavScreen[data-v-f2779853]{position:fixed;top:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px));right:0;bottom:0;left:0;padding:0 32px;width:100%;background-color:var(--vp-nav-screen-bg-color);overflow-y:auto;transition:background-color .25s;pointer-events:auto}.VPNavScreen.fade-enter-active[data-v-f2779853],.VPNavScreen.fade-leave-active[data-v-f2779853]{transition:opacity .25s}.VPNavScreen.fade-enter-active .container[data-v-f2779853],.VPNavScreen.fade-leave-active .container[data-v-f2779853]{transition:transform .25s ease}.VPNavScreen.fade-enter-from[data-v-f2779853],.VPNavScreen.fade-leave-to[data-v-f2779853]{opacity:0}.VPNavScreen.fade-enter-from .container[data-v-f2779853],.VPNavScreen.fade-leave-to .container[data-v-f2779853]{transform:translateY(-8px)}@media(min-width:768px){.VPNavScreen[data-v-f2779853]{display:none}}.container[data-v-f2779853]{margin:0 auto;padding:24px 0 96px;max-width:288px}.menu+.translations[data-v-f2779853],.menu+.appearance[data-v-f2779853],.translations+.appearance[data-v-f2779853]{margin-top:24px}.menu+.social-links[data-v-f2779853]{margin-top:16px}.appearance+.social-links[data-v-f2779853]{margin-top:16px}.VPNav[data-v-ae24b3ad]{position:relative;top:var(--vp-layout-top-height, 0px);left:0;z-index:var(--vp-z-index-nav);width:100%;pointer-events:none;transition:background-color .5s}@media(min-width:960px){.VPNav[data-v-ae24b3ad]{position:fixed}}.VPSidebarItem.level-0[data-v-b3fd67f8]{padding-bottom:24px}.VPSidebarItem.collapsed.level-0[data-v-b3fd67f8]{padding-bottom:10px}.item[data-v-b3fd67f8]{position:relative;display:flex;width:100%}.VPSidebarItem.collapsible>.item[data-v-b3fd67f8]{cursor:pointer}.indicator[data-v-b3fd67f8]{position:absolute;top:6px;bottom:6px;left:-17px;width:2px;border-radius:2px;transition:background-color .25s}.VPSidebarItem.level-2.is-active>.item>.indicator[data-v-b3fd67f8],.VPSidebarItem.level-3.is-active>.item>.indicator[data-v-b3fd67f8],.VPSidebarItem.level-4.is-active>.item>.indicator[data-v-b3fd67f8],.VPSidebarItem.level-5.is-active>.item>.indicator[data-v-b3fd67f8]{background-color:var(--vp-c-brand-1)}.link[data-v-b3fd67f8]{display:flex;align-items:center;flex-grow:1}.text[data-v-b3fd67f8]{flex-grow:1;padding:4px 0;line-height:24px;font-size:14px;transition:color .25s}.VPSidebarItem.level-0 .text[data-v-b3fd67f8]{font-weight:700;color:var(--vp-c-text-1)}.VPSidebarItem.level-1 .text[data-v-b3fd67f8],.VPSidebarItem.level-2 .text[data-v-b3fd67f8],.VPSidebarItem.level-3 .text[data-v-b3fd67f8],.VPSidebarItem.level-4 .text[data-v-b3fd67f8],.VPSidebarItem.level-5 .text[data-v-b3fd67f8]{font-weight:500;color:var(--vp-c-text-2)}.VPSidebarItem.level-0.is-link>.item>.link:hover .text[data-v-b3fd67f8],.VPSidebarItem.level-1.is-link>.item>.link:hover .text[data-v-b3fd67f8],.VPSidebarItem.level-2.is-link>.item>.link:hover .text[data-v-b3fd67f8],.VPSidebarItem.level-3.is-link>.item>.link:hover .text[data-v-b3fd67f8],.VPSidebarItem.level-4.is-link>.item>.link:hover .text[data-v-b3fd67f8],.VPSidebarItem.level-5.is-link>.item>.link:hover .text[data-v-b3fd67f8]{color:var(--vp-c-brand-1)}.VPSidebarItem.level-0.has-active>.item>.text[data-v-b3fd67f8],.VPSidebarItem.level-1.has-active>.item>.text[data-v-b3fd67f8],.VPSidebarItem.level-2.has-active>.item>.text[data-v-b3fd67f8],.VPSidebarItem.level-3.has-active>.item>.text[data-v-b3fd67f8],.VPSidebarItem.level-4.has-active>.item>.text[data-v-b3fd67f8],.VPSidebarItem.level-5.has-active>.item>.text[data-v-b3fd67f8],.VPSidebarItem.level-0.has-active>.item>.link>.text[data-v-b3fd67f8],.VPSidebarItem.level-1.has-active>.item>.link>.text[data-v-b3fd67f8],.VPSidebarItem.level-2.has-active>.item>.link>.text[data-v-b3fd67f8],.VPSidebarItem.level-3.has-active>.item>.link>.text[data-v-b3fd67f8],.VPSidebarItem.level-4.has-active>.item>.link>.text[data-v-b3fd67f8],.VPSidebarItem.level-5.has-active>.item>.link>.text[data-v-b3fd67f8]{color:var(--vp-c-text-1)}.VPSidebarItem.level-0.is-active>.item .link>.text[data-v-b3fd67f8],.VPSidebarItem.level-1.is-active>.item .link>.text[data-v-b3fd67f8],.VPSidebarItem.level-2.is-active>.item .link>.text[data-v-b3fd67f8],.VPSidebarItem.level-3.is-active>.item .link>.text[data-v-b3fd67f8],.VPSidebarItem.level-4.is-active>.item .link>.text[data-v-b3fd67f8],.VPSidebarItem.level-5.is-active>.item .link>.text[data-v-b3fd67f8]{color:var(--vp-c-brand-1)}.caret[data-v-b3fd67f8]{display:flex;justify-content:center;align-items:center;margin-right:-7px;width:32px;height:32px;color:var(--vp-c-text-3);cursor:pointer;transition:color .25s;flex-shrink:0}.item:hover .caret[data-v-b3fd67f8]{color:var(--vp-c-text-2)}.item:hover .caret[data-v-b3fd67f8]:hover{color:var(--vp-c-text-1)}.caret-icon[data-v-b3fd67f8]{font-size:18px;transform:rotate(90deg);transition:transform .25s}.VPSidebarItem.collapsed .caret-icon[data-v-b3fd67f8]{transform:rotate(0)}.VPSidebarItem.level-1 .items[data-v-b3fd67f8],.VPSidebarItem.level-2 .items[data-v-b3fd67f8],.VPSidebarItem.level-3 .items[data-v-b3fd67f8],.VPSidebarItem.level-4 .items[data-v-b3fd67f8],.VPSidebarItem.level-5 .items[data-v-b3fd67f8]{border-left:1px solid var(--vp-c-divider);padding-left:16px}.VPSidebarItem.collapsed .items[data-v-b3fd67f8]{display:none}.no-transition[data-v-c40bc020] .caret-icon{transition:none}.group+.group[data-v-c40bc020]{border-top:1px solid var(--vp-c-divider);padding-top:10px}@media(min-width:960px){.group[data-v-c40bc020]{padding-top:10px;width:calc(var(--vp-sidebar-width) - 64px)}}.VPSidebar[data-v-319d5ca6]{position:fixed;top:var(--vp-layout-top-height, 0px);bottom:0;left:0;z-index:var(--vp-z-index-sidebar);padding:32px 32px 96px;width:calc(100vw - 64px);max-width:320px;background-color:var(--vp-sidebar-bg-color);opacity:0;box-shadow:var(--vp-c-shadow-3);overflow-x:hidden;overflow-y:auto;transform:translate(-100%);transition:opacity .5s,transform .25s ease;overscroll-behavior:contain}.VPSidebar.open[data-v-319d5ca6]{opacity:1;visibility:visible;transform:translate(0);transition:opacity .25s,transform .5s cubic-bezier(.19,1,.22,1)}.dark .VPSidebar[data-v-319d5ca6]{box-shadow:var(--vp-shadow-1)}@media(min-width:960px){.VPSidebar[data-v-319d5ca6]{padding-top:var(--vp-nav-height);width:var(--vp-sidebar-width);max-width:100%;background-color:var(--vp-sidebar-bg-color);opacity:1;visibility:visible;box-shadow:none;transform:translate(0)}}@media(min-width:1440px){.VPSidebar[data-v-319d5ca6]{padding-left:max(32px,calc((100% - (var(--vp-layout-max-width) - 64px)) / 2));width:calc((100% - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) - 32px)}}@media(min-width:960px){.curtain[data-v-319d5ca6]{position:sticky;top:-64px;left:0;z-index:1;margin-top:calc(var(--vp-nav-height) * -1);margin-right:-32px;margin-left:-32px;height:var(--vp-nav-height);background-color:var(--vp-sidebar-bg-color)}}.nav[data-v-319d5ca6]{outline:0}.VPSkipLink[data-v-0b0ada53]{top:8px;left:8px;padding:8px 16px;z-index:999;border-radius:8px;font-size:12px;font-weight:700;text-decoration:none;color:var(--vp-c-brand-1);box-shadow:var(--vp-shadow-3);background-color:var(--vp-c-bg)}.VPSkipLink[data-v-0b0ada53]:focus{height:auto;width:auto;clip:auto;clip-path:none}@media(min-width:1280px){.VPSkipLink[data-v-0b0ada53]{top:14px;left:16px}}.Layout[data-v-5d98c3a5]{display:flex;flex-direction:column;min-height:100vh}.endpoint{display:flex;align-items:stretch;margin:1rem 0;border-radius:6px;overflow:hidden;background-color:var(--vp-c-bg-soft);border:1px solid var(--vp-c-divider)}.endpoint-method{padding:.75rem 1rem;font-weight:700;font-family:var(--vp-font-family-mono);font-size:.875rem;background-color:var(--vp-c-brand-1);color:#fff;display:flex;align-items:center;white-space:nowrap}.endpoint-url{padding:.75rem 1rem;font-family:var(--vp-font-family-mono);font-size:.875rem;display:flex;align-items:center;flex:1;overflow-x:auto}.endpoint-url .strong{font-weight:700;color:var(--vp-c-brand-1)}.dark .endpoint{background-color:var(--vp-c-bg-soft)}.dark .endpoint-method{background-color:var(--vp-c-brand-2)}.VPLocalSearchBox[data-v-ce626c7c]{position:fixed;z-index:100;top:0;right:0;bottom:0;left:0;display:flex}.backdrop[data-v-ce626c7c]{position:absolute;top:0;right:0;bottom:0;left:0;background:var(--vp-backdrop-bg-color);transition:opacity .5s}.shell[data-v-ce626c7c]{position:relative;padding:12px;margin:64px auto;display:flex;flex-direction:column;gap:16px;background:var(--vp-local-search-bg);width:min(100vw - 60px,900px);height:min-content;max-height:min(100vh - 128px,900px);border-radius:6px}@media(max-width:767px){.shell[data-v-ce626c7c]{margin:0;width:100vw;height:100vh;max-height:none;border-radius:0}}.search-bar[data-v-ce626c7c]{border:1px solid var(--vp-c-divider);border-radius:4px;display:flex;align-items:center;padding:0 12px;cursor:text}@media(max-width:767px){.search-bar[data-v-ce626c7c]{padding:0 8px}}.search-bar[data-v-ce626c7c]:focus-within{border-color:var(--vp-c-brand-1)}.local-search-icon[data-v-ce626c7c]{display:block;font-size:18px}.navigate-icon[data-v-ce626c7c]{display:block;font-size:14px}.search-icon[data-v-ce626c7c]{margin:8px}@media(max-width:767px){.search-icon[data-v-ce626c7c]{display:none}}.search-input[data-v-ce626c7c]{padding:6px 12px;font-size:inherit;width:100%}@media(max-width:767px){.search-input[data-v-ce626c7c]{padding:6px 4px}}.search-actions[data-v-ce626c7c]{display:flex;gap:4px}@media(any-pointer:coarse){.search-actions[data-v-ce626c7c]{gap:8px}}@media(min-width:769px){.search-actions.before[data-v-ce626c7c]{display:none}}.search-actions button[data-v-ce626c7c]{padding:8px}.search-actions button[data-v-ce626c7c]:not([disabled]):hover,.toggle-layout-button.detailed-list[data-v-ce626c7c]{color:var(--vp-c-brand-1)}.search-actions button.clear-button[data-v-ce626c7c]:disabled{opacity:.37}.search-keyboard-shortcuts[data-v-ce626c7c]{font-size:.8rem;opacity:75%;display:flex;flex-wrap:wrap;gap:16px;line-height:14px}.search-keyboard-shortcuts span[data-v-ce626c7c]{display:flex;align-items:center;gap:4px}@media(max-width:767px){.search-keyboard-shortcuts[data-v-ce626c7c]{display:none}}.search-keyboard-shortcuts kbd[data-v-ce626c7c]{background:#8080801a;border-radius:4px;padding:3px 6px;min-width:24px;display:inline-block;text-align:center;vertical-align:middle;border:1px solid rgba(128,128,128,.15);box-shadow:0 2px 2px #0000001a}.results[data-v-ce626c7c]{display:flex;flex-direction:column;gap:6px;overflow-x:hidden;overflow-y:auto;overscroll-behavior:contain}.result[data-v-ce626c7c]{display:flex;align-items:center;gap:8px;border-radius:4px;transition:none;line-height:1rem;border:solid 2px var(--vp-local-search-result-border);outline:none}.result>div[data-v-ce626c7c]{margin:12px;width:100%;overflow:hidden}@media(max-width:767px){.result>div[data-v-ce626c7c]{margin:8px}}.titles[data-v-ce626c7c]{display:flex;flex-wrap:wrap;gap:4px;position:relative;z-index:1001;padding:2px 0}.title[data-v-ce626c7c]{display:flex;align-items:center;gap:4px}.title.main[data-v-ce626c7c]{font-weight:500}.title-icon[data-v-ce626c7c]{opacity:.5;font-weight:500;color:var(--vp-c-brand-1)}.title svg[data-v-ce626c7c]{opacity:.5}.result.selected[data-v-ce626c7c]{--vp-local-search-result-bg: var(--vp-local-search-result-selected-bg);border-color:var(--vp-local-search-result-selected-border)}.excerpt-wrapper[data-v-ce626c7c]{position:relative}.excerpt[data-v-ce626c7c]{opacity:50%;pointer-events:none;max-height:140px;overflow:hidden;position:relative;margin-top:4px}.result.selected .excerpt[data-v-ce626c7c]{opacity:1}.excerpt[data-v-ce626c7c] *{font-size:.8rem!important;line-height:130%!important}.titles[data-v-ce626c7c] mark,.excerpt[data-v-ce626c7c] mark{background-color:var(--vp-local-search-highlight-bg);color:var(--vp-local-search-highlight-text);border-radius:2px;padding:0 2px}.excerpt[data-v-ce626c7c] .vp-code-group .tabs{display:none}.excerpt[data-v-ce626c7c] .vp-code-group div[class*=language-]{border-radius:8px!important}.excerpt-gradient-bottom[data-v-ce626c7c]{position:absolute;bottom:-1px;left:0;width:100%;height:8px;background:linear-gradient(transparent,var(--vp-local-search-result-bg));z-index:1000}.excerpt-gradient-top[data-v-ce626c7c]{position:absolute;top:-1px;left:0;width:100%;height:8px;background:linear-gradient(var(--vp-local-search-result-bg),transparent);z-index:1000}.result.selected .titles[data-v-ce626c7c],.result.selected .title-icon[data-v-ce626c7c]{color:var(--vp-c-brand-1)!important}.no-results[data-v-ce626c7c]{font-size:.9rem;text-align:center;padding:12px}svg[data-v-ce626c7c]{flex:none} diff --git a/docs/v26.6.1/assets/testing.md.4Xp8rUfy.js b/docs/v26.6.1/assets/testing.md.4Xp8rUfy.js new file mode 100644 index 0000000..a976333 --- /dev/null +++ b/docs/v26.6.1/assets/testing.md.4Xp8rUfy.js @@ -0,0 +1,181 @@ +import{_ as a,o as n,c as e,ag as t}from"./chunks/framework.D4c47gkQ.js";const h=JSON.parse('{"title":"Testsuite","description":"","frontmatter":{},"headers":[],"relativePath":"testing.md","filePath":"testing.md"}'),p={name:"testing.md"};function i(o,s,l,r,c,d){return n(),e("div",null,[...s[0]||(s[0]=[t(`OSRM comes with a testsuite containing both unit-tests using the Boost library and cucumber.js for scenario driven testing.
For a general introduction on Boost.Test have a look at its docs.
Unit tests should be registered according to the sub-project they're in. If you want to write tests for utility functions, add them to the utility test binary. See CMakeLists.txt in the unit test directory for how to register new unit tests.
There is a difference between only reporting a failed condition and aborting the test right at a failed condition. Have a look at BOOST_CHECK vs BOOST_REQUIRE. Instead of manually checking e.g. for equality, less than, if a function throws etc. use their corresponding Boost.Test primitives.
If you use BOOST_CHECK_EQUAL you have to implement operator<< for your type so that Boost.Test can print mismatches. If you do not want to do this, define BOOST_TEST_DONT_PRINT_LOG_VALUE (and undef it after the check call) or sidestep it with BOOST_CHECK(fst == snd);.
If you need to test features on a real dataset (think about this twice: prefer cucumber and dataset-independent tests for their reproducibility and minimality), there is a fixed dataset in test/data. This dataset is a small extract and may not even contain all tags or edge cases. Furthermore this dataset is not in sync with what you see in up-to-date OSM maps or on the demo server. See the library tests for how to add new dataset dependent tests.
To prepare the test data simply cd test/data/ and then run make.
To build the unit tests:
cd build
+cmake ..
+make testsYou should see the compiled binaries in build/unit_tests, you can then run each suite individually:
./engine-testsFor a general introduction on cucumber in our testsuite, have a look at the wiki.
This documentation aims to supply a guideline on how to write cucumber tests that test new features introduced into osrm.
It is often tempting to reduce the test to a path and accompanying instructions. Instructions can and will change over the course of improving guidance.
Instructions should only be used when writing a feature located in features/guidance. All other features should avoid using instructions at all.
OSRM is a navigation engine. Tests should always consider this context.
An important implication is the grid size. If tests use a very small grid size, you run into the chance of instructions being omitted. For example:
Background:
+ Given the profile "car"
+ Given a grid size of 10 meters
+
+Scenario: Testbot - Straight Road
+ Given the node map
+ """
+ a b c d
+ """
+
+ And the ways
+ | nodes | highway |
+ | ab | primary |
+ | bc | primary |
+ | cd | primary |
+
+ When I route I should get
+ | from | to | route |
+ | a | d | ab,bc,cd,cd |In a navigation engine, the instructions
would be impossible to announce and not helpful at all. Since no actual choices exist, the route you get could result in ab,cd and simply say depart and arrive.
To prevent such surprises, always consider the availability of other roads and use grid sizes/road lengths that correspond to actually reasonable scenarios in a road network.
If you specify many nodes in close succession to present a specific road geometry, consider using name to indicate to OSRM that the segment is a single road.
Background:
+ Given the profile "car"
+ Given a grid size of 10 meters
+
+Scenario: Testbot - Straight Road
+ Given the node map
+ """
+ a b c d
+ """
+
+ And the ways
+ | nodes | highway | name |
+ | ab | primary | road |
+ | bc | primary | road |
+ | cd | primary | road |
+
+ When I route I should get
+ | from | to | route | turns |
+ | a | d | road,road | depart,arrive |Guidance guarantees only essential maneuvers. You will always see depart and arrive as well as all turns that are not obvious.
So the following scenario does not change the instructions
Background:
+ Given the profile "car"
+ Given a grid size of 10 meters
+
+Scenario: Testbot - Straight Road
+ Given the node map
+ """
+ a b
+ d c
+ """
+
+ And the ways
+ | nodes | highway | name |
+ | ab | primary | road |
+ | bc | primary | road |
+ | cd | primary | road |
+
+ When I route I should get
+ | from | to | route | turns |
+ | a | d | road,road | depart,arrive |but if we modify it to
Background:
+ Given the profile "car"
+ Given a grid size of 10 meters
+
+Scenario: Testbot - Straight Road
+ Given the node map
+ """
+ a b e
+ d c
+ """
+
+ And the ways
+ | nodes | highway | name |
+ | ab | primary | road |
+ | bc | primary | road |
+ | cd | primary | road |
+ | be | primary | turn |
+
+ When I route I should get
+ | from | to | route | turns |
+ | a | d | road,road,road | depart,continue right,arrive |Modelling a road as roundabout has an implied oneway tag associated with it. In the following case, we can route from a to d but not from d to a. To discover those errors, make sure to check for all allowed directions.
Scenario: Enter and Exit mini roundabout with sharp angle # features/guidance/mini-roundabout.feature:37
+ Given the profile "car" # features/step_definitions/data.js:8
+ Given a grid size of 10 meters # features/step_definitions/data.js:20
+ Given the node map # features/step_definitions/data.js:45
+ """
+ a b
+ c d
+ """
+ And the ways # features/step_definitions/data.js:128
+ | nodes | highway | name |
+ | ab | tertiary | MySt |
+ | bc | roundabout | |
+ | cd | tertiary | MySt |
+ When I route I should get # features/step_definitions/routing.js:4
+ | from | to | route | turns | # |
+ | a | d | MySt,MySt | depart,arrive | # suppress multiple enter/exit mini roundabouts |
+ | d | a | MySt,MySt | depart,arrive | # suppress multiple enter/exit mini roundabouts |
+ Tables were not identical:
+ | from | to | route | turns | #
+ | a | d | MySt,MySt | depart,arrive | # suppress multiple enter/exit mini roundabouts |
+ | (-) d | (-) a | (-) MySt,MySt | (-) depart,arrive | (-) # suppress multiple enter/exit mini roundabouts |
+ | (+) d | (+) a | (+) | (+) | (+) # suppress multiple enter/exit mini roundabouts |Some features in OSRM can result in strange experiences during testcases. To prevent some of these issues, follow the guidelines below.
Using grid nodes as waypoints offers the chance of unwanted side effects. OSRM converts the grid into a so called edge-based graph.
Scenario: Testbot - Intersection
+ Given the node map
+ """
+ e
+ b a d
+ c
+ """
+
+ And the ways
+ | nodes | highway | oneway |
+ | ab | primary | yes |
+ | ac | primary | yes |
+ | ad | primary | yes |
+ | ae | primary | yes |Selecting a as a waypoint results in four possible starting locations. Which one of the routes a,b, a,c, a,d, or a,e is found is pure chance and depends on the order in the static r-tree.
To guarantee discovery, use:
Scenario: Testbot - Intersection
+ Given the node map
+ """
+ e
+ 4
+ b 1 a 3 d
+ 2
+ c
+ """
+
+ And the ways
+ | nodes | highway | oneway |
+ | ab | primary | yes |
+ | ac | primary | yes |
+ | ad | primary | yes |
+ | ae | primary | yes |And use 1,2,3, and 4 as starting waypoints. The routes 1,b, 2,c, 3,d, and 4,e can all be discovered.
Whenever you are independent of the start location (see use waypoints), the waypoint chosen as start/end location can still influence distances/durations.
If you are testing for a duration metric, allow for a tiny offset to ensure a passing test in the presence of rounding/snapping issues.
Alternative route discovery is a random feature in itself. The discovery of routes depends on the contraction order of roads and cannot be assumed successful, ever.
Adding turn restrictions requires the restriction to follow a very specific format.
We specify them in a table with the header | type | way:from | way:to | node:via | restriction |. It is important that turn restrictions require micro segmentation.
Consider the following scenario:
Given the node map:
+ """
+ e
+ |
+ a - - b - - c
+ |
+ d
+ """
+
+And the ways
+ | nodes | oneway |
+ | abc | yes |
+ | ebd | yes |
+
+And the relations
+ | type | way:from | way:to | node:via | restriction |
+ | restriction | abc | ebd | b | no_right_turn |The setting looks perfectly fine at first glance. However, it is not well defined. The forbidden right turn could be either a superfluous addition, forbidding the turn cb to be, or actually refer to the turn ab to bd to say that a turn is forbidden here.
To model turn-restrictions correctly and uniquely, we need to split segments that contribute to the restriction into the smallest possible parts. E.g. the above scenario could correctly be expressed as:
Given the node map:
+ """
+ e
+ |
+ a - - b - - c
+ |
+ d
+ """
+
+And the ways
+ | nodes | oneway | name |
+ | ab | yes | abc |
+ | bc | yes | abc |
+ | eb | yes | ebd |
+ | bd | yes | ebd |
+
+And the relations
+ | type | way:from | way:to | node:via | restriction |
+ | restriction | ab | bd | b | no_right_turn |Unless this format is used, OSRM will omit the (then ambiguous) turn restrictions and ignore them.
If you change some stuff in guidance, you will easily see tests change their result. E.g. if you change the angles for which we report right, then obviously some tests might not report a direction modifier named right anymore.
This small section will try to guide you in making the correct decisions for changing the behaviour of tests.
The difficulty in guidance tests is that not all items can be translated 1:1 from the ascii art into turn-angles.
The turn-angle calculation tries to find turn angles that would represent perceived turn angles, not the exact angle at the connection.
This is necessary, since connections in OSM are always bound by the paradigm that the way is supposed to be in the middle of the actual road. For broad streets, you will see stronger angles than the actual turns.
If we have a test that looks like this:
Given a grid size of 5 m
+Given the node map
+"""
+a - b - - - - - - c
+ \\
+ d - - - - - e
+"""
+
+When I route I should get
+ | waypoints | route | turns |
+ | a,e | abc,bde,bde | depart,turn slight right,arrive|And the test reports turn right for the route a->e, where before it said slight right.
If you change the turn angles, obviously you can expect changes in the distinction between slight right and right. In such a case it is, of course, reasonable to change the expected route to report right instead of slight right. You should consider inspecting the actual turn angles at b to see if you feel that change is justified.
However, you should never adjust the test itself. If you look at a failure, the other way around
Given a grid size of 5 m
+Given the node map
+"""
+a - b - - - - - - c
+ \\
+ d - - - - - e
+"""
+
+When I route I should get
+ | waypoints | route | turns |
+ | a,e | abc,bde,bde | depart,turn right,arrive|where we see a slight right, over the expected right. We could be tempted to adjust the grid size (e.g. from 10 m to 20 meters).
Such a change would fundamentally alter the tests, though. Since the part b-d is a short offset, when we are looking at a grid of size 5 m, the angle calculation will try and compensate for this offset.
In this case we would see a very slight turn angle. If your change now reports different turn angles, you can of course change the expected result. But you should not adjust the grid size. The test would be testing turn angles of 180 and 100 degrees, instead of 180 and 160.
Some changes you might see could look completely unrelated. To understand the impact of your changes, you can make use of the debugging utilities you can find in util/debug.hpp (and potentially other related headers).
If your test is inspecting a series of turns (remember, a turn does not necessarily equals an instruction), you could see interaction with post-processing. To see the unprocessed turns, you should print the steps at the end of step assembly (assembleSteps in engine/guidance/assemble_steps.hpp).
If you see unexpected changes, you can consider adding the locations field to your test to study what location a turn is reported at.
To study a test without post-processing impacts, you can create a copy of the case on a very large grid (like 2000 meters). In such a grid, turn collapsing would be essentially disabled.
Sadly, there is no general guideline.
If in doubt, ask another person. Inspect as much of the data as possible (e.g. print un-collapsed steps, turn angles and so on) and use your best judgement, if the new result seems justified.
`,89)])])}const g=a(p,[["render",i]]);export{h as __pageData,g as default}; diff --git a/docs/v26.6.1/assets/testing.md.4Xp8rUfy.lean.js b/docs/v26.6.1/assets/testing.md.4Xp8rUfy.lean.js new file mode 100644 index 0000000..b49ced2 --- /dev/null +++ b/docs/v26.6.1/assets/testing.md.4Xp8rUfy.lean.js @@ -0,0 +1 @@ +import{_ as a,o as n,c as e,ag as t}from"./chunks/framework.D4c47gkQ.js";const h=JSON.parse('{"title":"Testsuite","description":"","frontmatter":{},"headers":[],"relativePath":"testing.md","filePath":"testing.md"}'),p={name:"testing.md"};function i(o,s,l,r,c,d){return n(),e("div",null,[...s[0]||(s[0]=[t("",89)])])}const g=a(p,[["render",i]]);export{h as __pageData,g as default}; diff --git a/docs/v26.6.1/assets/tools.md.Bwog4E_F.js b/docs/v26.6.1/assets/tools.md.Bwog4E_F.js new file mode 100644 index 0000000..dba0b41 --- /dev/null +++ b/docs/v26.6.1/assets/tools.md.Bwog4E_F.js @@ -0,0 +1,10 @@ +import{_ as e,o as d,c as o,ag as a}from"./chunks/framework.D4c47gkQ.js";const m=JSON.parse('{"title":"Command-Line Tools","description":"","frontmatter":{},"headers":[],"relativePath":"tools.md","filePath":"tools.md"}'),s={name:"tools.md"};function i(r,t,n,c,l,h){return d(),o("div",null,[...t[0]||(t[0]=[a(`OSRM ships six command-line tools that cover the full data pipeline, from raw OSM data to a running routing server. All tools share a set of common options described below, followed by per-tool reference sections.
These flags are accepted by every tool.
| Flag | Short | Description |
|---|---|---|
--help | -h | Show the help message and exit. |
--version | -v | Show the version string and exit. |
--verbosity <level> | -l | Log verbosity: NONE, ERROR, WARNING, INFO (default), DEBUG. |
--list-inputs | Print all required and optional input file extensions the tool expects, then exit. Useful for deployment scripts. | |
--threads <n> | -t | Number of threads to use (default: number of logical CPUs). |
--list-inputs Prints one line per file in the format required|optional <extension>:
$ osrm-routed --list-inputs
+required .osrm.datasource_names
+required .osrm.ebg_nodes
+required .osrm.edges
+...
+optional .osrm.hsgr
+optional .osrm.cellsExample — collect all files needed to deploy osrm-routed:
BASE=map
+for line in $(osrm-routed --list-inputs); do
+ echo "$BASE$line"
+doneReads an OSM file and a Lua profile, and produces the intermediate .osrm.* files consumed by the graph-preparation tools.
osrm-extract <input.osm/.osm.bz2/.osm.pbf> [options]| Flag | Short | Default | Description |
|---|---|---|---|
--profile <path> | -p | profiles/car.lua | Path to the Lua routing profile. |
--output <path> | -o | Derived from input filename | Base path for generated output files. |
--data_version <string> | -d | (none) | Tag the dataset with a version string. Use osmosis to read the timestamp embedded in the PBF file. |
--small-component-size <n> | 1000 | Minimum node count for a strongly-connected component to be treated as "large". Affects nearest-neighbor snapping. | |
--with-osm-metadata | Parse OSM metadata (user, timestamp, etc.). May reduce extraction performance. | ||
--parse-conditional-restrictions | Save conditional turn restrictions to disk so they can be evaluated during contraction. | ||
--location-dependent-data <file> | GeoJSON files containing location-dependent data (e.g. speed limits by region). Repeatable. | ||
--disable-location-cache | Disable the internal node-location cache used for location-dependent data lookups. | ||
--dump-nbg-graph | Write the raw node-based graph to the .osrm file for debugging. |
Partitions the road network graph into a hierarchy of cells used by the Multi-Level Dijkstra (MLD) algorithm.
osrm-partition <input.osrm> [options]| Flag | Default | Description |
|---|---|---|
--max-cell-sizes <list> | 128,4096,65536,2097152 | Comma-separated maximum cell sizes per level, starting from level 1. The first value is also the bisection termination threshold. |
--balance <factor> | 1.2 | Maximum allowed size ratio between the two sides of a single bisection. |
--boundary <fraction> | 0.25 | Fraction of nodes to use as boundary sources/sinks during contraction. |
--optimizing-cuts <n> | 10 | Number of candidate cuts evaluated when optimizing a single bisection. |
--small-component-size <n> | 1000 | Node-count threshold below which a component is treated as small. |
Applies live traffic data (speed and turn-penalty files) to a partitioned MLD graph. Can be run repeatedly without re-partitioning when speeds change.
osrm-customize <input.osrm> [options]| Flag | Default | Description |
|---|---|---|
--segment-speed-file <file> | CSV with nodeA,nodeB,speed columns to override edge weights. Repeatable. | |
--turn-penalty-file <file> | CSV with from_node,via_node,to_node,penalty to override turn weights. Repeatable. | |
--edge-weight-updates-over-factor <x> | 0 (disabled) | Log edges whose weight changed by more than factor x (requires --segment-speed-file). |
--parse-conditionals-from-now <utc_timestamp> | 0 (disabled) | UTC Unix timestamp from which to evaluate conditional turn restrictions. |
--time-zone-file <file> | GeoJSON file with time-zone boundaries, required for conditional restriction parsing. |
Builds a Contraction Hierarchy (CH) from the extracted graph. Use this instead of osrm-partition + osrm-customize when you don't need live traffic updates.
osrm-contract <input.osrm> [options]| Flag | Default | Description |
|---|---|---|
--segment-speed-file <file> | CSV with nodeA,nodeB,speed columns to override edge weights. Repeatable. | |
--turn-penalty-file <file> | CSV with from_node,via_node,to_node,penalty to override turn weights. Repeatable. | |
--edge-weight-updates-over-factor <x> | 0 (disabled) | Log edges whose weight changed by more than factor x. |
--parse-conditionals-from-now <utc_timestamp> | 0 (disabled) | UTC Unix timestamp for evaluating conditional turn restrictions. |
--time-zone-file <file> | GeoJSON file with time-zone boundaries, required for conditional restriction parsing. |
The HTTP server. Loads a prepared dataset and serves the OSRM HTTP API.
osrm-routed <base.osrm> [options]| Flag | Short | Default | Description |
|---|---|---|---|
--ip <address> | -i | 0.0.0.0 | IP address to listen on. |
--port <n> | -p | 5000 | TCP port to listen on. |
--keepalive-timeout <s> | -k | 5 | HTTP keep-alive timeout in seconds. |
--trial | Start up fully, then exit immediately. Useful to validate a dataset without serving traffic. |
| Flag | Short | Default | Description |
|---|---|---|---|
--algorithm <name> | -a | CH | Routing algorithm: CH (Contraction Hierarchy) or MLD (Multi-Level Dijkstra). |
--shared-memory | -s | off | Load data from a shared memory region managed by osrm-datastore. |
--mmap | -m | off | Memory-map the data files instead of loading them into RAM. |
--dataset-name <name> | Shared memory dataset name to connect to (used with --shared-memory). | ||
--disable-feature-dataset <name> | Skip loading an optional dataset to save memory. Options: ROUTE_STEPS, ROUTE_GEOMETRY. |
| Flag | Default | Description |
|---|---|---|
--max-viaroute-size <n> | 500 | Maximum number of waypoints in a route query. |
--max-trip-size <n> | 100 | Maximum number of locations in a trip query. |
--max-table-size <n> | 100 | Maximum number of locations in a table query. |
--max-matching-size <n> | 100 | Maximum number of locations in a map-matching query. |
--max-nearest-size <n> | 100 | Maximum number of results in a nearest query. |
--max-alternatives <n> | 3 | Maximum number of alternative routes (MLD only). |
--max-matching-radius <m> | -1 (unlimited) | Maximum search radius in metres for map-matching. |
--default-radius <m> | -1 (unlimited) | Default snap radius for all queries. |
--max-header-size <bytes> | 0 (auto) | Maximum HTTP header size in bytes. |
Loads a prepared dataset into shared memory so that one or more osrm-routed processes can serve it with zero-copy access. Enables live traffic updates without restarting the server.
osrm-datastore [options] <base.osrm>| Flag | Short | Default | Description |
|---|---|---|---|
--dataset-name <name> | Name for this dataset in shared memory. Allows multiple datasets to coexist. | ||
--max-wait <s> | -1 (unlimited) | Seconds to wait for a running update to finish before forcibly acquiring the lock. | |
--only-metric | Reload only the metric (weights/durations) without replacing the full dataset. Optimized for frequent traffic updates. | ||
--disable-feature-dataset <name> | Skip loading an optional dataset. Options: ROUTE_STEPS, ROUTE_GEOMETRY. | ||
--remove-locks | -r | Remove stale shared-memory locks and exit. | |
--spring-clean | -s | Remove all OSRM shared memory regions and exit. | |
--list | List all datasets currently loaded in shared memory. | ||
--list-blocks | List all shared memory blocks currently in use. |
There is experimental support for building OSRM on Windows.
You will need a modern Windows development stack (e.g. Visual Studio 17). The published binaries are built with Windows Server 2025 Github hosted runners.
Dependencies are managed via vcpkg in manifest mode (see vcpkg.json at the repo root). The baseline commit is pinned in vcpkg-configuration.json.
git clone https://github.com/microsoft/vcpkg.git C:\\vcpkg
+C:\\vcpkg\\bootstrap-vcpkg.bat
+set VCPKG_ROOT=C:\\vcpkgFrom a x64 Native Tools Command Prompt for VS 2022 at the repo root:
cmake --preset ci-windows -DENABLE_NODE_BINDINGS=ON
+cmake --build --preset ci-windowsThe first configure will build every dependency from source, which takes a while. Subsequent configures reuse vcpkg's binary cache.
`,12)])])}const g=i(t,[["render",o]]);export{u as __pageData,g as default}; diff --git a/docs/v26.6.1/assets/windows-deps.md.CzNt0g42.lean.js b/docs/v26.6.1/assets/windows-deps.md.CzNt0g42.lean.js new file mode 100644 index 0000000..df213d2 --- /dev/null +++ b/docs/v26.6.1/assets/windows-deps.md.CzNt0g42.lean.js @@ -0,0 +1 @@ +import{_ as i,o as s,c as a,ag as n}from"./chunks/framework.D4c47gkQ.js";const u=JSON.parse('{"title":"Building OSRM for Windows","description":"","frontmatter":{},"headers":[],"relativePath":"windows-deps.md","filePath":"windows-deps.md"}'),t={name:"windows-deps.md"};function o(r,e,d,l,p,h){return s(),a("div",null,[...e[0]||(e[0]=[n("",12)])])}const g=i(t,[["render",o]]);export{u as __pageData,g as default}; diff --git a/docs/v26.6.1/cucumber.html b/docs/v26.6.1/cucumber.html new file mode 100644 index 0000000..d3be31f --- /dev/null +++ b/docs/v26.6.1/cucumber.html @@ -0,0 +1,25 @@ + + + + + +The Open Source Routing Machine - High performance routing engine for OpenStreetMap data
OSRM provides powerful routing services through both HTTP and Node.js APIs: