Add Flatbuffers support to NodeJS bindings

This commit is contained in:
Siarhei Fedartsou 2022-08-27 14:00:36 +02:00
parent df3c553f4f
commit 89136340f9
9 changed files with 212 additions and 32 deletions

View File

@ -956,7 +956,7 @@ Object used to describe waypoint on a route.
Default response format is `json`, but OSRM supports binary [`flatbuffers`](https://google.github.io/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
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.

View File

@ -64,6 +64,7 @@ Returns the fastest route between two or more coordinates while visiting the way
`null`/`true`/`false`
- `options.waypoints` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/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](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/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.format` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** Which output format to use, either `json`, or [`flatbuffers`](https://github.com/Project-OSRM/osrm-backend/tree/master/include/engine/api/flatbuffers).
- `callback` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)**
**Examples**
@ -98,7 +99,8 @@ Note: `coordinates` in the general options only supports a single `{longitude},{
Must be an integer greater than or equal to `1`. (optional, default `1`)
- `options.approaches` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)?** Keep waypoints on curb side. Can be `null` (unrestricted, default) or `curb`.
- `options.snapping` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/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](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)**
- `options.format` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** Which output format to use, either `json`, or [`flatbuffers`](https://github.com/Project-OSRM/osrm-backend/tree/master/include/engine/api/flatbuffers).
- `callback` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)**
**Examples**
@ -140,8 +142,9 @@ Optionally returns distance table.
- `options.fallback_coordinate` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/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](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)?** Multiply the table duration values in the table by this number for more controlled input into a route optimization solver.
- `options.snapping` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/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.format` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** Which output format to use, either `json`, or [`flatbuffers`](https://github.com/Project-OSRM/osrm-backend/tree/master/include/engine/api/flatbuffers).
- `options.annotations` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/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](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)**
- `callback` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)**
**Examples**
@ -225,7 +228,8 @@ if they can not be matched successfully.
- `options.tidy` **[Boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Allows the input track modification to obtain better matching quality for noisy tracks. (optional, default `false`)
- `options.waypoints` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/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](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/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](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)**
- `options.format` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** Which output format to use, either `json`, or [`flatbuffers`](https://github.com/Project-OSRM/osrm-backend/tree/master/include/engine/api/flatbuffers).
- `callback` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)**
**Examples**
@ -294,7 +298,8 @@ Right now, the following combinations are possible:
- `options.destination` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** Return route ends at `any` or `last` coordinate. (optional, default `any`)
- `options.approaches` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)?** Keep waypoints on curb side. Can be `null` (unrestricted, default) or `curb`.
- `options.snapping` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/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](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)**
- `options.format` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** Which output format to use, either `json`, or [`flatbuffers`](https://github.com/Project-OSRM/osrm-backend/tree/master/include/engine/api/flatbuffers).
- `callback` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)**
**Examples**
@ -332,12 +337,13 @@ specific behaviours.
- `plugin_config` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Object literal containing parameters for the trip query.
- `plugin_config.format` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** The format of the result object to various API calls.
Valid options are `object` (default), which returns a
standard Javascript object, as described above, and `json_buffer`, which will return a NodeJS
**[Buffer](https://nodejs.org/api/buffer.html)** object, containing a JSON string. The latter has
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](https://nodejs.org/api/buffer.html)** 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.
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`.
**Examples**

View File

