Skip to content

Commit

Permalink
Add Flatbuffers support to NodeJS bindings (#6338)
Browse files Browse the repository at this point in the history
  • Loading branch information
SiarheiFedartsou authored Aug 29, 2022
1 parent 06719be commit b4142cf
Show file tree
Hide file tree
Showing 10 changed files with 238 additions and 38 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Unreleased
- Changes from 5.26.0
- API:
- ADDED: Add Flatbuffers support to NodeJS bindings. [#6338](https://github.com/Project-OSRM/osrm-backend/pull/6338)
- CHANGED: Add `data_version` field to responses of all services. [#5387](https://github.com/Project-OSRM/osrm-backend/pull/5387)
- FIXED: Use Boost.Beast to parse HTTP request. [#6294](https://github.com/Project-OSRM/osrm-backend/pull/6294)
- FIXED: Fix inefficient osrm-routed connection handling [#6113](https://github.com/Project-OSRM/osrm-backend/pull/6113)
Expand Down
2 changes: 1 addition & 1 deletion docs/http.md
Original file line number Diff line number Diff line change
Expand Up @@ -956,7 +956,7 @@ The object is used to describe the waypoint on a route.
The 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.

Expand Down
19 changes: 12 additions & 7 deletions docs/nodejs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ Returns the fastest route between two or more coordinates while visiting the way
- `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`.
`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.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.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.skip_waypoints` **[Boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/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](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)**
Expand Down Expand Up @@ -99,6 +100,7 @@ Note: `coordinates` in the general options only supports a single `{longitude},{
- `options.number` **[Number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/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](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)?** Keep waypoints on curb side. Can be `null` (unrestricted, default) or `curb`.
- `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.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)**

Expand Down Expand Up @@ -334,12 +336,15 @@ 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
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.
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. 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**

Expand All @@ -351,7 +356,7 @@ var options = {
[13.374481201171875, 52.506191342034576]
]
};
osrm.route(options, { format: "json_buffer" }, function(err, response) {
osrm.route(options, { format: "buffer" }, function(err, response) {
if (err) throw err;
console.log(response.toString("utf-8"));
});
Expand Down
78 changes: 68 additions & 10 deletions include/nodejs/node_osrm_support.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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>
Expand All @@ -26,6 +26,7 @@
#include <iostream>
#include <iterator>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>

Expand All @@ -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>;
Expand Down Expand Up @@ -96,6 +97,17 @@ 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());

if (result_status == osrm::Status::Error)
{
BOOST_ASSERT(fbs_result->code());
throw std::logic_error(fbs_result->code()->message()->c_str());
}
}

inline engine_config_ptr argumentsToEngineConfig(const Nan::FunctionCallbackInfo<v8::Value> &args)
{
Expand Down Expand Up @@ -725,6 +737,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;
}

Expand Down Expand Up @@ -885,17 +927,18 @@ 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())
{
return {};
// output to buffer by default for Flatbuffers
return {output_format == osrm::engine::api::BaseParameters::OutputFormatType::FLATBUFFERS};
}
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())
Expand All @@ -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 {};
}

Expand All @@ -914,20 +957,35 @@ 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 {true};
}
return {false};
}
else if (format_str == "buffer")
{
return {true};
}
else if (format_str == "json_buffer")
{
if (output_format &&
output_format != osrm::engine::api::BaseParameters::OutputFormatType::JSON)
{
Nan::ThrowError("Deprecated `json_buffer` can only be used with JSON format");
}
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 {};
}
}

return {};
// output to buffer by default for Flatbuffers
return {output_format == osrm::engine::api::BaseParameters::OutputFormatType::FLATBUFFERS};
}

inline route_parameters_ptr
Expand Down
66 changes: 46 additions & 20 deletions src/nodejs/node_osrm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#include <exception>
#include <sstream>
#include <stdexcept>
#include <type_traits>
#include <utility>

Expand Down Expand Up @@ -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());

Expand All @@ -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)
Expand Down Expand Up @@ -299,6 +320,7 @@ inline void asyncForTiles(const Nan::FunctionCallbackInfo<v8::Value> &info,
* @param {Array} [options.approaches] Keep waypoints on curb side. Can be `null` (unrestricted, default) or `curb`.
* `null`/`true`/`false`
* @param {Array} [options.waypoints] Indices to coordinates to treat as waypoints. If not supplied, all coordinates are waypoints. Must include first and last coordinate index.
* @param {String} [options.format] Which output format to use, either `json`, or [`flatbuffers`](https://github.com/Project-OSRM/osrm-backend/tree/master/include/engine/api/flatbuffers).
* @param {String} [options.snapping] 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.
* @param {Boolean} [options.skip_waypoints=false] 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.
* @param {Function} callback
Expand Down Expand Up @@ -340,6 +362,7 @@ NAN_METHOD(Engine::route) //
* @param {Number} [options.number=1] Number of nearest segments that should be returned.
* Must be an integer greater than or equal to `1`.
* @param {Array} [options.approaches] Keep waypoints on curb side. Can be `null` (unrestricted, default) or `curb`.
* @param {String} [options.format] Which output format to use, either `json`, or [`flatbuffers`](https://github.com/Project-OSRM/osrm-backend/tree/master/include/engine/api/flatbuffers).
* @param {String} [options.snapping] 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.
* @param {Function} callback
*
Expand Down Expand Up @@ -606,12 +629,15 @@ NAN_METHOD(Engine::trip) //
* @name Configuration
* @param {Object} [plugin_config] - Object literal containing parameters for the trip query.
* @param {String} [plugin_config.format] 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
* 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.
* 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. 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`.
*
* @example
* var osrm = new OSRM('network.osrm');
Expand All @@ -621,7 +647,7 @@ NAN_METHOD(Engine::trip) //
* [13.374481201171875, 52.506191342034576]
* ]
* };
* osrm.route(options, { format: "json_buffer" }, function(err, response) {
* osrm.route(options, { format: "buffer" }, function(err, response) {
* if (err) throw err;
* console.log(response.toString("utf-8"));
* });
Expand Down
18 changes: 18 additions & 0 deletions test/nodejs/match.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading

0 comments on commit b4142cf

Please sign in to comment.