From ea36088732c7b3100ffea1192ca3978c10c767bd Mon Sep 17 00:00:00 2001 From: Patrick Niklaus Date: Thu, 19 May 2016 18:13:59 +0200 Subject: [PATCH] Refactors bindinds to use async workers This fixes concurrency issues reported in #189. References: - https://github.com/nodejs/nan#asynchronous-work-helpers - https://github.com/nodejs/nan/blob/master/doc/asyncworker.md#api_nan_async_worker - https://github.com/nodejs/nan/blob/master/doc/asyncworker.md#api_nan_async_queue_worker --- src/json_v8_renderer.hpp | 1 - src/node_osrm.cpp | 1182 +++---------------------------------- src/node_osrm.hpp | 39 ++ src/node_osrm_support.hpp | 760 ++++++++++++++++++++++++ test/index.js | 6 - 5 files changed, 896 insertions(+), 1092 deletions(-) create mode 100644 src/node_osrm.hpp create mode 100644 src/node_osrm_support.hpp diff --git a/src/json_v8_renderer.hpp b/src/json_v8_renderer.hpp index 829a785..f01dd9f 100644 --- a/src/json_v8_renderer.hpp +++ b/src/json_v8_renderer.hpp @@ -92,7 +92,6 @@ inline void renderToV8(v8::Local &out, const osrm::json::Object &obje osrm::json::Value value = object; mapbox::util::apply_visitor(V8Renderer(out), value); } - } #endif // JSON_V8_RENDERER_HPP diff --git a/src/node_osrm.cpp b/src/node_osrm.cpp index 6910edf..db8647d 100644 --- a/src/node_osrm.cpp +++ b/src/node_osrm.cpp @@ -1,1161 +1,173 @@ -#include "json_v8_renderer.hpp" - -// v8 -#include - -// OSRM -#include #include -#include #include + +#include +#include #include #include -#include -#include -#include #include -#include -#include - -#include -#include +#include -// STL -#include +#include #include -#include -#include -#include +#include #include -#include -#include + +#include "node_osrm.hpp" +#include "node_osrm_support.hpp" namespace node_osrm { -using osrm_ptr = std::unique_ptr; -using engine_config_ptr = std::unique_ptr; -using route_parameters_ptr = std::unique_ptr; -using trip_parameters_ptr = std::unique_ptr; -using tile_parameters_ptr = std::unique_ptr; -using match_parameters_ptr = std::unique_ptr; -using nearest_parameters_ptr = std::unique_ptr; -using table_parameters_ptr = std::unique_ptr; -namespace -{ -template std::unique_ptr make_unique(Types &&... Args) -{ - return (std::unique_ptr(new T(std::forward(Args)...))); -} -} +Engine::Engine(osrm::EngineConfig &config) : Base(), this_(std::make_shared(config)) {} -// Supports -engine_config_ptr argumentsToEngineConfig(const Nan::FunctionCallbackInfo &args) +Nan::Persistent &Engine::constructor() { - Nan::HandleScope scope; - auto engine_config = make_unique(); - - if (args.Length() == 0) - { - return engine_config; - } - else if (args.Length() > 1) - { - Nan::ThrowError("Only accepts one parameter"); - return engine_config_ptr(); - } - - BOOST_ASSERT(args.Length() == 1); - - if (args[0]->IsString()) - { - engine_config->storage_config = osrm::StorageConfig( - *v8::String::Utf8Value(Nan::To(args[0]).ToLocalChecked())); - engine_config->use_shared_memory = false; - return engine_config; - } - else if (!args[0]->IsObject()) - { - Nan::ThrowError("Parameter must be a path or options object"); - return engine_config_ptr(); - } - - BOOST_ASSERT(args[0]->IsObject()); - auto params = Nan::To(args[0]).ToLocalChecked(); - - auto path = params->Get(Nan::New("path").ToLocalChecked()); - auto shared_memory = params->Get(Nan::New("shared_memory").ToLocalChecked()); - if (!path->IsUndefined()) - { - engine_config->storage_config = osrm::StorageConfig( - *v8::String::Utf8Value(Nan::To(path).ToLocalChecked())); - } - if (!shared_memory->IsUndefined()) - { - if (shared_memory->IsBoolean()) - { - engine_config->use_shared_memory = Nan::To(shared_memory).FromJust(); - } - else - { - Nan::ThrowError("Shared_memory option must be a boolean"); - return engine_config_ptr(); - } - } - - if (path->IsUndefined() && !engine_config->use_shared_memory) - { - Nan::ThrowError("Shared_memory must be enabled if no path is " - "specified"); - return engine_config_ptr(); - } - - return engine_config; + static Nan::Persistent init; + return init; } -boost::optional> -parseCoordinateArray(const v8::Local &coordinates_array) +NAN_MODULE_INIT(Engine::Init) { - Nan::HandleScope scope; - boost::optional> resulting_coordinates; - std::vector temp_coordinates; + const auto whoami = Nan::New("OSRM").ToLocalChecked(); - for (uint32_t i = 0; i < coordinates_array->Length(); ++i) - { - v8::Local coordinate = coordinates_array->Get(i); + auto fnTp = Nan::New(New); + fnTp->InstanceTemplate()->SetInternalFieldCount(1); + fnTp->SetClassName(whoami); - if (!coordinate->IsArray()) - { - Nan::ThrowError("Coordinates must be an array of (lon/lat) pairs"); - return resulting_coordinates; - } - - v8::Local coordinate_pair = v8::Local::Cast(coordinate); - if (coordinate_pair->Length() != 2) - { - Nan::ThrowError("Coordinates must be an array of (lon/lat) pairs"); - return resulting_coordinates; - } - - if (!coordinate_pair->Get(0)->IsNumber() || !coordinate_pair->Get(1)->IsNumber()) - { - Nan::ThrowError("Each member of a coordinate pair must be a number"); - return resulting_coordinates; - } - - double lon = coordinate_pair->Get(0)->NumberValue(); - double lat = coordinate_pair->Get(1)->NumberValue(); - - if (std::isnan(lon) || std::isnan(lat) || std::isinf(lon) || std::isinf(lat)) - { - Nan::ThrowError("Lng/Lat coordinates must be valid numbers"); - return resulting_coordinates; - } + SetPrototypeMethod(fnTp, "route", route); + SetPrototypeMethod(fnTp, "nearest", nearest); + SetPrototypeMethod(fnTp, "table", table); + SetPrototypeMethod(fnTp, "tile", tile); + SetPrototypeMethod(fnTp, "match", match); + SetPrototypeMethod(fnTp, "trip", trip); - if (lon > 180 || lon < -180 || lat > 90 || lat < -90) - { - Nan::ThrowError("Lng/Lat coordinates must be within world bounds " - "(-180 < lng < 180, -90 < lat < 90)"); - return resulting_coordinates; - } + const auto fn = Nan::GetFunction(fnTp).ToLocalChecked(); - temp_coordinates.emplace_back(osrm::util::FloatLongitude(std::move(lon)), - osrm::util::FloatLatitude(std::move(lat))); - } + constructor().Reset(fn); - resulting_coordinates = boost::make_optional(std::move(temp_coordinates)); - return resulting_coordinates; + Nan::Set(target, whoami, fn); } -// Parses all the non-service specific parameters -template -bool argumentsToParameter(const Nan::FunctionCallbackInfo &args, - ParamType ¶ms, - bool requires_multiple_coordinates) +NAN_METHOD(Engine::New) { - Nan::HandleScope scope; - - if (args.Length() < 2) - { - Nan::ThrowTypeError("Two arguments required"); - return false; - } - - if (!args[0]->IsObject()) - { - Nan::ThrowTypeError("First arg must be an object"); - return false; - } - - v8::Local obj = Nan::To(args[0]).ToLocalChecked(); - - v8::Local coordinates = obj->Get(Nan::New("coordinates").ToLocalChecked()); - if (coordinates->IsUndefined()) - { - Nan::ThrowError("Must provide a coordinates property"); - return false; - } - else if (coordinates->IsArray()) - { - auto coordinates_array = v8::Local::Cast(coordinates); - if (coordinates_array->Length() < 2 && requires_multiple_coordinates) - { - Nan::ThrowError("At least two coordinates must be provided"); - return false; - } - else if (!requires_multiple_coordinates && coordinates_array->Length() != 1) - { - Nan::ThrowError("Exactly one coordinate pair must be provided"); - return false; - } - auto maybe_coordinates = parseCoordinateArray(coordinates_array); - if (maybe_coordinates) - { - std::copy(maybe_coordinates->begin(), maybe_coordinates->end(), - std::back_inserter(params->coordinates)); - } - else - { - return false; - } - } - else if (!coordinates->IsUndefined()) - { - BOOST_ASSERT(!coordinates->IsArray()); - Nan::ThrowError("Coordinates must be an array of (lon/lat) pairs"); - return false; - } - - if (obj->Has(Nan::New("bearings").ToLocalChecked())) + if (info.IsConstructCall()) { - v8::Local bearings = obj->Get(Nan::New("bearings").ToLocalChecked()); - - if (!bearings->IsArray()) + try { - Nan::ThrowError("Bearings must be an array of arrays of numbers"); - return false; - } + auto config = argumentsToEngineConfig(info); + if (!config) + return; - auto bearings_array = v8::Local::Cast(bearings); - - if (bearings_array->Length() != params->coordinates.size()) - { - Nan::ThrowError("Bearings array must have the same length as coordinates array"); - return false; + auto *const self = new Engine(*config); + self->Wrap(info.This()); } - - for (uint32_t i = 0; i < bearings_array->Length(); ++i) + catch (const std::exception &ex) { - v8::Local bearing_raw = bearings_array->Get(i); - - if (bearing_raw->IsNull()) - { - params->bearings.emplace_back(); - } - else if (bearing_raw->IsArray()) - { - auto bearing_pair = v8::Local::Cast(bearing_raw); - if (bearing_pair->Length() == 2) - { - if (!bearing_pair->Get(0)->IsNumber() || !bearing_pair->Get(1)->IsNumber()) - { - Nan::ThrowError("Bearing values need to be numbers in range 0..360"); - return false; - } - - short bearing_first = static_cast(bearing_pair->Get(0)->NumberValue()); - short bearing_second = static_cast(bearing_pair->Get(1)->NumberValue()); - - if (bearing_first < 0 || bearing_first >= 360 || bearing_second < 0 || - bearing_second >= 360) - { - Nan::ThrowError("Bearing values need to be in range 0..360"); - return false; - } - - params->bearings.push_back(osrm::Bearing{bearing_first, bearing_second}); - } - else - { - Nan::ThrowError("Bearing must be an array of [bearing, range] or null"); - return false; - } - } - else - { - Nan::ThrowError("Bearing must be an array of [bearing, range] or null"); - return false; - } + return Nan::ThrowTypeError(ex.what()); } - } - if (obj->Has(Nan::New("hints").ToLocalChecked())) - { - v8::Local hints = obj->Get(Nan::New("hints").ToLocalChecked()); - - if (!hints->IsArray()) - { - Nan::ThrowError("Hints must be an array of strings/null"); - return false; - } - - v8::Local hints_array = v8::Local::Cast(hints); - - if (hints_array->Length() != params->coordinates.size()) - { - Nan::ThrowError("Hints array must have the same length as coordinates array"); - return false; - } - - for (uint32_t i = 0; i < hints_array->Length(); ++i) - { - v8::Local hint = hints_array->Get(i); - if (hint->IsString()) - { - if (hint->ToString()->Length() == 0) - { - Nan::ThrowError("Hint cannot be an empty string"); - return false; - } - - params->hints.push_back( - osrm::engine::Hint::FromBase64(*v8::String::Utf8Value(hint))); - } - else if (hint->IsNull()) - { - params->hints.emplace_back(); - } - else - { - Nan::ThrowError("Hint must be null or string"); - return false; - } - } + info.GetReturnValue().Set(info.This()); } - - if (obj->Has(Nan::New("radiuses").ToLocalChecked())) - { - v8::Local radiuses = obj->Get(Nan::New("radiuses").ToLocalChecked()); - - if (!radiuses->IsArray()) - { - Nan::ThrowError("Radiuses must be an array of non-negative doubles or null"); - return false; - } - - v8::Local radiuses_array = v8::Local::Cast(radiuses); - - if (radiuses_array->Length() != params->coordinates.size()) - { - Nan::ThrowError("Radiuses array must have the same length as coordinates array"); - return false; - } - - for (uint32_t i = 0; i < radiuses_array->Length(); ++i) - { - v8::Local radius = radiuses_array->Get(i); - if (radius->IsNull()) - { - params->radiuses.emplace_back(); - } - else if (radius->IsNumber() && radius->NumberValue() >= 0) - { - params->radiuses.push_back(static_cast(radius->NumberValue())); - } - else - { - Nan::ThrowError("Radius must be non-negative double or null"); - return false; - } - } - } - - return true; -} - -template -bool parseCommonParameters(const v8::Local obj, ParamType ¶ms) -{ - if (obj->Has(Nan::New("steps").ToLocalChecked())) - { - params->steps = obj->Get(Nan::New("steps").ToLocalChecked())->BooleanValue(); - } - - if (obj->Has(Nan::New("geometries").ToLocalChecked())) - { - v8::Local geometries = obj->Get(Nan::New("geometries").ToLocalChecked()); - - if (!geometries->IsString()) - { - Nan::ThrowError("Geometries must be a string: [polyline, geojson]"); - return false; - } - - std::string geometries_str = *v8::String::Utf8Value(geometries); - - if (geometries_str == "polyline") - { - params->geometries = osrm::RouteParameters::GeometriesType::Polyline; - } - else if (geometries_str == "geojson") - { - params->geometries = osrm::RouteParameters::GeometriesType::GeoJSON; - } - else - { - Nan::ThrowError("'geometries' param must be one of [polyline, geojson]"); - return false; - } - } - - if (obj->Has(Nan::New("overview").ToLocalChecked())) - { - v8::Local overview = obj->Get(Nan::New("overview").ToLocalChecked()); - - if (!overview->IsString()) - { - Nan::ThrowError("Overview must be a string: [simplified, full, false]"); - return false; - } - - std::string overview_str = *v8::String::Utf8Value(overview); - - if (overview_str == "simplified") - { - params->overview = osrm::RouteParameters::OverviewType::Simplified; - } - else if (overview_str == "full") - { - params->overview = osrm::RouteParameters::OverviewType::Full; - } - else if (overview_str == "false") - { - params->overview = osrm::RouteParameters::OverviewType::False; - } - else - { - Nan::ThrowError("'overview' param must be one of [simplified, full, false]"); - return false; - } - } - - return true; -} - -route_parameters_ptr argumentsToRouteParameter(const Nan::FunctionCallbackInfo &args, - bool requires_multiple_coordinates) -{ - route_parameters_ptr params = make_unique(); - bool has_base_params = argumentsToParameter(args, params, requires_multiple_coordinates); - if (!has_base_params) - return route_parameters_ptr(); - - v8::Local obj = Nan::To(args[0]).ToLocalChecked(); - - if (obj->Has(Nan::New("continue_straight").ToLocalChecked())) - { - auto value = obj->Get(Nan::New("continue_straight").ToLocalChecked()); - if (!value->IsBoolean() && !value->IsNull()) - { - Nan::ThrowError("'continue_straight' parama must be boolean or null"); - } - if (value->IsBoolean()) - { - params->continue_straight = value->BooleanValue(); - } - } - - if (obj->Has(Nan::New("alternatives").ToLocalChecked())) - { - auto value = obj->Get(Nan::New("alternatives").ToLocalChecked()); - if (!value->IsBoolean()) - { - Nan::ThrowError("'alternatives' parama must be boolean"); - } - params->alternatives = value->BooleanValue(); - } - - bool parsedSuccessfully = parseCommonParameters(obj, params); - if (!parsedSuccessfully) - { - return route_parameters_ptr(); - } - - return params; -} - -tile_parameters_ptr argumentsToTileParameters(const Nan::FunctionCallbackInfo &args) -{ - tile_parameters_ptr params = make_unique(); - - if (args.Length() < 2) - { - Nan::ThrowTypeError("Coordinate object and callback required"); - return tile_parameters_ptr(); - } - - if (!args[0]->IsArray()) - { - Nan::ThrowTypeError("Parameter must be an array [x, y, z]"); - return tile_parameters_ptr(); - } - - v8::Local array = v8::Local::Cast(args[0]); - - if (array->Length() != 3) - { - Nan::ThrowTypeError("Parameter must be an array [x, y, z]"); - return tile_parameters_ptr(); - } - - v8::Local x = array->Get(0); - v8::Local y = array->Get(1); - v8::Local z = array->Get(2); - - if (!x->IsUint32() && !x->IsUndefined()) - { - Nan::ThrowError("Tile x coordinate must be unsigned interger"); - return tile_parameters_ptr(); - } - if (!y->IsUint32() && !y->IsUndefined()) - { - Nan::ThrowError("Tile y coordinate must be unsigned interger"); - return tile_parameters_ptr(); - } - if (!z->IsUint32() && !z->IsUndefined()) - { - Nan::ThrowError("Tile z coordinate must be unsigned interger"); - return tile_parameters_ptr(); - } - - params->x = x->Uint32Value(); - params->y = y->Uint32Value(); - params->z = z->Uint32Value(); - - return params; -} - -nearest_parameters_ptr argumentsToNearestParameter(const Nan::FunctionCallbackInfo &args, - bool requires_multiple_coordinates) -{ - nearest_parameters_ptr params = make_unique(); - bool has_base_params = argumentsToParameter(args, params, requires_multiple_coordinates); - if (!has_base_params) - return nearest_parameters_ptr(); - - v8::Local obj = Nan::To(args[0]).ToLocalChecked(); - - if (obj->Has(Nan::New("number").ToLocalChecked())) - { - v8::Local number = obj->Get(Nan::New("number").ToLocalChecked()); - - if (!number->IsUint32()) - { - Nan::ThrowError("Number must be an integer greater than or equal to 1"); - return nearest_parameters_ptr(); - } - else - { - unsigned number_value = static_cast(number->NumberValue()); - - if (number_value < 1) - { - Nan::ThrowError("Number must be an integer greater than or equal to 1"); - return nearest_parameters_ptr(); - } - - params->number_of_results = static_cast(number->NumberValue()); - } - } - - return params; -} - -table_parameters_ptr argumentsToTableParameter(const Nan::FunctionCallbackInfo &args, - bool requires_multiple_coordinates) -{ - table_parameters_ptr params = make_unique(); - bool has_base_params = argumentsToParameter(args, params, requires_multiple_coordinates); - if (!has_base_params) - return table_parameters_ptr(); - - v8::Local obj = Nan::To(args[0]).ToLocalChecked(); - - if (obj->Has(Nan::New("sources").ToLocalChecked())) + else { - v8::Local sources = obj->Get(Nan::New("sources").ToLocalChecked()); - - if (!sources->IsArray()) - { - Nan::ThrowError("Sources must be an array of indices (or undefined)"); - return table_parameters_ptr(); - } - - v8::Local sources_array = v8::Local::Cast(sources); - for (uint32_t i = 0; i < sources_array->Length(); ++i) - { - v8::Local source = sources_array->Get(i); - if (source->IsUint32()) - { - size_t source_value = static_cast(source->NumberValue()); - if (source_value > params->coordinates.size()) - { - Nan::ThrowError( - "Source indices must be less than or equal to the number of coordinates"); - return table_parameters_ptr(); - } - - params->sources.push_back(static_cast(source->NumberValue())); - } - else - { - Nan::ThrowError("Source must be an integer"); - return table_parameters_ptr(); - } - } + auto init = Nan::New(constructor()); + info.GetReturnValue().Set(init->NewInstance()); } - - if (obj->Has(Nan::New("destinations").ToLocalChecked())) - { - v8::Local destinations = obj->Get(Nan::New("destinations").ToLocalChecked()); - - if (!destinations->IsArray()) - { - Nan::ThrowError("Destinations must be an array of indices (or undefined)"); - return table_parameters_ptr(); - } - - v8::Local destinations_array = v8::Local::Cast(destinations); - for (uint32_t i = 0; i < destinations_array->Length(); ++i) - { - v8::Local destination = destinations_array->Get(i); - if (destination->IsUint32()) - { - size_t destination_value = static_cast(destination->NumberValue()); - if (destination_value > params->coordinates.size()) - { - Nan::ThrowError("Destination indices must be less than or equal to the number " - "of coordinates"); - return table_parameters_ptr(); - } - - params->destinations.push_back(static_cast(destination->NumberValue())); - } - else - { - Nan::ThrowError("Destination must be an integer"); - return table_parameters_ptr(); - } - } - } - - return params; } -trip_parameters_ptr argumentsToTripParameter(const Nan::FunctionCallbackInfo &args, - bool requires_multiple_coordinates) +template +inline void async(const Nan::FunctionCallbackInfo &info, + ParameterParser argsToParams, + ServiceMemFn service, + bool requires_multiple_coordinates) { - trip_parameters_ptr params = make_unique(); - bool has_base_params = argumentsToParameter(args, params, requires_multiple_coordinates); - if (!has_base_params) - return trip_parameters_ptr(); - - v8::Local obj = Nan::To(args[0]).ToLocalChecked(); - - bool parsedSuccessfully = parseCommonParameters(obj, params); - if (!parsedSuccessfully) - { - return trip_parameters_ptr(); - } + auto params = argsToParams(info, requires_multiple_coordinates); + if (!params) + return; - return params; -} + BOOST_ASSERT(params->IsValid()); -match_parameters_ptr argumentsToMatchParameter(const Nan::FunctionCallbackInfo &args, - bool requires_multiple_coordinates) -{ - match_parameters_ptr params = make_unique(); - bool has_base_params = argumentsToParameter(args, params, requires_multiple_coordinates); - if (!has_base_params) - return match_parameters_ptr(); + if (!info[info.Length() - 1]->IsFunction()) + return Nan::ThrowTypeError("last argument must be a callback function"); - v8::Local obj = Nan::To(args[0]).ToLocalChecked(); + auto *const self = Nan::ObjectWrap::Unwrap(info.Holder()); + using ParamPtr = decltype(params); - if (obj->Has(Nan::New("timestamps").ToLocalChecked())) + struct Worker final : Nan::AsyncWorker { - v8::Local timestamps = obj->Get(Nan::New("timestamps").ToLocalChecked()); + using Base = Nan::AsyncWorker; - if (!timestamps->IsArray()) + Worker(std::shared_ptr osrm_, + ParamPtr params_, + ServiceMemFn service, + Nan::Callback *callback) + : Base(callback), osrm{std::move(osrm_)}, service{std::move(service)}, + params{std::move(params_)} { - Nan::ThrowError("Timestamps must be an array of integers (or undefined)"); - return match_parameters_ptr(); } - v8::Local timestamps_array = v8::Local::Cast(timestamps); - - if (params->coordinates.size() != timestamps_array->Length()) + void Execute() override try { - Nan::ThrowError("Timestamp array must have the same size as the coordinates " - "array"); - return match_parameters_ptr(); + const auto status = ((*osrm).*(service))(*params, result); + ParseResult(status, result); } - - for (uint32_t i = 0; i < timestamps_array->Length(); ++i) + catch (const std::exception &e) { - v8::Local timestamp = timestamps_array->Get(i); - if (!timestamp->IsNumber()) - { - Nan::ThrowError("Timestamps array items must be numbers"); - return match_parameters_ptr(); - } - params->timestamps.emplace_back(static_cast(timestamp->NumberValue())); + SetErrorMessage(e.what()); } - } - - bool parsedSuccessfully = parseCommonParameters(obj, params); - if (!parsedSuccessfully) - { - return match_parameters_ptr(); - } - - return params; -} - -struct RouteQueryBaton; -struct NearestQueryBaton; -struct TableQueryBaton; -struct TripQueryBaton; -struct TileQueryBaton; -struct MatchQueryBaton; - -class Engine final : public Nan::ObjectWrap -{ - public: - static void Initialize(v8::Handle); - static void New(const Nan::FunctionCallbackInfo &args); - - static void route(const Nan::FunctionCallbackInfo &args); - static void nearest(const Nan::FunctionCallbackInfo &args); - static void table(const Nan::FunctionCallbackInfo &args); - static void tile(const Nan::FunctionCallbackInfo &args); - static void match(const Nan::FunctionCallbackInfo &args); - static void trip(const Nan::FunctionCallbackInfo &args); - - static void ParseResult(const osrm::engine::Status result_status_code, - osrm::json::Object& result); - - template - static void Run(const Nan::FunctionCallbackInfo &args, - ParamType params, - AsyncType async_fn, - AfterType after_fn); - - static void AsyncRunRoute(uv_work_t *); - static void AsyncRunNearest(uv_work_t *); - static void AsyncRunTable(uv_work_t *); - static void AsyncRunMatch(uv_work_t *); - static void AsyncRunTrip(uv_work_t *); - static void AsyncRunTile(uv_work_t *); - - template static void AfterRun(BatonType *closure); - - static void AfterRunRoute(uv_work_t *); - static void AfterRunNearest(uv_work_t *); - static void AfterRunTable(uv_work_t *); - static void AfterRunMatch(uv_work_t *); - static void AfterRunTrip(uv_work_t *); - static void AfterRunTile(uv_work_t *); - - private: - Engine(osrm::EngineConfig &engine_config) - : Nan::ObjectWrap(), this_(make_unique(engine_config)) - { - } - - static Nan::Persistent constructor; - osrm_ptr this_; -}; - -Nan::Persistent Engine::constructor; - -void Engine::Initialize(v8::Handle target) -{ - v8::Local function_template = Nan::New(Engine::New); - - function_template->InstanceTemplate()->SetInternalFieldCount(1); - function_template->SetClassName(Nan::New("OSRM").ToLocalChecked()); - SetPrototypeMethod(function_template, "route", route); - SetPrototypeMethod(function_template, "nearest", nearest); - SetPrototypeMethod(function_template, "table", table); - SetPrototypeMethod(function_template, "tile", tile); - SetPrototypeMethod(function_template, "match", match); - SetPrototypeMethod(function_template, "trip", trip); - - constructor.Reset(function_template->GetFunction()); - Nan::Set(target, Nan::New("OSRM").ToLocalChecked(), function_template->GetFunction()); -} - -void Engine::New(const Nan::FunctionCallbackInfo &args) -{ - Nan::HandleScope scope; - if (!args.IsConstructCall()) - { - Nan::ThrowTypeError("Cannot call constructor as function, you need to use 'new' " - "keyword"); - return; - } - - try - { - auto engine_config_ptr = argumentsToEngineConfig(args); - if (!engine_config_ptr) + void HandleOKCallback() override { - return; - } - auto engine_ptr = new Engine(*engine_config_ptr); - engine_ptr->Wrap(args.This()); - args.GetReturnValue().Set(args.This()); - } - catch (std::exception const &ex) - { - Nan::ThrowTypeError(ex.what()); - } -} - -template struct RunQueryBaton -{ - uv_work_t request; - Engine *machine; - ResultT result; - Nan::Persistent cb; - std::string error; -}; - -struct RouteQueryBaton : public RunQueryBaton -{ - route_parameters_ptr params; -}; - -struct TableQueryBaton : public RunQueryBaton -{ - table_parameters_ptr params; -}; - -struct NearestQueryBaton : public RunQueryBaton -{ - nearest_parameters_ptr params; -}; - -struct TripQueryBaton : public RunQueryBaton -{ - trip_parameters_ptr params; -}; - -struct TileQueryBaton : public RunQueryBaton -{ - tile_parameters_ptr params; -}; - -struct MatchQueryBaton : public RunQueryBaton -{ - match_parameters_ptr params; -}; - -void Engine::route(const Nan::FunctionCallbackInfo &args) -{ - Nan::HandleScope scope; - route_parameters_ptr params = argumentsToRouteParameter(args, true); - if (!params) - return; - - BOOST_ASSERT(params->IsValid()); - - Run(args, std::move(params), AsyncRunRoute, AfterRunRoute); -} - -void Engine::match(const Nan::FunctionCallbackInfo &args) -{ - Nan::HandleScope scope; - match_parameters_ptr params = argumentsToMatchParameter(args, true); - if (!params) - return; - - BOOST_ASSERT(params->IsValid()); - - Run(args, std::move(params), AsyncRunMatch, AfterRunMatch); -} - -void Engine::trip(const Nan::FunctionCallbackInfo &args) -{ - Nan::HandleScope scope; - trip_parameters_ptr params = argumentsToTripParameter(args, true); - if (!params) - return; - - BOOST_ASSERT(params->IsValid()); - - Run(args, std::move(params), AsyncRunTrip, AfterRunTrip); -} - -void Engine::table(const Nan::FunctionCallbackInfo &args) -{ - Nan::HandleScope scope; - table_parameters_ptr params = argumentsToTableParameter(args, true); - if (!params) - return; - - BOOST_ASSERT(params->IsValid()); - - Run(args, std::move(params), AsyncRunTable, AfterRunTable); -} - -void Engine::nearest(const Nan::FunctionCallbackInfo &args) -{ - Nan::HandleScope scope; - nearest_parameters_ptr params = argumentsToNearestParameter(args, false); - if (!params) - return; - - BOOST_ASSERT(params->IsValid()); - - Run(args, std::move(params), AsyncRunNearest, AfterRunNearest); -} - -void Engine::tile(const Nan::FunctionCallbackInfo &args) -{ - Nan::HandleScope scope; - tile_parameters_ptr params = argumentsToTileParameters(args); - if (!params) - return; - - BOOST_ASSERT(params->IsValid()); - - Run(args, std::move(params), AsyncRunTile, AfterRunTile); -} - -template -void Engine::Run(const Nan::FunctionCallbackInfo &args, - ParamType params, - AsyncType asnyc_fn, - AfterType after_fn) -{ - Nan::HandleScope scope; - v8::Local callback = args[args.Length() - 1]; - - if (!callback->IsFunction()) - { - Nan::ThrowTypeError("last argument must be a callback function"); - return; - } - - auto closure = new BatonType(); - closure->request.data = closure; - closure->machine = ObjectWrap::Unwrap(args.This()); - closure->params = std::move(params); - closure->error = ""; - closure->cb.Reset(callback.As()); - uv_queue_work(uv_default_loop(), &closure->request, asnyc_fn, - reinterpret_cast(after_fn)); - closure->machine->Ref(); - return; -} - -void Engine::ParseResult(const osrm::Status result_status, osrm::json::Object& result) -{ - const auto code_iter = result.values.find("code"); - const auto end_iter = result.values.end(); - - BOOST_ASSERT(code_iter != end_iter); + Nan::HandleScope scope; - if (result_status == osrm::Status::Error) - { - throw std::logic_error(code_iter->second.get().value.c_str()); - } + const constexpr auto argc = 2u; + v8::Local argv[argc] = {Nan::Null(), render(result)}; - result.values.erase(code_iter); - const auto message_iter = result.values.find("message"); - if (message_iter != end_iter) - { - result.values.erase(message_iter); - } -} - -void Engine::AsyncRunRoute(uv_work_t *req) -{ - RouteQueryBaton *closure = static_cast(req->data); - try - { - const auto status = closure->machine->this_->Route(*closure->params, closure->result); - - ParseResult(status, closure->result); - } - catch (std::exception const &ex) - { - closure->error = ex.what(); - } -} - -void Engine::AsyncRunNearest(uv_work_t *req) -{ - NearestQueryBaton *closure = static_cast(req->data); - try - { - const auto status = closure->machine->this_->Nearest(*closure->params, closure->result); - - ParseResult(status, closure->result); - } - catch (std::exception const &ex) - { - closure->error = ex.what(); - } -} - -void Engine::AsyncRunTable(uv_work_t *req) -{ - TableQueryBaton *closure = static_cast(req->data); - try - { - const auto status = closure->machine->this_->Table(*closure->params, closure->result); - - ParseResult(status, closure->result); - } - catch (std::exception const &ex) - { - closure->error = ex.what(); - } -} - -void Engine::AsyncRunTrip(uv_work_t *req) -{ - TripQueryBaton *closure = static_cast(req->data); - try - { - const auto status = closure->machine->this_->Trip(*closure->params, closure->result); - - ParseResult(status, closure->result); - } - catch (std::exception const &ex) - { - closure->error = ex.what(); - } -} - -void Engine::AsyncRunTile(uv_work_t *req) -{ - TileQueryBaton *closure = static_cast(req->data); - try - { - const auto status = closure->machine->this_->Tile(*closure->params, closure->result); - if (status == osrm::Status::Error) - { - throw std::logic_error("InvalidRequest"); + callback->Call(argc, argv); } - } - catch (std::exception const &ex) - { - closure->error = ex.what(); - } -} -void Engine::AsyncRunMatch(uv_work_t *req) -{ - MatchQueryBaton *closure = static_cast(req->data); - try - { - const auto status = closure->machine->this_->Match(*closure->params, closure->result); + // Keeps the OSRM object alive even after shutdown until we're done with callback + std::shared_ptr osrm; + ServiceMemFn service; + const ParamPtr params; - ParseResult(status, closure->result); - } - catch (std::exception const &ex) - { - closure->error = ex.what(); - } -} + // All services return json::Object .. except for Tile! + using ObjectOrString = + typename std::conditional::value, + std::string, + osrm::json::Object>::type; -template v8::Local render(const ResultT &result); - -template <> v8::Local render(const std::string &result) -{ - return Nan::CopyBuffer(result.data(), result.size()).ToLocalChecked(); -} + ObjectOrString result; + }; -template <> v8::Local render(const osrm::json::Object &result) -{ - v8::Local value; - renderToV8(value, result); - return value; -} - -template void Engine::AfterRun(BatonType *closure) -{ - Nan::TryCatch try_catch; - if (closure->error.size() > 0) - { - v8::Local argv[1] = {Nan::Error(closure->error.c_str())}; - Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 1, argv); - } - else - { - v8::Local result = render(closure->result); - v8::Local argv[2] = {Nan::Null(), result}; - Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(closure->cb), 2, argv); - } - if (try_catch.HasCaught()) - { - Nan::FatalException(try_catch); - } - closure->machine->Unref(); - closure->cb.Reset(); - delete closure; + auto *callback = new Nan::Callback{info[info.Length() - 1].As()}; + Nan::AsyncQueueWorker(new Worker{self->this_, std::move(params), service, callback}); } -void Engine::AfterRunRoute(uv_work_t *req) +NAN_METHOD(Engine::route) // { - Nan::HandleScope scope; - RouteQueryBaton *closure = static_cast(req->data); - AfterRun(closure); + async(info, &argumentsToRouteParameter, &osrm::OSRM::Route, true); } - -void Engine::AfterRunTable(uv_work_t *req) +NAN_METHOD(Engine::nearest) // { - Nan::HandleScope scope; - TableQueryBaton *closure = static_cast(req->data); - AfterRun(closure); + async(info, &argumentsToNearestParameter, &osrm::OSRM::Nearest, false); } - -void Engine::AfterRunNearest(uv_work_t *req) +NAN_METHOD(Engine::table) // { - Nan::HandleScope scope; - NearestQueryBaton *closure = static_cast(req->data); - AfterRun(closure); + async(info, &argumentsToTableParameter, &osrm::OSRM::Table, true); } - -void Engine::AfterRunTrip(uv_work_t *req) +NAN_METHOD(Engine::tile) { - Nan::HandleScope scope; - TripQueryBaton *closure = static_cast(req->data); - AfterRun(closure); + async(info, &argumentsToTileParameters, &osrm::OSRM::Tile, {/*unused*/}); } - -void Engine::AfterRunTile(uv_work_t *req) +NAN_METHOD(Engine::match) // { - Nan::HandleScope scope; - TileQueryBaton *closure = static_cast(req->data); - AfterRun(closure); + async(info, &argumentsToMatchParameter, &osrm::OSRM::Match, true); } - -void Engine::AfterRunMatch(uv_work_t *req) +NAN_METHOD(Engine::trip) // { - Nan::HandleScope scope; - MatchQueryBaton *closure = static_cast(req->data); - AfterRun(closure); -} - -extern "C" { -static void start(v8::Handle target) { Engine::Initialize(target); } + async(info, &argumentsToTripParameter, &osrm::OSRM::Trip, true); } } // namespace node_osrm - -NODE_MODULE(osrm, node_osrm::start) diff --git a/src/node_osrm.hpp b/src/node_osrm.hpp new file mode 100644 index 0000000..842f8ec --- /dev/null +++ b/src/node_osrm.hpp @@ -0,0 +1,39 @@ +#ifndef NODE_OSRM_HPP +#define NODE_OSRM_HPP + +#include +#include +#include + +namespace node_osrm +{ + +struct Engine final : public Nan::ObjectWrap +{ + using Base = Nan::ObjectWrap; + + static NAN_MODULE_INIT(Init); + + static NAN_METHOD(New); + + static NAN_METHOD(route); + static NAN_METHOD(nearest); + static NAN_METHOD(table); + static NAN_METHOD(tile); + static NAN_METHOD(match); + static NAN_METHOD(trip); + + Engine(osrm::EngineConfig &config); + + // Thread-safe singleton accessor + static Nan::Persistent &constructor(); + + // Ref-counted OSRM alive even after shutdown until last callback is done + std::shared_ptr this_; +}; + +} // ns node_osrm + +NODE_MODULE(osrm, node_osrm::Engine::Init) + +#endif diff --git a/src/node_osrm_support.hpp b/src/node_osrm_support.hpp new file mode 100644 index 0000000..66688d0 --- /dev/null +++ b/src/node_osrm_support.hpp @@ -0,0 +1,760 @@ +#ifndef NODE_OSRM_SUPPORT_HPP +#define NODE_OSRM_SUPPORT_HPP + +#include "json_v8_renderer.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace node_osrm +{ + +using engine_config_ptr = std::unique_ptr; +using route_parameters_ptr = std::unique_ptr; +using trip_parameters_ptr = std::unique_ptr; +using tile_parameters_ptr = std::unique_ptr; +using match_parameters_ptr = std::unique_ptr; +using nearest_parameters_ptr = std::unique_ptr; +using table_parameters_ptr = std::unique_ptr; + +template inline v8::Local render(const ResultT &result); + +template <> v8::Local inline render(const std::string &result) +{ + return Nan::CopyBuffer(result.data(), result.size()).ToLocalChecked(); +} + +template <> v8::Local inline render(const osrm::json::Object &result) +{ + v8::Local value; + renderToV8(value, result); + return value; +} + +inline void ParseResult(const osrm::Status &result_status, osrm::json::Object &result) +{ + const auto code_iter = result.values.find("code"); + const auto end_iter = result.values.end(); + + BOOST_ASSERT(code_iter != end_iter); + + if (result_status == osrm::Status::Error) + { + throw std::logic_error(code_iter->second.get().value.c_str()); + } + + result.values.erase(code_iter); + const auto message_iter = result.values.find("message"); + if (message_iter != end_iter) + { + result.values.erase(message_iter); + } +} + +inline void ParseResult(const osrm::Status &result_status, const std::string & /*unused*/) {} + +inline engine_config_ptr argumentsToEngineConfig(const Nan::FunctionCallbackInfo &args) +{ + Nan::HandleScope scope; + auto engine_config = boost::make_unique(); + + if (args.Length() == 0) + { + return engine_config; + } + else if (args.Length() > 1) + { + Nan::ThrowError("Only accepts one parameter"); + return engine_config_ptr(); + } + + BOOST_ASSERT(args.Length() == 1); + + if (args[0]->IsString()) + { + engine_config->storage_config = osrm::StorageConfig( + *v8::String::Utf8Value(Nan::To(args[0]).ToLocalChecked())); + engine_config->use_shared_memory = false; + return engine_config; + } + else if (!args[0]->IsObject()) + { + Nan::ThrowError("Parameter must be a path or options object"); + return engine_config_ptr(); + } + + BOOST_ASSERT(args[0]->IsObject()); + auto params = Nan::To(args[0]).ToLocalChecked(); + + auto path = params->Get(Nan::New("path").ToLocalChecked()); + auto shared_memory = params->Get(Nan::New("shared_memory").ToLocalChecked()); + if (!path->IsUndefined()) + { + engine_config->storage_config = + osrm::StorageConfig(*v8::String::Utf8Value(Nan::To(path).ToLocalChecked())); + } + if (!shared_memory->IsUndefined()) + { + if (shared_memory->IsBoolean()) + { + engine_config->use_shared_memory = Nan::To(shared_memory).FromJust(); + } + else + { + Nan::ThrowError("Shared_memory option must be a boolean"); + return engine_config_ptr(); + } + } + + if (path->IsUndefined() && !engine_config->use_shared_memory) + { + Nan::ThrowError("Shared_memory must be enabled if no path is " + "specified"); + return engine_config_ptr(); + } + + return engine_config; +} + +inline boost::optional> +parseCoordinateArray(const v8::Local &coordinates_array) +{ + Nan::HandleScope scope; + boost::optional> resulting_coordinates; + std::vector temp_coordinates; + + for (uint32_t i = 0; i < coordinates_array->Length(); ++i) + { + v8::Local coordinate = coordinates_array->Get(i); + + if (!coordinate->IsArray()) + { + Nan::ThrowError("Coordinates must be an array of (lon/lat) pairs"); + return resulting_coordinates; + } + + v8::Local coordinate_pair = v8::Local::Cast(coordinate); + if (coordinate_pair->Length() != 2) + { + Nan::ThrowError("Coordinates must be an array of (lon/lat) pairs"); + return resulting_coordinates; + } + + if (!coordinate_pair->Get(0)->IsNumber() || !coordinate_pair->Get(1)->IsNumber()) + { + Nan::ThrowError("Each member of a coordinate pair must be a number"); + return resulting_coordinates; + } + + double lon = coordinate_pair->Get(0)->NumberValue(); + double lat = coordinate_pair->Get(1)->NumberValue(); + + if (std::isnan(lon) || std::isnan(lat) || std::isinf(lon) || std::isinf(lat)) + { + Nan::ThrowError("Lng/Lat coordinates must be valid numbers"); + return resulting_coordinates; + } + + if (lon > 180 || lon < -180 || lat > 90 || lat < -90) + { + Nan::ThrowError("Lng/Lat coordinates must be within world bounds " + "(-180 < lng < 180, -90 < lat < 90)"); + return resulting_coordinates; + } + + temp_coordinates.emplace_back(osrm::util::FloatLongitude(std::move(lon)), + osrm::util::FloatLatitude(std::move(lat))); + } + + resulting_coordinates = boost::make_optional(std::move(temp_coordinates)); + return resulting_coordinates; +} + +// Parses all the non-service specific parameters +template +inline bool argumentsToParameter(const Nan::FunctionCallbackInfo &args, + ParamType ¶ms, + bool requires_multiple_coordinates) +{ + Nan::HandleScope scope; + + if (args.Length() < 2) + { + Nan::ThrowTypeError("Two arguments required"); + return false; + } + + if (!args[0]->IsObject()) + { + Nan::ThrowTypeError("First arg must be an object"); + return false; + } + + v8::Local obj = Nan::To(args[0]).ToLocalChecked(); + + v8::Local coordinates = obj->Get(Nan::New("coordinates").ToLocalChecked()); + if (coordinates->IsUndefined()) + { + Nan::ThrowError("Must provide a coordinates property"); + return false; + } + else if (coordinates->IsArray()) + { + auto coordinates_array = v8::Local::Cast(coordinates); + if (coordinates_array->Length() < 2 && requires_multiple_coordinates) + { + Nan::ThrowError("At least two coordinates must be provided"); + return false; + } + else if (!requires_multiple_coordinates && coordinates_array->Length() != 1) + { + Nan::ThrowError("Exactly one coordinate pair must be provided"); + return false; + } + auto maybe_coordinates = parseCoordinateArray(coordinates_array); + if (maybe_coordinates) + { + std::copy(maybe_coordinates->begin(), maybe_coordinates->end(), + std::back_inserter(params->coordinates)); + } + else + { + return false; + } + } + else if (!coordinates->IsUndefined()) + { + BOOST_ASSERT(!coordinates->IsArray()); + Nan::ThrowError("Coordinates must be an array of (lon/lat) pairs"); + return false; + } + + if (obj->Has(Nan::New("bearings").ToLocalChecked())) + { + v8::Local bearings = obj->Get(Nan::New("bearings").ToLocalChecked()); + + if (!bearings->IsArray()) + { + Nan::ThrowError("Bearings must be an array of arrays of numbers"); + return false; + } + + auto bearings_array = v8::Local::Cast(bearings); + + if (bearings_array->Length() != params->coordinates.size()) + { + Nan::ThrowError("Bearings array must have the same length as coordinates array"); + return false; + } + + for (uint32_t i = 0; i < bearings_array->Length(); ++i) + { + v8::Local bearing_raw = bearings_array->Get(i); + + if (bearing_raw->IsNull()) + { + params->bearings.emplace_back(); + } + else if (bearing_raw->IsArray()) + { + auto bearing_pair = v8::Local::Cast(bearing_raw); + if (bearing_pair->Length() == 2) + { + if (!bearing_pair->Get(0)->IsNumber() || !bearing_pair->Get(1)->IsNumber()) + { + Nan::ThrowError("Bearing values need to be numbers in range 0..360"); + return false; + } + + short bearing_first = static_cast(bearing_pair->Get(0)->NumberValue()); + short bearing_second = static_cast(bearing_pair->Get(1)->NumberValue()); + + if (bearing_first < 0 || bearing_first >= 360 || bearing_second < 0 || + bearing_second >= 360) + { + Nan::ThrowError("Bearing values need to be in range 0..360"); + return false; + } + + params->bearings.push_back(osrm::Bearing{bearing_first, bearing_second}); + } + else + { + Nan::ThrowError("Bearing must be an array of [bearing, range] or null"); + return false; + } + } + else + { + Nan::ThrowError("Bearing must be an array of [bearing, range] or null"); + return false; + } + } + } + + if (obj->Has(Nan::New("hints").ToLocalChecked())) + { + v8::Local hints = obj->Get(Nan::New("hints").ToLocalChecked()); + + if (!hints->IsArray()) + { + Nan::ThrowError("Hints must be an array of strings/null"); + return false; + } + + v8::Local hints_array = v8::Local::Cast(hints); + + if (hints_array->Length() != params->coordinates.size()) + { + Nan::ThrowError("Hints array must have the same length as coordinates array"); + return false; + } + + for (uint32_t i = 0; i < hints_array->Length(); ++i) + { + v8::Local hint = hints_array->Get(i); + if (hint->IsString()) + { + if (hint->ToString()->Length() == 0) + { + Nan::ThrowError("Hint cannot be an empty string"); + return false; + } + + params->hints.push_back( + osrm::engine::Hint::FromBase64(*v8::String::Utf8Value(hint))); + } + else if (hint->IsNull()) + { + params->hints.emplace_back(); + } + else + { + Nan::ThrowError("Hint must be null or string"); + return false; + } + } + } + + if (obj->Has(Nan::New("radiuses").ToLocalChecked())) + { + v8::Local radiuses = obj->Get(Nan::New("radiuses").ToLocalChecked()); + + if (!radiuses->IsArray()) + { + Nan::ThrowError("Radiuses must be an array of non-negative doubles or null"); + return false; + } + + v8::Local radiuses_array = v8::Local::Cast(radiuses); + + if (radiuses_array->Length() != params->coordinates.size()) + { + Nan::ThrowError("Radiuses array must have the same length as coordinates array"); + return false; + } + + for (uint32_t i = 0; i < radiuses_array->Length(); ++i) + { + v8::Local radius = radiuses_array->Get(i); + if (radius->IsNull()) + { + params->radiuses.emplace_back(); + } + else if (radius->IsNumber() && radius->NumberValue() >= 0) + { + params->radiuses.push_back(static_cast(radius->NumberValue())); + } + else + { + Nan::ThrowError("Radius must be non-negative double or null"); + return false; + } + } + } + + return true; +} + +template +inline bool parseCommonParameters(const v8::Local obj, ParamType ¶ms) +{ + if (obj->Has(Nan::New("steps").ToLocalChecked())) + { + params->steps = obj->Get(Nan::New("steps").ToLocalChecked())->BooleanValue(); + } + + if (obj->Has(Nan::New("geometries").ToLocalChecked())) + { + v8::Local geometries = obj->Get(Nan::New("geometries").ToLocalChecked()); + + if (!geometries->IsString()) + { + Nan::ThrowError("Geometries must be a string: [polyline, geojson]"); + return false; + } + + std::string geometries_str = *v8::String::Utf8Value(geometries); + + if (geometries_str == "polyline") + { + params->geometries = osrm::RouteParameters::GeometriesType::Polyline; + } + else if (geometries_str == "geojson") + { + params->geometries = osrm::RouteParameters::GeometriesType::GeoJSON; + } + else + { + Nan::ThrowError("'geometries' param must be one of [polyline, geojson]"); + return false; + } + } + + if (obj->Has(Nan::New("overview").ToLocalChecked())) + { + v8::Local overview = obj->Get(Nan::New("overview").ToLocalChecked()); + + if (!overview->IsString()) + { + Nan::ThrowError("Overview must be a string: [simplified, full, false]"); + return false; + } + + std::string overview_str = *v8::String::Utf8Value(overview); + + if (overview_str == "simplified") + { + params->overview = osrm::RouteParameters::OverviewType::Simplified; + } + else if (overview_str == "full") + { + params->overview = osrm::RouteParameters::OverviewType::Full; + } + else if (overview_str == "false") + { + params->overview = osrm::RouteParameters::OverviewType::False; + } + else + { + Nan::ThrowError("'overview' param must be one of [simplified, full, false]"); + return false; + } + } + + return true; +} + +inline route_parameters_ptr +argumentsToRouteParameter(const Nan::FunctionCallbackInfo &args, + bool requires_multiple_coordinates) +{ + route_parameters_ptr params = boost::make_unique(); + bool has_base_params = argumentsToParameter(args, params, requires_multiple_coordinates); + if (!has_base_params) + return route_parameters_ptr(); + + v8::Local obj = Nan::To(args[0]).ToLocalChecked(); + + if (obj->Has(Nan::New("continue_straight").ToLocalChecked())) + { + auto value = obj->Get(Nan::New("continue_straight").ToLocalChecked()); + if (!value->IsBoolean() && !value->IsNull()) + { + Nan::ThrowError("'continue_straight' parama must be boolean or null"); + } + if (value->IsBoolean()) + { + params->continue_straight = value->BooleanValue(); + } + } + + if (obj->Has(Nan::New("alternatives").ToLocalChecked())) + { + auto value = obj->Get(Nan::New("alternatives").ToLocalChecked()); + if (!value->IsBoolean()) + { + Nan::ThrowError("'alternatives' parama must be boolean"); + } + params->alternatives = value->BooleanValue(); + } + + bool parsedSuccessfully = parseCommonParameters(obj, params); + if (!parsedSuccessfully) + { + return route_parameters_ptr(); + } + + return params; +} + +inline tile_parameters_ptr +argumentsToTileParameters(const Nan::FunctionCallbackInfo &args, bool /*unused*/) +{ + tile_parameters_ptr params = boost::make_unique(); + + if (args.Length() < 2) + { + Nan::ThrowTypeError("Coordinate object and callback required"); + return tile_parameters_ptr(); + } + + if (!args[0]->IsArray()) + { + Nan::ThrowTypeError("Parameter must be an array [x, y, z]"); + return tile_parameters_ptr(); + } + + v8::Local array = v8::Local::Cast(args[0]); + + if (array->Length() != 3) + { + Nan::ThrowTypeError("Parameter must be an array [x, y, z]"); + return tile_parameters_ptr(); + } + + v8::Local x = array->Get(0); + v8::Local y = array->Get(1); + v8::Local z = array->Get(2); + + if (!x->IsUint32() && !x->IsUndefined()) + { + Nan::ThrowError("Tile x coordinate must be unsigned interger"); + return tile_parameters_ptr(); + } + if (!y->IsUint32() && !y->IsUndefined()) + { + Nan::ThrowError("Tile y coordinate must be unsigned interger"); + return tile_parameters_ptr(); + } + if (!z->IsUint32() && !z->IsUndefined()) + { + Nan::ThrowError("Tile z coordinate must be unsigned interger"); + return tile_parameters_ptr(); + } + + params->x = x->Uint32Value(); + params->y = y->Uint32Value(); + params->z = z->Uint32Value(); + + return params; +} + +inline nearest_parameters_ptr +argumentsToNearestParameter(const Nan::FunctionCallbackInfo &args, + bool requires_multiple_coordinates) +{ + nearest_parameters_ptr params = boost::make_unique(); + bool has_base_params = argumentsToParameter(args, params, requires_multiple_coordinates); + if (!has_base_params) + return nearest_parameters_ptr(); + + v8::Local obj = Nan::To(args[0]).ToLocalChecked(); + + if (obj->Has(Nan::New("number").ToLocalChecked())) + { + v8::Local number = obj->Get(Nan::New("number").ToLocalChecked()); + + if (!number->IsUint32()) + { + Nan::ThrowError("Number must be an integer greater than or equal to 1"); + return nearest_parameters_ptr(); + } + else + { + unsigned number_value = static_cast(number->NumberValue()); + + if (number_value < 1) + { + Nan::ThrowError("Number must be an integer greater than or equal to 1"); + return nearest_parameters_ptr(); + } + + params->number_of_results = static_cast(number->NumberValue()); + } + } + + return params; +} + +inline table_parameters_ptr +argumentsToTableParameter(const Nan::FunctionCallbackInfo &args, + bool requires_multiple_coordinates) +{ + table_parameters_ptr params = boost::make_unique(); + bool has_base_params = argumentsToParameter(args, params, requires_multiple_coordinates); + if (!has_base_params) + return table_parameters_ptr(); + + v8::Local obj = Nan::To(args[0]).ToLocalChecked(); + + if (obj->Has(Nan::New("sources").ToLocalChecked())) + { + v8::Local sources = obj->Get(Nan::New("sources").ToLocalChecked()); + + if (!sources->IsArray()) + { + Nan::ThrowError("Sources must be an array of indices (or undefined)"); + return table_parameters_ptr(); + } + + v8::Local sources_array = v8::Local::Cast(sources); + for (uint32_t i = 0; i < sources_array->Length(); ++i) + { + v8::Local source = sources_array->Get(i); + if (source->IsUint32()) + { + size_t source_value = static_cast(source->NumberValue()); + if (source_value > params->coordinates.size()) + { + Nan::ThrowError( + "Source indices must be less than or equal to the number of coordinates"); + return table_parameters_ptr(); + } + + params->sources.push_back(static_cast(source->NumberValue())); + } + else + { + Nan::ThrowError("Source must be an integer"); + return table_parameters_ptr(); + } + } + } + + if (obj->Has(Nan::New("destinations").ToLocalChecked())) + { + v8::Local destinations = obj->Get(Nan::New("destinations").ToLocalChecked()); + + if (!destinations->IsArray()) + { + Nan::ThrowError("Destinations must be an array of indices (or undefined)"); + return table_parameters_ptr(); + } + + v8::Local destinations_array = v8::Local::Cast(destinations); + for (uint32_t i = 0; i < destinations_array->Length(); ++i) + { + v8::Local destination = destinations_array->Get(i); + if (destination->IsUint32()) + { + size_t destination_value = static_cast(destination->NumberValue()); + if (destination_value > params->coordinates.size()) + { + Nan::ThrowError("Destination indices must be less than or equal to the number " + "of coordinates"); + return table_parameters_ptr(); + } + + params->destinations.push_back(static_cast(destination->NumberValue())); + } + else + { + Nan::ThrowError("Destination must be an integer"); + return table_parameters_ptr(); + } + } + } + + return params; +} + +inline trip_parameters_ptr +argumentsToTripParameter(const Nan::FunctionCallbackInfo &args, + bool requires_multiple_coordinates) +{ + trip_parameters_ptr params = boost::make_unique(); + bool has_base_params = argumentsToParameter(args, params, requires_multiple_coordinates); + if (!has_base_params) + return trip_parameters_ptr(); + + v8::Local obj = Nan::To(args[0]).ToLocalChecked(); + + bool parsedSuccessfully = parseCommonParameters(obj, params); + if (!parsedSuccessfully) + { + return trip_parameters_ptr(); + } + + return params; +} + +inline match_parameters_ptr +argumentsToMatchParameter(const Nan::FunctionCallbackInfo &args, + bool requires_multiple_coordinates) +{ + match_parameters_ptr params = boost::make_unique(); + bool has_base_params = argumentsToParameter(args, params, requires_multiple_coordinates); + if (!has_base_params) + return match_parameters_ptr(); + + v8::Local obj = Nan::To(args[0]).ToLocalChecked(); + + if (obj->Has(Nan::New("timestamps").ToLocalChecked())) + { + v8::Local timestamps = obj->Get(Nan::New("timestamps").ToLocalChecked()); + + if (!timestamps->IsArray()) + { + Nan::ThrowError("Timestamps must be an array of integers (or undefined)"); + return match_parameters_ptr(); + } + + v8::Local timestamps_array = v8::Local::Cast(timestamps); + + if (params->coordinates.size() != timestamps_array->Length()) + { + Nan::ThrowError("Timestamp array must have the same size as the coordinates " + "array"); + return match_parameters_ptr(); + } + + for (uint32_t i = 0; i < timestamps_array->Length(); ++i) + { + v8::Local timestamp = timestamps_array->Get(i); + if (!timestamp->IsNumber()) + { + Nan::ThrowError("Timestamps array items must be numbers"); + return match_parameters_ptr(); + } + params->timestamps.emplace_back(static_cast(timestamp->NumberValue())); + } + } + + bool parsedSuccessfully = parseCommonParameters(obj, params); + if (!parsedSuccessfully) + { + return match_parameters_ptr(); + } + + return params; +} + +} // ns node_osrm + +#endif diff --git a/test/index.js b/test/index.js index ac5dfc0..15bbf25 100644 --- a/test/index.js +++ b/test/index.js @@ -2,12 +2,6 @@ var OSRM = require('../'); var test = require('tape'); var berlin_path = require('./osrm-data-path').data_path; -test('constructor: throws if new keyword is not used', function(assert) { - assert.plan(1); - assert.throws(function() { OSRM(); }, - /Cannot call constructor as function, you need to use 'new' keyword/); -}); - test('constructor: uses defaults with no parameter', function(assert) { assert.plan(1); var osrm = new OSRM();