#ifndef OSRM_BINDINGS_NODE_SUPPORT_HPP #define OSRM_BINDINGS_NODE_SUPPORT_HPP #include "nodejs/json_v8_renderer.hpp" #include "engine/api/flatbuffers/fbresult_generated.h" #include "osrm/approach.hpp" #include "osrm/bearing.hpp" #include "osrm/coordinate.hpp" #include "osrm/engine_config.hpp" #include "osrm/json_container.hpp" #include "osrm/match_parameters.hpp" #include "osrm/nearest_parameters.hpp" #include "osrm/osrm.hpp" #include "osrm/route_parameters.hpp" #include "osrm/status.hpp" #include "osrm/storage_config.hpp" #include "osrm/table_parameters.hpp" #include "osrm/tile_parameters.hpp" #include "osrm/trip_parameters.hpp" #include "util/json_renderer.hpp" #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; struct PluginParameters { bool renderToBuffer = false; }; using ObjectOrString = typename std::variant; template inline Napi::Value render(const Napi::Env &env, const ResultT &result); template <> Napi::Value inline render(const Napi::Env &env, const std::string &result) { return Napi::Buffer::Copy(env, result.data(), result.size()); } template <> Napi::Value inline render(const Napi::Env &env, const ObjectOrString &result) { if (std::holds_alternative(result)) { // Convert osrm::json object tree into matching v8 object tree Napi::Value value; renderToV8(env, value, std::get(result)); return value; } else { // Return the string object as a node Buffer return Napi::Buffer::Copy( env, std::get(result).data(), std::get(result).size()); } } inline bool IsUnsignedInteger(const Napi::Value &value) { if (!value.IsNumber()) { return false; } const auto doubleValue = value.ToNumber().DoubleValue(); return doubleValue >= 0.0 && std::floor(doubleValue) == doubleValue; } 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(std::get(code_iter->second).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 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 void ThrowError(const Napi::Env &env, const char *message) { Napi::Error::New(env, message).ThrowAsJavaScriptException(); } inline void ThrowTypeError(const Napi::Env &env, const char *message) { Napi::TypeError::New(env, message).ThrowAsJavaScriptException(); } inline engine_config_ptr argumentsToEngineConfig(const Napi::CallbackInfo &args) { Napi::HandleScope scope(args.Env()); auto engine_config = std::make_unique(); if (args.Length() == 0) { return engine_config; } else if (args.Length() > 1) { ThrowError(args.Env(), "Only accepts one parameter"); return engine_config_ptr(); } BOOST_ASSERT(args.Length() == 1); if (args[0].IsString()) { engine_config->storage_config = osrm::StorageConfig(args[0].ToString().Utf8Value()); engine_config->use_shared_memory = false; return engine_config; } else if (!args[0].IsObject()) { ThrowError(args.Env(), "Parameter must be a path or options object"); return engine_config_ptr(); } BOOST_ASSERT(args[0].IsObject()); auto params = args[0].As(); auto path = params.Get("path"); if (path.IsEmpty()) return engine_config_ptr(); auto memory_file = params.Get("memory_file"); if (memory_file.IsEmpty()) return engine_config_ptr(); auto shared_memory = params.Get("shared_memory"); if (shared_memory.IsEmpty()) return engine_config_ptr(); auto mmap_memory = params.Get("mmap_memory"); if (mmap_memory.IsEmpty()) return engine_config_ptr(); if (!memory_file.IsUndefined()) { if (path.IsUndefined()) { ThrowError(args.Env(), "memory_file option requires a path to a file."); return engine_config_ptr(); } engine_config->memory_file = memory_file.ToString().Utf8Value(); } auto dataset_name = params.Get("dataset_name"); if (dataset_name.IsEmpty()) return engine_config_ptr(); if (!dataset_name.IsUndefined()) { if (dataset_name.IsString()) { engine_config->dataset_name = dataset_name.ToString().Utf8Value(); } else { ThrowError(args.Env(), "dataset_name needs to be a string"); return engine_config_ptr(); } } auto disable_feature_dataset = params.Get("disable_feature_dataset"); if (disable_feature_dataset.IsArray()) { Napi::Array datasets = disable_feature_dataset.As(); for (uint32_t i = 0; i < datasets.Length(); ++i) { Napi::Value dataset = datasets.Get(i); if (!dataset.IsString()) { ThrowError(args.Env(), "disable_feature_dataset list option must be a string"); return engine_config_ptr(); } auto dataset_str = dataset.ToString().Utf8Value(); if (dataset_str == "ROUTE_GEOMETRY") { engine_config->disable_feature_dataset.push_back( osrm::storage::FeatureDataset::ROUTE_GEOMETRY); } else if (dataset_str == "ROUTE_STEPS") { engine_config->disable_feature_dataset.push_back( osrm::storage::FeatureDataset::ROUTE_STEPS); } else { ThrowError( args.Env(), "disable_feature_dataset array can include 'ROUTE_GEOMETRY', 'ROUTE_STEPS'."); return engine_config_ptr(); } } } else if (!disable_feature_dataset.IsUndefined()) { ThrowError(args.Env(), "disable_feature_dataset option must be an array and can include the string " "values 'ROUTE_GEOMETRY', 'ROUTE_STEPS'."); return engine_config_ptr(); } if (!path.IsUndefined()) { engine_config->storage_config = osrm::StorageConfig(path.ToString().Utf8Value(), engine_config->disable_feature_dataset); engine_config->use_shared_memory = false; } if (!shared_memory.IsUndefined()) { if (shared_memory.IsBoolean()) { engine_config->use_shared_memory = shared_memory.ToBoolean().Value(); } else { ThrowError(args.Env(), "Shared_memory option must be a boolean"); return engine_config_ptr(); } } if (!mmap_memory.IsUndefined()) { if (mmap_memory.IsBoolean()) { engine_config->use_mmap = mmap_memory.ToBoolean().Value(); } else { ThrowError(args.Env(), "mmap_memory option must be a boolean"); return engine_config_ptr(); } } if (path.IsUndefined() && !engine_config->use_shared_memory) { ThrowError(args.Env(), "Shared_memory must be enabled if no path is " "specified"); return engine_config_ptr(); } auto algorithm = params.Get("algorithm"); if (algorithm.IsEmpty()) return engine_config_ptr(); if (algorithm.IsString()) { auto algorithm_str = algorithm.ToString().Utf8Value(); if (algorithm_str == "CH") { engine_config->algorithm = osrm::EngineConfig::Algorithm::CH; } else if (algorithm_str == "MLD") { engine_config->algorithm = osrm::EngineConfig::Algorithm::MLD; } else { ThrowError(args.Env(), "algorithm option must be one of 'CH', or 'MLD'."); return engine_config_ptr(); } } else if (!algorithm.IsUndefined()) { ThrowError(args.Env(), "algorithm option must be a string and one of 'CH', or 'MLD'."); return engine_config_ptr(); } // Set EngineConfig system-wide limits on construction, if requested auto max_locations_trip = params.Get("max_locations_trip"); auto max_locations_viaroute = params.Get("max_locations_viaroute"); auto max_locations_distance_table = params.Get("max_locations_distance_table"); auto max_locations_map_matching = params.Get("max_locations_map_matching"); auto max_results_nearest = params.Get("max_results_nearest"); auto max_alternatives = params.Get("max_alternatives"); auto max_radius_map_matching = params.Get("max_radius_map_matching"); auto default_radius = params.Get("default_radius"); if (!max_locations_trip.IsUndefined() && !max_locations_trip.IsNumber()) { ThrowError(args.Env(), "max_locations_trip must be an integral number"); return engine_config_ptr(); } if (!max_locations_viaroute.IsUndefined() && !max_locations_viaroute.IsNumber()) { ThrowError(args.Env(), "max_locations_viaroute must be an integral number"); return engine_config_ptr(); } if (!max_locations_distance_table.IsUndefined() && !max_locations_distance_table.IsNumber()) { ThrowError(args.Env(), "max_locations_distance_table must be an integral number"); return engine_config_ptr(); } if (!max_locations_map_matching.IsUndefined() && !max_locations_map_matching.IsNumber()) { ThrowError(args.Env(), "max_locations_map_matching must be an integral number"); return engine_config_ptr(); } if (!max_results_nearest.IsUndefined() && !max_results_nearest.IsNumber()) { ThrowError(args.Env(), "max_results_nearest must be an integral number"); return engine_config_ptr(); } if (!max_alternatives.IsUndefined() && !max_alternatives.IsNumber()) { ThrowError(args.Env(), "max_alternatives must be an integral number"); return engine_config_ptr(); } if (!max_radius_map_matching.IsUndefined() && max_radius_map_matching.IsString() && max_radius_map_matching.ToString().Utf8Value() != "unlimited") { ThrowError(args.Env(), "max_radius_map_matching must be unlimited or an integral number"); return engine_config_ptr(); } if (!default_radius.IsUndefined() && default_radius.IsString() && default_radius.ToString().Utf8Value() != "unlimited") { ThrowError(args.Env(), "default_radius must be unlimited or an integral number"); return engine_config_ptr(); } if (max_locations_trip.IsNumber()) engine_config->max_locations_trip = max_locations_trip.ToNumber().Int32Value(); if (max_locations_viaroute.IsNumber()) engine_config->max_locations_viaroute = max_locations_viaroute.ToNumber().Int32Value(); if (max_locations_distance_table.IsNumber()) engine_config->max_locations_distance_table = max_locations_distance_table.ToNumber().Int32Value(); if (max_locations_map_matching.IsNumber()) engine_config->max_locations_map_matching = max_locations_map_matching.ToNumber().Int32Value(); if (max_results_nearest.IsNumber()) engine_config->max_results_nearest = max_results_nearest.ToNumber().Int32Value(); if (max_alternatives.IsNumber()) engine_config->max_alternatives = max_alternatives.ToNumber().Int32Value(); if (max_radius_map_matching.IsNumber()) engine_config->max_radius_map_matching = max_radius_map_matching.ToNumber().DoubleValue(); else if (max_radius_map_matching.IsString() && max_radius_map_matching.ToString().Utf8Value() == "unlimited") engine_config->max_radius_map_matching = -1.0; if (default_radius.IsNumber()) engine_config->default_radius = default_radius.ToNumber().DoubleValue(); else if (default_radius.IsString() && default_radius.ToString().Utf8Value() == "unlimited") engine_config->default_radius = -1.0; return engine_config; } inline std::optional> parseCoordinateArray(const Napi::Array &coordinates_array) { Napi::HandleScope scope(coordinates_array.Env()); std::optional> resulting_coordinates; std::vector temp_coordinates; for (uint32_t i = 0; i < coordinates_array.Length(); ++i) { Napi::Value coordinate = coordinates_array.Get(i); if (coordinate.IsEmpty()) return resulting_coordinates; if (!coordinate.IsArray()) { ThrowError(coordinates_array.Env(), "Coordinates must be an array of (lon/lat) pairs"); return resulting_coordinates; } Napi::Array coordinate_pair = coordinate.As(); if (coordinate_pair.Length() != 2) { ThrowError(coordinates_array.Env(), "Coordinates must be an array of (lon/lat) pairs"); return resulting_coordinates; } if (!coordinate_pair.Get(static_cast(0)).IsNumber() || !coordinate_pair.Get(static_cast(1)).IsNumber()) { ThrowError(coordinates_array.Env(), "Each member of a coordinate pair must be a number"); return resulting_coordinates; } double lon = coordinate_pair.Get(static_cast(0)).As().DoubleValue(); double lat = coordinate_pair.Get(static_cast(1)).As().DoubleValue(); if (std::isnan(lon) || std::isnan(lat) || std::isinf(lon) || std::isinf(lat)) { ThrowError(coordinates_array.Env(), "Lng/Lat coordinates must be valid numbers"); return resulting_coordinates; } if (lon > 180 || lon < -180 || lat > 90 || lat < -90) { ThrowError(coordinates_array.Env(), "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 = std::make_optional(std::move(temp_coordinates)); return resulting_coordinates; } // Parses all the non-service specific parameters template inline bool argumentsToParameter(const Napi::CallbackInfo &args, ParamType ¶ms, bool requires_multiple_coordinates) { Napi::HandleScope scope(args.Env()); if (args.Length() < 2) { ThrowTypeError(args.Env(), "Two arguments required"); return false; } if (!args[0].IsObject()) { ThrowTypeError(args.Env(), "First arg must be an object"); return false; } Napi::Object obj = args[0].As(); Napi::Value coordinates = obj.Get("coordinates"); if (coordinates.IsEmpty()) return false; if (coordinates.IsUndefined()) { ThrowError(args.Env(), "Must provide a coordinates property"); return false; } else if (coordinates.IsArray()) { auto coordinates_array = coordinates.As(); if (coordinates_array.Length() < 2 && requires_multiple_coordinates) { ThrowError(args.Env(), "At least two coordinates must be provided"); return false; } else if (!requires_multiple_coordinates && coordinates_array.Length() != 1) { ThrowError(args.Env(), "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()); ThrowError(args.Env(), "Coordinates must be an array of (lon/lat) pairs"); return false; } if (obj.Has("approaches")) { Napi::Value approaches = obj.Get("approaches"); if (approaches.IsEmpty()) return false; if (!approaches.IsArray()) { ThrowError(args.Env(), "Approaches must be an arrays of strings"); return false; } auto approaches_array = approaches.As(); if (approaches_array.Length() != params->coordinates.size()) { ThrowError(args.Env(), "Approaches array must have the same length as coordinates array"); return false; } for (uint32_t i = 0; i < approaches_array.Length(); ++i) { Napi::Value approach_raw = approaches_array.Get(i); if (approach_raw.IsEmpty()) return false; if (approach_raw.IsNull()) { params->approaches.emplace_back(); } else if (approach_raw.IsString()) { std::string approach_str = approach_raw.ToString().Utf8Value(); if (approach_str == "curb") { params->approaches.push_back(osrm::Approach::CURB); } else if (approach_str == "opposite") { params->approaches.push_back(osrm::Approach::OPPOSITE); } else if (approach_str == "unrestricted") { params->approaches.push_back(osrm::Approach::UNRESTRICTED); } else { ThrowError(args.Env(), "'approaches' param must be one of [curb, opposite, unrestricted]"); return false; } } else { ThrowError(args.Env(), "Approach must be a string: [curb, opposite, unrestricted] or null"); return false; } } } if (obj.Has("bearings")) { Napi::Value bearings = obj.Get("bearings"); if (bearings.IsEmpty()) return false; if (!bearings.IsArray()) { ThrowError(args.Env(), "Bearings must be an array of arrays of numbers"); return false; } auto bearings_array = bearings.As(); if (bearings_array.Length() != params->coordinates.size()) { ThrowError(args.Env(), "Bearings array must have the same length as coordinates array"); return false; } for (uint32_t i = 0; i < bearings_array.Length(); ++i) { Napi::Value bearing_raw = bearings_array.Get(i); if (bearing_raw.IsEmpty()) return false; if (bearing_raw.IsNull()) { params->bearings.emplace_back(); } else if (bearing_raw.IsArray()) { auto bearing_pair = bearing_raw.As(); if (bearing_pair.Length() == 2) { if (!bearing_pair.Get(static_cast(0)).IsNumber() || !bearing_pair.Get(static_cast(1)).IsNumber()) { ThrowError(args.Env(), "Bearing values need to be numbers in range 0..360"); return false; } const auto bearing = bearing_pair.Get(static_cast(0)).ToNumber().Int32Value(); const auto range = bearing_pair.Get(static_cast(1)).ToNumber().Int32Value(); if (bearing < 0 || bearing > 360 || range < 0 || range > 180) { ThrowError(args.Env(), "Bearing values need to be in range 0..360, 0..180"); return false; } params->bearings.push_back( osrm::Bearing{static_cast(bearing), static_cast(range)}); } else { ThrowError(args.Env(), "Bearing must be an array of [bearing, range] or null"); return false; } } else { ThrowError(args.Env(), "Bearing must be an array of [bearing, range] or null"); return false; } } } if (obj.Has("hints")) { Napi::Value hints = obj.Get("hints"); if (hints.IsEmpty()) return false; if (!hints.IsArray()) { ThrowError(args.Env(), "Hints must be an array of strings/null"); return false; } Napi::Array hints_array = hints.As(); if (hints_array.Length() != params->coordinates.size()) { ThrowError(args.Env(), "Hints array must have the same length as coordinates array"); return false; } for (uint32_t i = 0; i < hints_array.Length(); ++i) { Napi::Value hint = hints_array.Get(i); if (hint.IsEmpty()) return false; if (hint.IsString()) { if (hint.ToString().Utf8Value().length() == 0) { ThrowError(args.Env(), "Hint cannot be an empty string"); return false; } params->hints.emplace_back( osrm::engine::Hint::FromBase64(hint.ToString().Utf8Value())); } else if (hint.IsNull()) { params->hints.emplace_back(); } else { ThrowError(args.Env(), "Hint must be null or string"); return false; } } } if (obj.Has("radiuses")) { Napi::Value radiuses = obj.Get("radiuses"); if (radiuses.IsEmpty()) return false; if (!radiuses.IsArray()) { ThrowError(args.Env(), "Radiuses must be an array of non-negative doubles or null"); return false; } Napi::Array radiuses_array = radiuses.As(); if (radiuses_array.Length() != params->coordinates.size()) { ThrowError(args.Env(), "Radiuses array must have the same length as coordinates array"); return false; } for (uint32_t i = 0; i < radiuses_array.Length(); ++i) { Napi::Value radius = radiuses_array.Get(i); if (radius.IsEmpty()) return false; if (radius.IsNull()) { params->radiuses.emplace_back(); } else if (radius.IsNumber() && radius.ToNumber().DoubleValue() >= 0) { params->radiuses.push_back(radius.ToNumber().DoubleValue()); } else { ThrowError(args.Env(), "Radius must be non-negative double or null"); return false; } } } if (obj.Has("generate_hints")) { Napi::Value generate_hints = obj.Get("generate_hints"); if (generate_hints.IsEmpty()) return false; if (!generate_hints.IsBoolean()) { ThrowError(args.Env(), "generate_hints must be of type Boolean"); return false; } params->generate_hints = generate_hints.ToBoolean().Value(); } if (obj.Has("skip_waypoints")) { Napi::Value skip_waypoints = obj.Get("skip_waypoints"); if (skip_waypoints.IsEmpty()) return false; if (!skip_waypoints.IsBoolean()) { ThrowError(args.Env(), "skip_waypoints must be of type Boolean"); return false; } params->skip_waypoints = skip_waypoints.ToBoolean().Value(); } if (obj.Has("exclude")) { Napi::Value exclude = obj.Get("exclude"); if (exclude.IsEmpty()) return false; if (!exclude.IsArray()) { ThrowError(args.Env(), "Exclude must be an array of strings or empty"); return false; } Napi::Array exclude_array = exclude.As(); for (uint32_t i = 0; i < exclude_array.Length(); ++i) { Napi::Value class_name = exclude_array.Get(i); if (class_name.IsEmpty()) return false; if (class_name.IsString()) { std::string class_name_str = class_name.ToString().Utf8Value(); params->exclude.emplace_back(std::move(class_name_str)); } else { ThrowError(args.Env(), "Exclude must be an array of strings or empty"); return false; } } } if (obj.Has("format")) { Napi::Value format = obj.Get("format"); if (format.IsEmpty()) { return false; } if (!format.IsString()) { ThrowError(args.Env(), "format must be a string: \"json\" or \"flatbuffers\""); return false; } std::string format_str = format.ToString().Utf8Value(); 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 { ThrowError(args.Env(), "format must be a string: \"json\" or \"flatbuffers\""); return false; } } if (obj.Has("snapping")) { Napi::Value snapping = obj.Get("snapping"); if (snapping.IsEmpty()) return false; if (!snapping.IsString()) { ThrowError(args.Env(), "Snapping must be a string: [default, any]"); return false; } std::string snapping_str = snapping.ToString().Utf8Value(); if (snapping_str == "default") { params->snapping = osrm::RouteParameters::SnappingType::Default; } else if (snapping_str == "any") { params->snapping = osrm::RouteParameters::SnappingType::Any; } else { ThrowError(args.Env(), "'snapping' param must be one of [default, any]"); return false; } } return true; } template inline bool parseCommonParameters(const Napi::Object &obj, ParamType ¶ms) { if (obj.Has("steps")) { auto steps = obj.Get("steps"); if (steps.IsEmpty()) return false; if (steps.IsBoolean()) { params->steps = steps.ToBoolean().Value(); } else { ThrowError(obj.Env(), "'steps' param must be a boolean"); return false; } } if (obj.Has("annotations")) { auto annotations = obj.Get("annotations"); if (annotations.IsEmpty()) return false; if (annotations.IsBoolean()) { params->annotations = annotations.ToBoolean().Value(); params->annotations_type = params->annotations ? osrm::RouteParameters::AnnotationsType::All : osrm::RouteParameters::AnnotationsType::None; } else if (annotations.IsArray()) { Napi::Array annotations_array = annotations.As(); for (std::size_t i = 0; i < annotations_array.Length(); i++) { std::string annotations_str = annotations_array.Get(i).ToString().Utf8Value(); if (annotations_str == "duration") { params->annotations_type = params->annotations_type | osrm::RouteParameters::AnnotationsType::Duration; } else if (annotations_str == "nodes") { params->annotations_type = params->annotations_type | osrm::RouteParameters::AnnotationsType::Nodes; } else if (annotations_str == "distance") { params->annotations_type = params->annotations_type | osrm::RouteParameters::AnnotationsType::Distance; } else if (annotations_str == "weight") { params->annotations_type = params->annotations_type | osrm::RouteParameters::AnnotationsType::Weight; } else if (annotations_str == "datasources") { params->annotations_type = params->annotations_type | osrm::RouteParameters::AnnotationsType::Datasources; } else if (annotations_str == "speed") { params->annotations_type = params->annotations_type | osrm::RouteParameters::AnnotationsType::Speed; } else { ThrowError(obj.Env(), "this 'annotations' param is not supported"); return false; } params->annotations = params->annotations_type != osrm::RouteParameters::AnnotationsType::None; } } else { ThrowError(obj.Env(), "this 'annotations' param is not supported"); return false; } } if (obj.Has("geometries")) { Napi::Value geometries = obj.Get("geometries"); if (geometries.IsEmpty()) return false; if (!geometries.IsString()) { ThrowError(obj.Env(), "Geometries must be a string: [polyline, polyline6, geojson]"); return false; } std::string geometries_str = geometries.ToString().Utf8Value(); if (geometries_str == "polyline") { params->geometries = osrm::RouteParameters::GeometriesType::Polyline; } else if (geometries_str == "polyline6") { params->geometries = osrm::RouteParameters::GeometriesType::Polyline6; } else if (geometries_str == "geojson") { params->geometries = osrm::RouteParameters::GeometriesType::GeoJSON; } else { ThrowError(obj.Env(), "'geometries' param must be one of [polyline, polyline6, geojson]"); return false; } } if (obj.Has("overview")) { Napi::Value overview = obj.Get("overview"); if (overview.IsEmpty()) return false; if (!overview.IsString()) { ThrowError(obj.Env(), "Overview must be a string: [simplified, full, false]"); return false; } std::string overview_str = overview.ToString().Utf8Value(); 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 { ThrowError(obj.Env(), "'overview' param must be one of [simplified, full, false]"); return false; } } return true; } inline PluginParameters argumentsToPluginParameters( const Napi::CallbackInfo &args, const std::optional &output_format = {}) { if (args.Length() < 3 || !args[1].IsObject()) { // output to buffer by default for Flatbuffers return {output_format == osrm::engine::api::BaseParameters::OutputFormatType::FLATBUFFERS}; } Napi::Object obj = args[1].As(); if (obj.Has("format")) { Napi::Value format = obj.Get("format"); if (format.IsEmpty()) { return {}; } if (!format.IsString()) { ThrowError(args.Env(), "format must be a string: \"object\" or \"buffer\""); return {}; } std::string format_str = format.ToString().Utf8Value(); if (format_str == "object") { if (output_format == osrm::engine::api::BaseParameters::OutputFormatType::FLATBUFFERS) { ThrowError(args.Env(), "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) { ThrowError(args.Env(), "Deprecated `json_buffer` can only be used with JSON format"); } return {true}; } else { ThrowError(args.Env(), "format must be a string: \"object\" or \"buffer\""); return {}; } } // output to buffer by default for Flatbuffers return {output_format == osrm::engine::api::BaseParameters::OutputFormatType::FLATBUFFERS}; } inline route_parameters_ptr argumentsToRouteParameter(const Napi::CallbackInfo &args, bool requires_multiple_coordinates) { route_parameters_ptr params = std::make_unique(); bool has_base_params = argumentsToParameter(args, params, requires_multiple_coordinates); if (!has_base_params) return route_parameters_ptr(); Napi::Object obj = args[0].As(); if (obj.Has("continue_straight")) { auto value = obj.Get("continue_straight"); if (value.IsEmpty()) return route_parameters_ptr(); if (!value.IsBoolean() && !value.IsNull()) { ThrowError(args.Env(), "'continue_straight' param must be boolean or null"); return route_parameters_ptr(); } if (value.IsBoolean()) { params->continue_straight = value.ToBoolean().Value(); } } if (obj.Has("alternatives")) { auto value = obj.Get("alternatives"); if (value.IsEmpty()) return route_parameters_ptr(); if (value.IsBoolean()) { params->alternatives = value.ToBoolean().Value(); params->number_of_alternatives = value.ToBoolean().Value() ? 1u : 0u; } else if (value.IsNumber()) { params->alternatives = value.ToBoolean().Value(); params->number_of_alternatives = value.ToNumber().Int32Value(); } else { ThrowError(args.Env(), "'alternatives' param must be boolean or number"); return route_parameters_ptr(); } } if (obj.Has("waypoints")) { Napi::Value waypoints = obj.Get("waypoints"); if (waypoints.IsEmpty()) return route_parameters_ptr(); // must be array if (!waypoints.IsArray()) { ThrowError( args.Env(), "Waypoints must be an array of integers corresponding to the input coordinates."); return route_parameters_ptr(); } auto waypoints_array = waypoints.As(); // must have at least two elements if (waypoints_array.Length() < 2) { ThrowError(args.Env(), "At least two waypoints must be provided"); return route_parameters_ptr(); } auto coords_size = params->coordinates.size(); auto waypoints_array_size = waypoints_array.Length(); const auto first_index = waypoints_array.Get(static_cast(0)).ToNumber().Uint32Value(); const auto last_index = waypoints_array.Get(waypoints_array_size - 1).ToNumber().Uint32Value(); if (first_index != 0 || last_index != coords_size - 1) { ThrowError(args.Env(), "First and last waypoints values must correspond to first and last " "coordinate indices"); return route_parameters_ptr(); } for (uint32_t i = 0; i < waypoints_array_size; ++i) { Napi::Value waypoint_value = waypoints_array.Get(i); // all elements must be numbers if (!waypoint_value.IsNumber()) { ThrowError(args.Env(), "Waypoint values must be an array of integers"); return route_parameters_ptr(); } // check that the waypoint index corresponds with an inpute coordinate const auto index = waypoint_value.ToNumber().Uint32Value(); if (index >= coords_size) { ThrowError(args.Env(), "Waypoints must correspond with the index of an input coordinate"); return route_parameters_ptr(); } params->waypoints.emplace_back(index); } if (!params->waypoints.empty()) { for (std::size_t i = 0; i < params->waypoints.size() - 1; i++) { if (params->waypoints[i] >= params->waypoints[i + 1]) { ThrowError(args.Env(), "Waypoints must be supplied in increasing order"); return route_parameters_ptr(); } } } } bool parsedSuccessfully = parseCommonParameters(obj, params); if (!parsedSuccessfully) { return route_parameters_ptr(); } return params; } inline tile_parameters_ptr argumentsToTileParameters(const Napi::CallbackInfo &args, bool /*unused*/) { tile_parameters_ptr params = std::make_unique(); if (args.Length() < 2) { ThrowTypeError(args.Env(), "Coordinate object and callback required"); return tile_parameters_ptr(); } if (!args[0].IsArray()) { ThrowTypeError(args.Env(), "Parameter must be an array [x, y, z]"); return tile_parameters_ptr(); } Napi::Array array = args[0].As(); if (array.Length() != 3) { ThrowTypeError(args.Env(), "Parameter must be an array [x, y, z]"); return tile_parameters_ptr(); } Napi::Value x = array.Get(static_cast(0)); Napi::Value y = array.Get(static_cast(1)); Napi::Value z = array.Get(static_cast(2)); if (x.IsEmpty() || y.IsEmpty() || z.IsEmpty()) return tile_parameters_ptr(); if (!IsUnsignedInteger(x) && !x.IsUndefined()) { ThrowError(args.Env(), "Tile x coordinate must be unsigned interger"); return tile_parameters_ptr(); } if (!IsUnsignedInteger(y) && !y.IsUndefined()) { ThrowError(args.Env(), "Tile y coordinate must be unsigned interger"); return tile_parameters_ptr(); } if (!IsUnsignedInteger(z) && !z.IsUndefined()) { ThrowError(args.Env(), "Tile z coordinate must be unsigned interger"); return tile_parameters_ptr(); } params->x = x.ToNumber().Uint32Value(); params->y = y.ToNumber().Uint32Value(); params->z = z.ToNumber().Uint32Value(); if (!params->IsValid()) { ThrowError(args.Env(), "Invalid tile coordinates"); return tile_parameters_ptr(); } return params; } inline nearest_parameters_ptr argumentsToNearestParameter(const Napi::CallbackInfo &args, bool requires_multiple_coordinates) { nearest_parameters_ptr params = std::make_unique(); bool has_base_params = argumentsToParameter(args, params, requires_multiple_coordinates); if (!has_base_params) return nearest_parameters_ptr(); Napi::Object obj = args[0].As(); if (obj.IsEmpty()) return nearest_parameters_ptr(); if (obj.Has("number")) { Napi::Value number = obj.Get("number"); if (!IsUnsignedInteger(number)) { ThrowError(args.Env(), "Number must be an integer greater than or equal to 1"); return nearest_parameters_ptr(); } else { unsigned number_value = number.ToNumber().Uint32Value(); if (number_value < 1) { ThrowError(args.Env(), "Number must be an integer greater than or equal to 1"); return nearest_parameters_ptr(); } params->number_of_results = number_value; } } return params; } inline table_parameters_ptr argumentsToTableParameter(const Napi::CallbackInfo &args, bool requires_multiple_coordinates) { table_parameters_ptr params = std::make_unique(); bool has_base_params = argumentsToParameter(args, params, requires_multiple_coordinates); if (!has_base_params) return table_parameters_ptr(); Napi::Object obj = args[0].As(); if (obj.IsEmpty()) return table_parameters_ptr(); if (obj.Has("sources")) { Napi::Value sources = obj.Get("sources"); if (sources.IsEmpty()) return table_parameters_ptr(); if (!sources.IsArray()) { ThrowError(args.Env(), "Sources must be an array of indices (or undefined)"); return table_parameters_ptr(); } Napi::Array sources_array = sources.As(); for (uint32_t i = 0; i < sources_array.Length(); ++i) { Napi::Value source = sources_array.Get(i); if (source.IsEmpty()) return table_parameters_ptr(); if (IsUnsignedInteger(source)) { size_t source_value = source.ToNumber().Uint32Value(); if (source_value >= params->coordinates.size()) { ThrowError(args.Env(), "Source indices must be less than the number of coordinates"); return table_parameters_ptr(); } params->sources.push_back(source.ToNumber().Uint32Value()); } else { ThrowError(args.Env(), "Source must be an integer"); return table_parameters_ptr(); } } } if (obj.Has("destinations")) { Napi::Value destinations = obj.Get("destinations"); if (destinations.IsEmpty()) return table_parameters_ptr(); if (!destinations.IsArray()) { ThrowError(args.Env(), "Destinations must be an array of indices (or undefined)"); return table_parameters_ptr(); } Napi::Array destinations_array = destinations.As(); for (uint32_t i = 0; i < destinations_array.Length(); ++i) { Napi::Value destination = destinations_array.Get(i); if (destination.IsEmpty()) return table_parameters_ptr(); if (IsUnsignedInteger(destination)) { size_t destination_value = destination.ToNumber().Uint32Value(); if (destination_value >= params->coordinates.size()) { ThrowError(args.Env(), "Destination indices must be less than the number " "of coordinates"); return table_parameters_ptr(); } params->destinations.push_back(destination_value); } else { ThrowError(args.Env(), "Destination must be an integer"); return table_parameters_ptr(); } } } if (obj.Has("annotations")) { Napi::Value annotations = obj.Get("annotations"); if (annotations.IsEmpty()) return table_parameters_ptr(); if (!annotations.IsArray()) { ThrowError(args.Env(), "Annotations must an array containing 'duration' or 'distance', or both"); return table_parameters_ptr(); } params->annotations = osrm::TableParameters::AnnotationsType::None; Napi::Array annotations_array = annotations.As(); for (std::size_t i = 0; i < annotations_array.Length(); ++i) { std::string annotations_str = annotations_array.Get(i).ToString().Utf8Value(); if (annotations_str == "duration") { params->annotations = params->annotations | osrm::TableParameters::AnnotationsType::Duration; } else if (annotations_str == "distance") { params->annotations = params->annotations | osrm::TableParameters::AnnotationsType::Distance; } else { ThrowError(args.Env(), "this 'annotations' param is not supported"); return table_parameters_ptr(); } } } if (obj.Has("fallback_speed")) { auto fallback_speed = obj.Get("fallback_speed"); if (!fallback_speed.IsNumber()) { ThrowError(args.Env(), "fallback_speed must be a number"); return table_parameters_ptr(); } else if (fallback_speed.ToNumber().DoubleValue() <= 0) { ThrowError(args.Env(), "fallback_speed must be > 0"); return table_parameters_ptr(); } params->fallback_speed = fallback_speed.ToNumber().DoubleValue(); } if (obj.Has("fallback_coordinate")) { auto fallback_coordinate = obj.Get("fallback_coordinate"); if (!fallback_coordinate.IsString()) { ThrowError(args.Env(), "fallback_coordinate must be a string: [input, snapped]"); return table_parameters_ptr(); } std::string fallback_coordinate_str = fallback_coordinate.ToString().Utf8Value(); if (fallback_coordinate_str == "snapped") { params->fallback_coordinate_type = osrm::TableParameters::FallbackCoordinateType::Snapped; } else if (fallback_coordinate_str == "input") { params->fallback_coordinate_type = osrm::TableParameters::FallbackCoordinateType::Input; } else { ThrowError(args.Env(), "'fallback_coordinate' param must be one of [input, snapped]"); return table_parameters_ptr(); } } if (obj.Has("scale_factor")) { auto scale_factor = obj.Get("scale_factor"); if (!scale_factor.IsNumber()) { ThrowError(args.Env(), "scale_factor must be a number"); return table_parameters_ptr(); } else if (scale_factor.ToNumber().DoubleValue() <= 0) { ThrowError(args.Env(), "scale_factor must be > 0"); return table_parameters_ptr(); } params->scale_factor = scale_factor.ToNumber().DoubleValue(); } return params; } inline trip_parameters_ptr argumentsToTripParameter(const Napi::CallbackInfo &args, bool requires_multiple_coordinates) { trip_parameters_ptr params = std::make_unique(); bool has_base_params = argumentsToParameter(args, params, requires_multiple_coordinates); if (!has_base_params) return trip_parameters_ptr(); Napi::Object obj = args[0].As(); bool parsedSuccessfully = parseCommonParameters(obj, params); if (!parsedSuccessfully) { return trip_parameters_ptr(); } if (obj.Has("roundtrip")) { auto roundtrip = obj.Get("roundtrip"); if (roundtrip.IsEmpty()) return trip_parameters_ptr(); if (roundtrip.IsBoolean()) { params->roundtrip = roundtrip.ToBoolean().Value(); } else { ThrowError(args.Env(), "'roundtrip' param must be a boolean"); return trip_parameters_ptr(); } } if (obj.Has("source")) { Napi::Value source = obj.Get("source"); if (source.IsEmpty()) return trip_parameters_ptr(); if (!source.IsString()) { ThrowError(args.Env(), "Source must be a string: [any, first]"); return trip_parameters_ptr(); } std::string source_str = source.ToString().Utf8Value(); if (source_str == "first") { params->source = osrm::TripParameters::SourceType::First; } else if (source_str == "any") { params->source = osrm::TripParameters::SourceType::Any; } else { ThrowError(args.Env(), "'source' param must be one of [any, first]"); return trip_parameters_ptr(); } } if (obj.Has("destination")) { Napi::Value destination = obj.Get("destination"); if (destination.IsEmpty()) return trip_parameters_ptr(); if (!destination.IsString()) { ThrowError(args.Env(), "Destination must be a string: [any, last]"); return trip_parameters_ptr(); } std::string destination_str = destination.ToString().Utf8Value(); if (destination_str == "last") { params->destination = osrm::TripParameters::DestinationType::Last; } else if (destination_str == "any") { params->destination = osrm::TripParameters::DestinationType::Any; } else { ThrowError(args.Env(), "'destination' param must be one of [any, last]"); return trip_parameters_ptr(); } } return params; } inline match_parameters_ptr argumentsToMatchParameter(const Napi::CallbackInfo &args, bool requires_multiple_coordinates) { match_parameters_ptr params = std::make_unique(); bool has_base_params = argumentsToParameter(args, params, requires_multiple_coordinates); if (!has_base_params) return match_parameters_ptr(); Napi::Object obj = args[0].As(); if (obj.Has("timestamps")) { Napi::Value timestamps = obj.Get("timestamps"); if (timestamps.IsEmpty()) return match_parameters_ptr(); if (!timestamps.IsArray()) { ThrowError(args.Env(), "Timestamps must be an array of integers (or undefined)"); return match_parameters_ptr(); } Napi::Array timestamps_array = timestamps.As(); if (params->coordinates.size() != timestamps_array.Length()) { ThrowError(args.Env(), "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) { Napi::Value timestamp = timestamps_array.Get(i); if (timestamp.IsEmpty()) return match_parameters_ptr(); if (!timestamp.IsNumber()) { ThrowError(args.Env(), "Timestamps array items must be numbers"); return match_parameters_ptr(); } params->timestamps.emplace_back(timestamp.ToNumber().Int64Value()); } } if (obj.Has("gaps")) { Napi::Value gaps = obj.Get("gaps"); if (gaps.IsEmpty()) return match_parameters_ptr(); if (!gaps.IsString()) { ThrowError(args.Env(), "Gaps must be a string: [split, ignore]"); return match_parameters_ptr(); } std::string gaps_str = gaps.ToString().Utf8Value(); if (gaps_str == "split") { params->gaps = osrm::MatchParameters::GapsType::Split; } else if (gaps_str == "ignore") { params->gaps = osrm::MatchParameters::GapsType::Ignore; } else { ThrowError(args.Env(), "'gaps' param must be one of [split, ignore]"); return match_parameters_ptr(); } } if (obj.Has("tidy")) { Napi::Value tidy = obj.Get("tidy"); if (tidy.IsEmpty()) return match_parameters_ptr(); if (!tidy.IsBoolean()) { ThrowError(args.Env(), "tidy must be of type Boolean"); return match_parameters_ptr(); } params->tidy = tidy.ToBoolean().Value(); } if (obj.Has("waypoints")) { Napi::Value waypoints = obj.Get("waypoints"); if (waypoints.IsEmpty()) return match_parameters_ptr(); // must be array if (!waypoints.IsArray()) { ThrowError( args.Env(), "Waypoints must be an array of integers corresponding to the input coordinates."); return match_parameters_ptr(); } auto waypoints_array = waypoints.As(); // must have at least two elements if (waypoints_array.Length() < 2) { ThrowError(args.Env(), "At least two waypoints must be provided"); return match_parameters_ptr(); } auto coords_size = params->coordinates.size(); auto waypoints_array_size = waypoints_array.Length(); const auto first_index = waypoints_array.Get(static_cast(0)).ToNumber().Uint32Value(); const auto last_index = waypoints_array.Get(waypoints_array_size - 1).ToNumber().Uint32Value(); if (first_index != 0 || last_index != coords_size - 1) { ThrowError(args.Env(), "First and last waypoints values must correspond to first and last " "coordinate indices"); return match_parameters_ptr(); } for (uint32_t i = 0; i < waypoints_array_size; ++i) { Napi::Value waypoint_value = waypoints_array.Get(i); // all elements must be numbers if (!waypoint_value.IsNumber()) { ThrowError(args.Env(), "Waypoint values must be an array of integers"); return match_parameters_ptr(); } // check that the waypoint index corresponds with an inpute coordinate const auto index = waypoint_value.ToNumber().Uint32Value(); if (index >= coords_size) { ThrowError(args.Env(), "Waypoints must correspond with the index of an input coordinate"); return match_parameters_ptr(); } params->waypoints.emplace_back(index); } } bool parsedSuccessfully = parseCommonParameters(obj, params); if (!parsedSuccessfully) { return match_parameters_ptr(); } return params; } } // namespace node_osrm #endif