@ -2,8 +2,7 @@
#define OSRM_BINDINGS_NODE_SUPPORT_HPP
#include "nodejs/json_v8_renderer.hpp"
#include "util/json_renderer.hpp"
#include "engine/api/flatbuffers/fbresult_generated.h"
#include "osrm/approach.hpp"
#include "osrm/bearing.hpp"
#include "osrm/coordinate.hpp"
@ -18,6 +17,7 @@
#include "osrm/table_parameters.hpp"
#include "osrm/tile_parameters.hpp"
#include "osrm/trip_parameters.hpp"
#include "util/json_renderer.hpp"
#include <boost/assert.hpp>
#include <boost/optional.hpp>
@ -26,6 +26,7 @@
#include <iostream>
#include <iterator>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
@ -46,7 +47,7 @@ using table_parameters_ptr = std::unique_ptr<osrm::TableParameters>;
struct PluginParameters
{
bool renderJSONToBuffer = false;
bool renderToBuffer = false;
};
using ObjectOrString = typename mapbox::util::variant<osrm::json::Object, std::string>;
@ -96,6 +97,18 @@ inline void ParseResult(const osrm::Status &result_status, osrm::json::Object &r
}
inline void ParseResult(const osrm::Status & /*result_status*/, const std::string & /*unused*/) {}
inline void ParseResult(const osrm::Status &result_status,
const flatbuffers::FlatBufferBuilder &fbs_builder)
{
auto fbs_result = osrm::engine::api::fbresult::GetFBResult(fbs_builder.GetBufferPointer());
BOOST_ASSERT(fb->code());
if (result_status == osrm::Status::Error)
{
throw std::logic_error(fbs_result->code()->message()->c_str());
}
}
inline engine_config_ptr argumentsToEngineConfig(const Nan::FunctionCallbackInfo<v8::Value> &args)
{
@ -725,6 +738,36 @@ inline bool argumentsToParameter(const Nan::FunctionCallbackInfo<v8::Value> &arg
}
}
if (Nan::Has(obj, Nan::New("format").ToLocalChecked()).FromJust())
{
v8::Local<v8::Value> format =
Nan::Get(obj, Nan::New("format").ToLocalChecked()).ToLocalChecked();
if (format.IsEmpty())
{
return false;
}
if (!format->IsString())
{
Nan::ThrowError("format must be a string: \"json\" or \"flatbuffers\"");
return false;
}
std::string format_str = *Nan::Utf8String(format);
if (format_str == "json")
{
params->format = osrm::engine::api::BaseParameters::OutputFormatType::JSON;
}
else if (format_str == "flatbuffers")
{
params->format = osrm::engine::api::BaseParameters::OutputFormatType::FLATBUFFERS;
}
else
{
Nan::ThrowError("format must be a string: \"json\" or \"flatbuffers\"");
return false;
}
}
return true;
}
@ -885,8 +928,9 @@ inline bool parseCommonParameters(const v8::Local<v8::Object> &obj, ParamType &p
return true;
}
inline PluginParameters
argumentsToPluginParameters(const Nan::FunctionCallbackInfo<v8::Value> &args)
inline PluginParameters argumentsToPluginParameters(
const Nan::FunctionCallbackInfo<v8::Value> &args,
const boost::optional<osrm::engine::api::BaseParameters::OutputFormatType> &output_format = {})
{
if (args.Length() < 3 || !args[1]->IsObject())
{
@ -895,7 +939,6 @@ argumentsToPluginParameters(const Nan::FunctionCallbackInfo<v8::Value> &args)
v8::Local<v8::Object> obj = Nan::To<v8::Object>(args[1]).ToLocalChecked();
if (Nan::Has(obj, Nan::New("format").ToLocalChecked()).FromJust())
{
v8::Local<v8::Value> format =
Nan::Get(obj, Nan::New("format").ToLocalChecked()).ToLocalChecked();
if (format.IsEmpty())
@ -905,7 +948,7 @@ argumentsToPluginParameters(const Nan::FunctionCallbackInfo<v8::Value> &args)
if (!format->IsString())
{
Nan::ThrowError("format must be a string: \"object\" or \"json_buffer\"");
Nan::ThrowError("format must be a string: \"object\" or \"buffer\"");
return {};
}
@ -914,19 +957,27 @@ argumentsToPluginParameters(const Nan::FunctionCallbackInfo<v8::Value> &args)
if (format_str == "object")
{
if (output_format == osrm::engine::api::BaseParameters::OutputFormatType::FLATBUFFERS)
{
Nan::ThrowError("Flatbuffers result can only output to buffer.");
}
return {false};
}
else if (format_str == "json_buffer")
else if (format_str == "buffer" || format_str == "json_buffer")
{
return {true};
}
else
{
Nan::ThrowError("format must be a string: \"object\" or \"json_buffer\"");
Nan::ThrowError("format must be a string: \"object\" or \"buffer\"");
return {};
}
}
// output to buffer by default for Flatbuffers
if (output_format == osrm::engine::api::BaseParameters::OutputFormatType::FLATBUFFERS)
{
return {true};
}
return {};
}

View File

@ -10,6 +10,7 @@
#include <exception>
#include <sstream>
#include <stdexcept>
#include <type_traits>
#include <utility>
@ -128,8 +129,7 @@ inline void async(const Nan::FunctionCallbackInfo<v8::Value> &info,
auto params = argsToParams(info, requires_multiple_coordinates);
if (!params)
return;
auto pluginParams = argumentsToPluginParameters(info);
auto pluginParams = argumentsToPluginParameters(info, params->format);
BOOST_ASSERT(params->IsValid());
@ -156,20 +156,41 @@ inline void async(const Nan::FunctionCallbackInfo<v8::Value> &info,
void Execute() override
try
{
osrm::engine::api::ResultT r;
r = osrm::util::json::Object();
const auto status = ((*osrm).*(service))(*params, r);
auto json_result = r.get<osrm::json::Object>();
ParseResult(status, json_result);
if (pluginParams.renderJSONToBuffer)
switch (
params->format.value_or(osrm::engine::api::BaseParameters::OutputFormatType::JSON))
{
std::ostringstream buf;
osrm::util::json::render(buf, json_result);
result = buf.str();
case osrm::engine::api::BaseParameters::OutputFormatType::JSON:
{
osrm::engine::api::ResultT r;
r = osrm::util::json::Object();
const auto status = ((*osrm).*(service))(*params, r);
auto json_result = r.get<osrm::json::Object>();
ParseResult(status, json_result);
if (pluginParams.renderToBuffer)
{
std::ostringstream buf;
osrm::util::json::render(buf, json_result);
result = buf.str();
}
else
{
result = json_result;
}
}
else
break;
case osrm::engine::api::BaseParameters::OutputFormatType::FLATBUFFERS:
{
result = json_result;
osrm::engine::api::ResultT r = flatbuffers::FlatBufferBuilder();
const auto status = ((*osrm).*(service))(*params, r);
const auto &fbs_result = r.get<flatbuffers::FlatBufferBuilder>();
ParseResult(status, fbs_result);
BOOST_ASSERT(pluginParams.renderToBuffer);
std::string result_str(
reinterpret_cast<const char *>(fbs_result.GetBufferPointer()),
fbs_result.GetSize());
result = std::move(result_str);
}
break;
}
}
catch (const std::exception &e)

View File

@ -4,6 +4,24 @@ var data_path = require('./constants').data_path;
var mld_data_path = require('./constants').mld_data_path;
var three_test_coordinates = require('./constants').three_test_coordinates;
var two_test_coordinates = require('./constants').two_test_coordinates;
const flatbuffers = require('../../features/support/flatbuffers').flatbuffers;
const FBResult = require('../../features/support/fbresult_generated').osrm.engine.api.fbresult.FBResult;
test('match: match in Monaco with flatbuffers format', function(assert) {
assert.plan(2);
var osrm = new OSRM(data_path);
var options = {
coordinates: three_test_coordinates,
timestamps: [1424684612, 1424684616, 1424684620],
format: 'flatbuffers'
};
osrm.match(options, function(err, response) {
assert.ifError(err);
const fb = FBResult.getRootAsFBResult(new flatbuffers.ByteBuffer(response));
assert.equal(fb.routesLength(), 1);
});
});
test('match: match in Monaco', function(assert) {
assert.plan(5);

View File

@ -4,8 +4,26 @@ var data_path = require('./constants').data_path;
var mld_data_path = require('./constants').mld_data_path;
var three_test_coordinates = require('./constants').three_test_coordinates;
var two_test_coordinates = require('./constants').two_test_coordinates;
const flatbuffers = require('../../features/support/flatbuffers').flatbuffers;
const FBResult = require('../../features/support/fbresult_generated').osrm.engine.api.fbresult.FBResult;
test('nearest with flatbuffers format', function(assert) {
assert.plan(5);
var osrm = new OSRM(data_path);
osrm.nearest({
coordinates: [three_test_coordinates[0]],
format: 'flatbuffers'
}, function(err, result) {
assert.ifError(err);
assert.ok(result instanceof Buffer);
const fb = FBResult.getRootAsFBResult(new flatbuffers.ByteBuffer(result));
assert.equals(fb.waypointsLength(), 1);
assert.ok(fb.waypoints(0).location());
assert.ok(fb.waypoints(0).name());
});
});
test('nearest', function(assert) {
assert.plan(4);
var osrm = new OSRM(data_path);

View File

@ -5,6 +5,43 @@ var monaco_mld_path = require('./constants').mld_data_path;
var monaco_corech_path = require('./constants').corech_data_path;
var three_test_coordinates = require('./constants').three_test_coordinates;
var two_test_coordinates = require('./constants').two_test_coordinates;
const flatbuffers = require('../../features/support/flatbuffers').flatbuffers;
const FBResult = require('../../features/support/fbresult_generated').osrm.engine.api.fbresult.FBResult;
test('route: routes Monaco and can return result in flatbuffers', function(assert) {
assert.plan(5);
var osrm = new OSRM(monaco_path);
osrm.route({coordinates: two_test_coordinates, format: 'flatbuffers'}, function(err, result) {
assert.ifError(err);
assert.ok(result instanceof Buffer);
const fb = FBResult.getRootAsFBResult(new flatbuffers.ByteBuffer(result));
assert.equals(fb.waypointsLength(), 2);
assert.equals(fb.routesLength(), 1);
assert.ok(fb.routes(0).polyline);
});
});
test('route: routes Monaco and can return result in flatbuffers if output format is passed explicitly', function(assert) {
assert.plan(5);
var osrm = new OSRM(monaco_path);
osrm.route({coordinates: two_test_coordinates, format: 'flatbuffers'}, {output: 'buffer'}, function(err, result) {
assert.ifError(err);
assert.ok(result instanceof Buffer);
var buf = new flatbuffers.ByteBuffer(result);
const fb = FBResult.getRootAsFBResult(buf);
assert.equals(fb.waypointsLength(), 2);
assert.equals(fb.routesLength(), 1);
assert.ok(fb.routes(0).polyline);
});
});
test('route: throws error if required output in object in flatbuffers format', function(assert) {
assert.plan(1);
var osrm = new OSRM(monaco_path);
assert.throws(function() {
osrm.route({coordinates: two_test_coordinates, format: 'flatbuffers'}, {format: 'object'}, function(err, result) {});
});
});
test('route: routes Monaco', function(assert) {

View File

@ -4,6 +4,24 @@ var data_path = require('./constants').data_path;
var mld_data_path = require('./constants').mld_data_path;
var three_test_coordinates = require('./constants').three_test_coordinates;
var two_test_coordinates = require('./constants').two_test_coordinates;
const flatbuffers = require('../../features/support/flatbuffers').flatbuffers;
const FBResult = require('../../features/support/fbresult_generated').osrm.engine.api.fbresult.FBResult;
test('table: flatbuffer format', function(assert) {
assert.plan(3);
var osrm = new OSRM(data_path);
var options = {
coordinates: [three_test_coordinates[0], three_test_coordinates[1]],
format: 'flatbuffers'
};
osrm.table(options, function(err, table) {
assert.ifError(err);
assert.ok(table instanceof Buffer);
const fb = FBResult.getRootAsFBResult(new flatbuffers.ByteBuffer(table));
assert.ok(fb.table());
});
});
test('table: test annotations paramater combination', function(assert) {
assert.plan(12);

View File

@ -4,7 +4,18 @@ var data_path = require('./constants').data_path;
var mld_data_path = require('./constants').mld_data_path;
var three_test_coordinates = require('./constants').three_test_coordinates;
var two_test_coordinates = require('./constants').two_test_coordinates;
const flatbuffers = require('../../features/support/flatbuffers').flatbuffers;
const FBResult = require('../../features/support/fbresult_generated').osrm.engine.api.fbresult.FBResult;
test('trip: trip in Monaco with flatbuffers format', function(assert) {
assert.plan(2);
var osrm = new OSRM(data_path);
osrm.trip({coordinates: two_test_coordinates, format: 'flatbuffers'}, function(err, trip) {
assert.ifError(err);
const fb = FBResult.getRootAsFBResult(new flatbuffers.ByteBuffer(trip));
assert.equal(fb.routesLength(), 1);
});
});
test('trip: trip in Monaco', function(assert) {
assert.plan(2);