From 1e4d11622882629b4fdee61ed1576ba073471dda Mon Sep 17 00:00:00 2001 From: mapsam Date: Thu, 16 Nov 2017 10:12:33 -0800 Subject: [PATCH 01/22] add vector-tile 2.x branch --- scripts/install_deps.sh | 7 +- scripts/setup.sh | 2 +- src/geometry_processors.hpp | 64 ----------------- src/vtquery.cpp | 138 ++++++++++++++++++------------------ 4 files changed, 73 insertions(+), 138 deletions(-) delete mode 100644 src/geometry_processors.hpp diff --git a/scripts/install_deps.sh b/scripts/install_deps.sh index d7c583a2..b9f12daf 100755 --- a/scripts/install_deps.sh +++ b/scripts/install_deps.sh @@ -12,10 +12,11 @@ function install() { ./scripts/setup.sh --config local.env source local.env -install geometry 0.9.2 +install geometry 96d3505 install variant 1.1.4 -install vtzero 556fac5 -install protozero ccf6c39 +install vtzero 1c38ce7 +install protozero 1.6.0 install spatial-algorithms 2904283 install boost 1.65.1 install cheap-ruler 2.5.3 +install vector-tile 0390175 diff --git a/scripts/setup.sh b/scripts/setup.sh index b28b9fbe..83a46f4f 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -3,7 +3,7 @@ set -eu set -o pipefail -export MASON_RELEASE="${MASON_RELEASE:-469edc8}" +export MASON_RELEASE="${MASON_RELEASE:-v0.16.0}" export MASON_LLVM_RELEASE="${MASON_LLVM_RELEASE:-5.0.0}" PLATFORM=$(uname | tr A-Z a-z) diff --git a/src/geometry_processors.hpp b/src/geometry_processors.hpp deleted file mode 100644 index 1fc7eff0..00000000 --- a/src/geometry_processors.hpp +++ /dev/null @@ -1,64 +0,0 @@ -#include -#include -#include - -// convert the point geometry from vtzero into mapbox geometry point structure -// https://gist.github.com/artemp/cfe855cbaf0cbc277ce80b5e768e7d0b#file-gistfile1-txt-L24 -struct point_processor { - point_processor(mapbox::geometry::multi_point& mpoint) - : mpoint_(mpoint) {} - - void points_begin(uint32_t count) const { - if (count > 1) mpoint_.reserve(count); - } - - void points_point(vtzero::point const& point) const { - mpoint_.emplace_back(point.x, point.y); - } - - void points_end() const { - // - } - mapbox::geometry::multi_point& mpoint_; -}; - -struct linestring_processor { - linestring_processor(mapbox::geometry::multi_line_string& mline) - : mline_(mline) {} - - void linestring_begin(std::uint32_t count) { - mline_.emplace_back(); - mline_.back().reserve(count); - } - - void linestring_point(vtzero::point const& point) const { - mline_.back().emplace_back(point.x, point.y); - } - - void linestring_end() const noexcept { - } - mapbox::geometry::multi_line_string& mline_; -}; - -struct polygon_processor { - polygon_processor(mapbox::geometry::multi_polygon& mpoly) - : mpoly_(mpoly), - ring_() {} - - void ring_begin(std::uint32_t count) { - ring_.reserve(count); - } - - void ring_point(vtzero::point const& point) { - ring_.emplace_back(point.x, point.y); - } - - void ring_end(bool is_outer) { - if (is_outer) mpoly_.emplace_back(); - mpoly_.back().push_back(std::move(ring_)); - ring_.clear(); - } - - mapbox::geometry::multi_polygon& mpoly_; - mapbox::geometry::linear_ring ring_; -}; diff --git a/src/vtquery.cpp b/src/vtquery.cpp index 1ce64585..77aa207a 100644 --- a/src/vtquery.cpp +++ b/src/vtquery.cpp @@ -1,5 +1,5 @@ #include "vtquery.hpp" -#include "geometry_processors.hpp" +// #include "geometry_processors.hpp" #include "util.hpp" #include @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -28,11 +29,9 @@ const char* getGeomTypeString(int enumVal) { } struct ResultObject { - using properties_type = std::vector>; - // custom constructor ResultObject( - std::vector>&& props_map, // specifies an r-value completely, whatever had the memory beforehand, this now controls it + mapbox::feature::property_map&& props_map, // specifies an r-value completely, whatever had the memory beforehand, this now controls it std::string const& name, mapbox::geometry::point&& p, double distance0, @@ -52,7 +51,7 @@ struct ResultObject { // use the default destructor ~ResultObject() = default; - std::vector> properties; + mapbox::feature::property_map properties; std::string layer_name; mapbox::geometry::point coordinates; double distance; @@ -128,24 +127,24 @@ struct QueryData { GeomType geometry_filter_type; }; -v8::Local get_property_value(const vtzero::property_value_view value) { - switch (value.type()) { - case vtzero::property_value_type::string_value: - return Nan::New(std::string(value.string_value())).ToLocalChecked(); - case vtzero::property_value_type::float_value: - return Nan::New(double(value.float_value())); - case vtzero::property_value_type::double_value: - return Nan::New(value.double_value()); - case vtzero::property_value_type::int_value: - return Nan::New(value.int_value()); - case vtzero::property_value_type::uint_value: - return Nan::New(value.uint_value()); - case vtzero::property_value_type::sint_value: - return Nan::New(value.sint_value()); - default: // case vtzero::property_value_type::bool_value: - return Nan::New(value.bool_value()); - } -} +// v8::Local get_property_value(const vtzero::property_value_view value) { +// switch (value.type()) { +// case vtzero::property_value_type::string_value: +// return Nan::New(std::string(value.string_value())).ToLocalChecked(); +// case vtzero::property_value_type::float_value: +// return Nan::New(double(value.float_value())); +// case vtzero::property_value_type::double_value: +// return Nan::New(value.double_value()); +// case vtzero::property_value_type::int_value: +// return Nan::New(value.int_value()); +// case vtzero::property_value_type::uint_value: +// return Nan::New(value.uint_value()); +// case vtzero::property_value_type::sint_value: +// return Nan::New(value.sint_value()); +// default: // case vtzero::property_value_type::bool_value: +// return Nan::New(value.bool_value()); +// } +// } struct Worker : Nan::AsyncWorker { using Base = Nan::AsyncWorker; @@ -207,47 +206,51 @@ struct Worker : Nan::AsyncWorker { mapbox::geometry::point query_point = utils::create_query_point(data.longitude, data.latitude, data.zoom, extent, tile_obj.x, tile_obj.y); GeomType original_geometry_type = GeomType::unknown; // set to unknown but this will get overwritten while (auto feature = layer.next_feature()) { + + // extract geometry - mapbox::geometry::geometry + auto query_geometry = mapbox::vector_tile::extract_geometry(feature); + // create a dummy default geometry structure that will be updated in the switch statement below - mapbox::geometry::geometry query_geometry = mapbox::geometry::point(); - // get the geometry type and decode the geometry into mapbox::geometry data structures - switch (feature.geometry_type()) { - case vtzero::GeomType::POINT: { - if (data.geometry_filter_type != GeomType::all && data.geometry_filter_type != GeomType::point) { - continue; - } - mapbox::geometry::multi_point mpoint; - point_processor proc_point(mpoint); - vtzero::decode_point_geometry(feature.geometry(), false, proc_point); - query_geometry = std::move(mpoint); - original_geometry_type = GeomType::point; - break; - } - case vtzero::GeomType::LINESTRING: { - if (data.geometry_filter_type != GeomType::all && data.geometry_filter_type != GeomType::linestring) { - continue; - } - mapbox::geometry::multi_line_string mline; - linestring_processor proc_line(mline); - vtzero::decode_linestring_geometry(feature.geometry(), false, proc_line); - query_geometry = std::move(mline); - original_geometry_type = GeomType::linestring; - break; - } - case vtzero::GeomType::POLYGON: { - if (data.geometry_filter_type != GeomType::all && data.geometry_filter_type != GeomType::polygon) { - continue; - } - mapbox::geometry::multi_polygon mpoly; - polygon_processor proc_poly(mpoly); - vtzero::decode_polygon_geometry(feature.geometry(), false, proc_poly); - query_geometry = std::move(mpoly); - original_geometry_type = GeomType::polygon; - break; - } - default: { - continue; - } - } + // mapbox::geometry::geometry query_geometry = mapbox::geometry::point(); + // // get the geometry type and decode the geometry into mapbox::geometry data structures + // switch (feature.geometry_type()) { + // case vtzero::GeomType::POINT: { + // if (data.geometry_filter_type != GeomType::all && data.geometry_filter_type != GeomType::point) { + // continue; + // } + // mapbox::geometry::multi_point mpoint; + // point_processor proc_point(mpoint); + // vtzero::decode_point_geometry(feature.geometry(), false, proc_point); + // query_geometry = std::move(mpoint); + // original_geometry_type = GeomType::point; + // break; + // } + // case vtzero::GeomType::LINESTRING: { + // if (data.geometry_filter_type != GeomType::all && data.geometry_filter_type != GeomType::linestring) { + // continue; + // } + // mapbox::geometry::multi_line_string mline; + // linestring_processor proc_line(mline); + // vtzero::decode_linestring_geometry(feature.geometry(), false, proc_line); + // query_geometry = std::move(mline); + // original_geometry_type = GeomType::linestring; + // break; + // } + // case vtzero::GeomType::POLYGON: { + // if (data.geometry_filter_type != GeomType::all && data.geometry_filter_type != GeomType::polygon) { + // continue; + // } + // mapbox::geometry::multi_polygon mpoly; + // polygon_processor proc_poly(mpoly); + // vtzero::decode_polygon_geometry(feature.geometry(), false, proc_poly); + // query_geometry = std::move(mpoly); + // original_geometry_type = GeomType::polygon; + // break; + // } + // default: { + // continue; + // } + // } // implement closest point algorithm on query geometry and the query point auto const cp_info = mapbox::geometry::algorithms::closest_point(query_geometry, query_point); @@ -260,16 +263,11 @@ struct Worker : Nan::AsyncWorker { if (meters <= data.radius + 1) { // TODO(sam) https://github.com/mapbox/vtquery/issues/36 // decode properties (will be libvectortile eventually) - ResultObject::properties_type properties_list; - while (auto prop = feature.next_property()) { - std::string key = std::string{prop.key()}; - vtzero::property_value_view value = prop.value(); - properties_list.emplace_back(std::pair(key, value)); - } + auto props = mapbox::vector_tile::extract_properties(feature); // emplace_back allows us to put a new object directly into the vector // wherease push_back we need to create a new object in memory and move it into the vector - results_.emplace_back(std::move(properties_list), + results_.emplace_back(std::move(props), layer_name, std::move(feature_lnglat), meters, From 93b190bd645062d25044678c6d540b9e4cb7e3a7 Mon Sep 17 00:00:00 2001 From: mapsam Date: Tue, 21 Nov 2017 12:35:58 -0800 Subject: [PATCH 02/22] integrate vector-tile, apply visitors, sort during --- src/vtquery.cpp | 210 +++++++++++++++++++++++------------------------- 1 file changed, 100 insertions(+), 110 deletions(-) diff --git a/src/vtquery.cpp b/src/vtquery.cpp index 77aa207a..6fec3ee4 100644 --- a/src/vtquery.cpp +++ b/src/vtquery.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +30,12 @@ const char* getGeomTypeString(int enumVal) { } struct ResultObject { + mapbox::feature::property_map properties; + std::string layer_name; + mapbox::geometry::point coordinates; + double distance; + GeomType original_geometry_type; + // custom constructor ResultObject( mapbox::feature::property_map&& props_map, // specifies an r-value completely, whatever had the memory beforehand, this now controls it @@ -39,8 +46,8 @@ struct ResultObject { : properties(std::move(props_map)), layer_name(name), coordinates(std::move(p)), - distance(distance0), // these are small enough that we can just copy - original_geometry_type(geom_type) {} // these are small enough that we can just copy + distance(distance0), + original_geometry_type(geom_type) {} // default move constructor ResultObject(ResultObject&&) = default; @@ -51,11 +58,11 @@ struct ResultObject { // use the default destructor ~ResultObject() = default; - mapbox::feature::property_map properties; - std::string layer_name; - mapbox::geometry::point coordinates; - double distance; - GeomType original_geometry_type; + // less than operator between result objects + bool operator< (ResultObject const& rhs) const { + return this->distance < rhs.distance; + } + }; struct TileObject { @@ -127,34 +134,47 @@ struct QueryData { GeomType geometry_filter_type; }; -// v8::Local get_property_value(const vtzero::property_value_view value) { -// switch (value.type()) { -// case vtzero::property_value_type::string_value: -// return Nan::New(std::string(value.string_value())).ToLocalChecked(); -// case vtzero::property_value_type::float_value: -// return Nan::New(double(value.float_value())); -// case vtzero::property_value_type::double_value: -// return Nan::New(value.double_value()); -// case vtzero::property_value_type::int_value: -// return Nan::New(value.int_value()); -// case vtzero::property_value_type::uint_value: -// return Nan::New(value.uint_value()); -// case vtzero::property_value_type::sint_value: -// return Nan::New(value.sint_value()); -// default: // case vtzero::property_value_type::bool_value: -// return Nan::New(value.bool_value()); -// } -// } +struct property_value_visitor { + v8::Local & properties_obj; + std::string const& key; + + template + void operator() (T) {} + + void operator() (bool v) { + properties_obj->Set(Nan::New(key).ToLocalChecked(), Nan::New(v)); + } + void operator() (uint64_t v) { + properties_obj->Set(Nan::New(key).ToLocalChecked(), Nan::New(v)); + } + void operator() (int64_t v) { + properties_obj->Set(Nan::New(key).ToLocalChecked(), Nan::New(v)); + } + void operator() (double v) { + properties_obj->Set(Nan::New(key).ToLocalChecked(), Nan::New(v)); + } + void operator() (std::string const& v) { + properties_obj->Set(Nan::New(key).ToLocalChecked(), Nan::New(v).ToLocalChecked()); + } +}; + +void set_property(mapbox::feature::property_map::value_type const& property, + v8::Local & properties_obj) { + mapbox::util::apply_visitor(property_value_visitor{properties_obj, property.first}, property.second); +} struct Worker : Nan::AsyncWorker { using Base = Nan::AsyncWorker; + std::unique_ptr query_data_; + std::deque results_; + std::priority_queue> results_sorted_; + Worker(std::unique_ptr query_data, Nan::Callback* cb) : Base(cb), query_data_(std::move(query_data)), - results_(), - sorted_results_() {} + results_() {} // The Execute() function is getting called when the worker starts to run. // - You only have access to member variables stored in this worker. @@ -206,51 +226,32 @@ struct Worker : Nan::AsyncWorker { mapbox::geometry::point query_point = utils::create_query_point(data.longitude, data.latitude, data.zoom, extent, tile_obj.x, tile_obj.y); GeomType original_geometry_type = GeomType::unknown; // set to unknown but this will get overwritten while (auto feature = layer.next_feature()) { - - // extract geometry - mapbox::geometry::geometry - auto query_geometry = mapbox::vector_tile::extract_geometry(feature); - - // create a dummy default geometry structure that will be updated in the switch statement below - // mapbox::geometry::geometry query_geometry = mapbox::geometry::point(); - // // get the geometry type and decode the geometry into mapbox::geometry data structures - // switch (feature.geometry_type()) { - // case vtzero::GeomType::POINT: { - // if (data.geometry_filter_type != GeomType::all && data.geometry_filter_type != GeomType::point) { - // continue; - // } - // mapbox::geometry::multi_point mpoint; - // point_processor proc_point(mpoint); - // vtzero::decode_point_geometry(feature.geometry(), false, proc_point); - // query_geometry = std::move(mpoint); - // original_geometry_type = GeomType::point; - // break; - // } - // case vtzero::GeomType::LINESTRING: { - // if (data.geometry_filter_type != GeomType::all && data.geometry_filter_type != GeomType::linestring) { - // continue; - // } - // mapbox::geometry::multi_line_string mline; - // linestring_processor proc_line(mline); - // vtzero::decode_linestring_geometry(feature.geometry(), false, proc_line); - // query_geometry = std::move(mline); - // original_geometry_type = GeomType::linestring; - // break; - // } - // case vtzero::GeomType::POLYGON: { - // if (data.geometry_filter_type != GeomType::all && data.geometry_filter_type != GeomType::polygon) { - // continue; - // } - // mapbox::geometry::multi_polygon mpoly; - // polygon_processor proc_poly(mpoly); - // vtzero::decode_polygon_geometry(feature.geometry(), false, proc_poly); - // query_geometry = std::move(mpoly); - // original_geometry_type = GeomType::polygon; - // break; - // } - // default: { - // continue; - // } - // } + switch (feature.geometry_type()) { + case vtzero::GeomType::POINT: { + if (data.geometry_filter_type != GeomType::all && data.geometry_filter_type != GeomType::point) { + continue; + } + auto query_geometry = mapbox::vector_tile::extract_geometry(feature); + break; + } + case vtzero::GeomType::LINESTRING: { + if (data.geometry_filter_type != GeomType::all && data.geometry_filter_type != GeomType::linestring) { + continue; + } + auto query_geometry = mapbox::vector_tile::extract_geometry(feature); + break; + } + case vtzero::GeomType::POLYGON: { + if (data.geometry_filter_type != GeomType::all && data.geometry_filter_type != GeomType::polygon) { + continue; + } + auto query_geometry = mapbox::vector_tile::extract_geometry(feature); + break; + } + default: { + continue; + } + } // implement closest point algorithm on query geometry and the query point auto const cp_info = mapbox::geometry::algorithms::closest_point(query_geometry, query_point); @@ -260,32 +261,31 @@ struct Worker : Nan::AsyncWorker { auto meters = utils::distance_in_meters(query_lnglat, feature_lnglat); // if the distance is within the threshold, save it - if (meters <= data.radius + 1) { // TODO(sam) https://github.com/mapbox/vtquery/issues/36 - - // decode properties (will be libvectortile eventually) - auto props = mapbox::vector_tile::extract_properties(feature); - - // emplace_back allows us to put a new object directly into the vector - // wherease push_back we need to create a new object in memory and move it into the vector - results_.emplace_back(std::move(props), - layer_name, - std::move(feature_lnglat), - meters, - original_geometry_type); + if (meters <= data.radius + 1 && meters >= 0.0) { + if (results_sorted_.size() < data.num_results) { + auto props = mapbox::vector_tile::extract_properties(feature); + ResultObject r(std::move(props), + layer_name, + std::move(feature_lnglat), + meters, + original_geometry_type); + results_.push_back(r); + results_sorted_.emplace(&r); + } else if (meters < results_sorted_.top()->distance) { + results_sorted_.pop(); + auto props = mapbox::vector_tile::extract_properties(feature); + ResultObject r(std::move(props), + layer_name, + std::move(feature_lnglat), + meters, + original_geometry_type); + results_.push_back(r); + results_sorted_.emplace(&r); + } } } // end tile.layer.feature loop } // end tile.layer loop } // end tile loop - - // create sort vector - sorted_results_.reserve(results_.size()); - for (auto& r : results_) { - sorted_results_.push_back(&r); // save the pointer of r - } - - // sort based on distance - std::sort(sorted_results_.begin(), sorted_results_.end(), [](ResultObject const* a, ResultObject const* b) { return a->distance < b->distance; }); - } catch (const std::exception& e) { SetErrorMessage(e.what()); } @@ -297,17 +297,14 @@ struct Worker : Nan::AsyncWorker { Nan::HandleScope scope; // number of results to loop through - QueryData const& data = *query_data_; - std::uint32_t num_results = data.num_results; - v8::Local results_object = Nan::New(); - v8::Local features_array = Nan::New(); - std::uint32_t features_size = 0; + v8::Local features_array = Nan::New(results_sorted_.size()); results_object->Set(Nan::New("type").ToLocalChecked(), Nan::New("FeatureCollection").ToLocalChecked()); // for each result object - for (auto feature : sorted_results_) { + while (!results_sorted_.empty()) { + auto const& feature = results_sorted_.top(); // get reference to top item in results queue v8::Local feature_obj = Nan::New(); feature_obj->Set(Nan::New("type").ToLocalChecked(), Nan::New("Feature").ToLocalChecked()); @@ -324,7 +321,7 @@ struct Worker : Nan::AsyncWorker { v8::Local properties_obj = Nan::New(); v8::Local tilequery_properties_obj = Nan::New(); for (auto const& prop : feature->properties) { - properties_obj->Set(Nan::New(prop.first).ToLocalChecked(), get_property_value(prop.second)); + set_property(prop, properties_obj); } // set properties.tilquery @@ -338,11 +335,8 @@ struct Worker : Nan::AsyncWorker { feature_obj->Set(Nan::New("properties").ToLocalChecked(), properties_obj); // add feature to features array - features_array->Set(features_size, feature_obj); - features_size++; - if (features_size >= num_results) { - break; - } + features_array->Set(static_cast(results_.size()-1), feature_obj); + results_sorted_.pop(); // remove item from results queue } results_object->Set(Nan::New("features").ToLocalChecked(), features_array); @@ -354,10 +348,6 @@ struct Worker : Nan::AsyncWorker { // Static cast done here to avoid 'cppcoreguidelines-pro-bounds-array-to-pointer-decay' warning with clang-tidy callback->Call(argc, static_cast*>(argv)); } - - std::unique_ptr query_data_; - std::deque results_; - std::vector sorted_results_; }; NAN_METHOD(vtquery) { From a8715a195ebd68a7204a0d3cb268c59be56fb3a8 Mon Sep 17 00:00:00 2001 From: mapsam Date: Tue, 21 Nov 2017 12:40:16 -0800 Subject: [PATCH 03/22] move --- src/vtquery.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/vtquery.cpp b/src/vtquery.cpp index 6fec3ee4..e2a1bb58 100644 --- a/src/vtquery.cpp +++ b/src/vtquery.cpp @@ -163,6 +163,9 @@ void set_property(mapbox::feature::property_map::value_type const& property, mapbox::util::apply_visitor(property_value_visitor{properties_obj, property.first}, property.second); } +// properties_obj->Set(Nan::New(prop.first).ToLocalChecked(), get_property_value(prop.second)); + + struct Worker : Nan::AsyncWorker { using Base = Nan::AsyncWorker; @@ -226,26 +229,27 @@ struct Worker : Nan::AsyncWorker { mapbox::geometry::point query_point = utils::create_query_point(data.longitude, data.latitude, data.zoom, extent, tile_obj.x, tile_obj.y); GeomType original_geometry_type = GeomType::unknown; // set to unknown but this will get overwritten while (auto feature = layer.next_feature()) { + mapbox::geometry::geometry query_geometry = mapbox::geometry::point(); switch (feature.geometry_type()) { case vtzero::GeomType::POINT: { if (data.geometry_filter_type != GeomType::all && data.geometry_filter_type != GeomType::point) { continue; } - auto query_geometry = mapbox::vector_tile::extract_geometry(feature); + query_geometry = mapbox::vector_tile::extract_geometry(feature); break; } case vtzero::GeomType::LINESTRING: { if (data.geometry_filter_type != GeomType::all && data.geometry_filter_type != GeomType::linestring) { continue; } - auto query_geometry = mapbox::vector_tile::extract_geometry(feature); + query_geometry = mapbox::vector_tile::extract_geometry(feature); break; } case vtzero::GeomType::POLYGON: { if (data.geometry_filter_type != GeomType::all && data.geometry_filter_type != GeomType::polygon) { continue; } - auto query_geometry = mapbox::vector_tile::extract_geometry(feature); + query_geometry = mapbox::vector_tile::extract_geometry(feature); break; } default: { @@ -269,7 +273,7 @@ struct Worker : Nan::AsyncWorker { std::move(feature_lnglat), meters, original_geometry_type); - results_.push_back(r); + results_.emplace_back(std::move(r)); results_sorted_.emplace(&r); } else if (meters < results_sorted_.top()->distance) { results_sorted_.pop(); @@ -279,7 +283,7 @@ struct Worker : Nan::AsyncWorker { std::move(feature_lnglat), meters, original_geometry_type); - results_.push_back(r); + results_.emplace_back(std::move(r)); results_sorted_.emplace(&r); } } From 90beb6ffb8e8f1c6a4431626ea2a32f93b09a50f Mon Sep 17 00:00:00 2001 From: mapsam Date: Wed, 22 Nov 2017 09:51:58 -0800 Subject: [PATCH 04/22] 0.0 conditional, priority queue --- src/vtquery.cpp | 92 +++++++++++++--------- test/vtquery.test.js | 177 ++++++++++++++++++++++--------------------- 2 files changed, 144 insertions(+), 125 deletions(-) diff --git a/src/vtquery.cpp b/src/vtquery.cpp index e2a1bb58..f11e4ba9 100644 --- a/src/vtquery.cpp +++ b/src/vtquery.cpp @@ -39,7 +39,7 @@ struct ResultObject { // custom constructor ResultObject( mapbox::feature::property_map&& props_map, // specifies an r-value completely, whatever had the memory beforehand, this now controls it - std::string const& name, + std::string name, mapbox::geometry::point&& p, double distance0, GeomType geom_type) @@ -57,12 +57,6 @@ struct ResultObject { // use the default destructor ~ResultObject() = default; - - // less than operator between result objects - bool operator< (ResultObject const& rhs) const { - return this->distance < rhs.distance; - } - }; struct TileObject { @@ -163,15 +157,19 @@ void set_property(mapbox::feature::property_map::value_type const& property, mapbox::util::apply_visitor(property_value_visitor{properties_obj, property.first}, property.second); } -// properties_obj->Set(Nan::New(prop.first).ToLocalChecked(), get_property_value(prop.second)); - +struct CompareDistance +{ + bool operator()(const ResultObject* r1, const ResultObject* r2) const { + return r1->distance < r2->distance; + } +}; struct Worker : Nan::AsyncWorker { using Base = Nan::AsyncWorker; std::unique_ptr query_data_; std::deque results_; - std::priority_queue> results_sorted_; + std::priority_queue, CompareDistance> results_queue_; Worker(std::unique_ptr query_data, Nan::Callback* cb) @@ -235,6 +233,7 @@ struct Worker : Nan::AsyncWorker { if (data.geometry_filter_type != GeomType::all && data.geometry_filter_type != GeomType::point) { continue; } + original_geometry_type = GeomType::point; query_geometry = mapbox::vector_tile::extract_geometry(feature); break; } @@ -242,6 +241,7 @@ struct Worker : Nan::AsyncWorker { if (data.geometry_filter_type != GeomType::all && data.geometry_filter_type != GeomType::linestring) { continue; } + original_geometry_type = GeomType::linestring; query_geometry = mapbox::vector_tile::extract_geometry(feature); break; } @@ -249,6 +249,7 @@ struct Worker : Nan::AsyncWorker { if (data.geometry_filter_type != GeomType::all && data.geometry_filter_type != GeomType::polygon) { continue; } + original_geometry_type = GeomType::polygon; query_geometry = mapbox::vector_tile::extract_geometry(feature); break; } @@ -260,31 +261,49 @@ struct Worker : Nan::AsyncWorker { // implement closest point algorithm on query geometry and the query point auto const cp_info = mapbox::geometry::algorithms::closest_point(query_geometry, query_point); - // convert x/y into lng/lat point - auto feature_lnglat = utils::convert_vt_to_ll(extent, tile_obj.z, tile_obj.x, tile_obj.y, cp_info); - auto meters = utils::distance_in_meters(query_lnglat, feature_lnglat); - - // if the distance is within the threshold, save it - if (meters <= data.radius + 1 && meters >= 0.0) { - if (results_sorted_.size() < data.num_results) { - auto props = mapbox::vector_tile::extract_properties(feature); - ResultObject r(std::move(props), - layer_name, - std::move(feature_lnglat), - meters, - original_geometry_type); - results_.emplace_back(std::move(r)); - results_sorted_.emplace(&r); - } else if (meters < results_sorted_.top()->distance) { - results_sorted_.pop(); + // if the distance is exactly 0.0, add it to the list downright + // otherwise check if the radius is greater than 0.0 and less than the radius + if (cp_info.distance == 0.0) { + // we'll only add the first "n" results depending on the num_results - there's no way to sort otherwise + if (results_queue_.size() < data.num_results) { + auto qp = mapbox::geometry::point{data.longitude, data.latitude}; // original query lng/lat auto props = mapbox::vector_tile::extract_properties(feature); ResultObject r(std::move(props), layer_name, - std::move(feature_lnglat), - meters, + std::move(qp), + 0.0, original_geometry_type); results_.emplace_back(std::move(r)); - results_sorted_.emplace(&r); + results_queue_.emplace(&r); + } + } else { + // convert x/y into lng/lat point + auto feature_lnglat = utils::convert_vt_to_ll(extent, tile_obj.z, tile_obj.x, tile_obj.y, cp_info); + auto meters = utils::distance_in_meters(query_lnglat, feature_lnglat); + + // if the distance is within the threshold, save it + if (meters <= data.radius && meters >= 0.0) { + if (results_queue_.size() < data.num_results) { + std::clog << "hit! layer name: " << layer_name << std::endl; + auto props = mapbox::vector_tile::extract_properties(feature); + ResultObject r(std::move(props), + layer_name, + std::move(feature_lnglat), + meters, + original_geometry_type); + results_.emplace_back(std::move(r)); + results_queue_.emplace(&r); + } else if (meters < results_queue_.top()->distance) { + results_queue_.pop(); + auto props = mapbox::vector_tile::extract_properties(feature); + ResultObject r(std::move(props), + layer_name, + std::move(feature_lnglat), + meters, + original_geometry_type); + results_.emplace_back(std::move(r)); + results_queue_.emplace(&r); + } } } } // end tile.layer.feature loop @@ -302,13 +321,12 @@ struct Worker : Nan::AsyncWorker { // number of results to loop through v8::Local results_object = Nan::New(); - v8::Local features_array = Nan::New(results_sorted_.size()); - + v8::Local features_array = Nan::New(); results_object->Set(Nan::New("type").ToLocalChecked(), Nan::New("FeatureCollection").ToLocalChecked()); // for each result object - while (!results_sorted_.empty()) { - auto const& feature = results_sorted_.top(); // get reference to top item in results queue + while (!results_queue_.empty()) { + auto const& feature = results_queue_.top(); // get reference to top item in results queue v8::Local feature_obj = Nan::New(); feature_obj->Set(Nan::New("type").ToLocalChecked(), Nan::New("Feature").ToLocalChecked()); @@ -323,12 +341,12 @@ struct Worker : Nan::AsyncWorker { // create properties object v8::Local properties_obj = Nan::New(); - v8::Local tilequery_properties_obj = Nan::New(); for (auto const& prop : feature->properties) { set_property(prop, properties_obj); } // set properties.tilquery + v8::Local tilequery_properties_obj = Nan::New(); tilequery_properties_obj->Set(Nan::New("distance").ToLocalChecked(), Nan::New(feature->distance)); std::string og_geom = getGeomTypeString(feature->original_geometry_type); tilequery_properties_obj->Set(Nan::New("geometry").ToLocalChecked(), Nan::New(og_geom).ToLocalChecked()); @@ -339,8 +357,8 @@ struct Worker : Nan::AsyncWorker { feature_obj->Set(Nan::New("properties").ToLocalChecked(), properties_obj); // add feature to features array - features_array->Set(static_cast(results_.size()-1), feature_obj); - results_sorted_.pop(); // remove item from results queue + features_array->Set(static_cast(results_queue_.size()-1), feature_obj); + results_queue_.pop(); // remove item from results queue } results_object->Set(Nan::New("features").ToLocalChecked(), features_array); diff --git a/test/vtquery.test.js b/test/vtquery.test.js index 2c946190..7b6e7cb4 100644 --- a/test/vtquery.test.js +++ b/test/vtquery.test.js @@ -437,98 +437,99 @@ test('options - numResults: successfully limits results', assert => { test('options - layers: successfully returns only requested layers', assert => { const buffer = bufferSF; const ll = [-122.4477, 37.7665]; // direct hit - vtquery([{buffer: buffer, z: 15, x: 5238, y: 12666}], ll, {radius: 2000, layers: ['poi_label']}, function(err, result) { + vtquery([{buffer: buffer, z: 15, x: 5238, y: 12666}], ll, {radius: 2000, layers: ['place_label']}, function(err, result) { assert.ifError(err); result.features.forEach(function(feature) { + console.log(feature); assert.equal(feature.properties.tilequery.layer, 'poi_label', 'proper layer'); }); assert.end(); }); }); - -test('options - geometry: successfully returns only points', assert => { - const buffer = bufferSF; - const ll = [-122.4477, 37.7665]; // direct hit - vtquery([{buffer: buffer, z: 15, x: 5238, y: 12666}], ll, {radius: 2000, geometry: 'point'}, function(err, result) { - assert.ifError(err); - assert.equal(result.features.length, 5, 'expected number of features'); - result.features.forEach(function(feature) { - assert.equal(feature.properties.tilequery.geometry, 'point', 'expected original geometry'); - }); - assert.end(); - }); -}); - -test('options - geometry: successfully returns only linestrings', assert => { - const buffer = bufferSF; - const ll = [-122.4477, 37.7665]; // direct hit - vtquery([{buffer: buffer, z: 15, x: 5238, y: 12666}], ll, {radius: 200, geometry: 'linestring'}, function(err, result) { - assert.ifError(err); - assert.equal(result.features.length, 5, 'expected number of features'); - result.features.forEach(function(feature) { - assert.equal(feature.properties.tilequery.geometry, 'linestring', 'expected original geometry'); - }); - assert.end(); - }); -}); - - -test('options - geometry: successfully returns only polygons', assert => { - const buffer = bufferSF; - const ll = [-122.4477, 37.7665]; // direct hit - vtquery([{buffer: buffer, z: 15, x: 5238, y: 12666}], ll, {radius: 200, geometry: 'polygon'}, function(err, result) { - assert.ifError(err); - assert.equal(result.features.length, 5, 'expected number of features'); - result.features.forEach(function(feature) { - assert.equal(feature.properties.tilequery.geometry, 'polygon', 'expected original geometry'); - }); - assert.end(); - }); -}); - -test('success: returns all possible data value types', assert => { - const tiles = [{buffer: mvtf.get('038').buffer, z: 15, x: 5248, y: 11436}]; - const opts = { - radius: 800 // about the width of a z15 tile - } - vtquery(tiles, [-122.3384, 47.6635], opts, function(err, result) { - const p = result.features[0].properties; - - assert.equal(typeof p.string_value, 'string', 'expected value type'); - assert.equal(typeof p.bool_value, 'boolean', 'expected value type'); - assert.equal(typeof p.int_value, 'number', 'expected value type'); - assert.equal(typeof p.double_value, 'number', 'expected value type'); - assert.ok(p.double_value % 1 !== 0, 'expected decimal'); - assert.equal(typeof p.float_value, 'number', 'expected value type'); - assert.ok(p.float_value % 1 !== 0, 'expected decimal'); - assert.equal(typeof p.sint_value, 'number', 'expected value type'); - assert.ok(p.sint_value < 0, 'expected signedness'); - assert.equal(typeof p.uint_value, 'number', 'expected value type'); - assert.ifError(err); - assert.end(); - }); -}); - -test('success: does not throw on unknown geometry type', assert => { - const tiles = [{buffer: mvtf.get('003').buffer, z: 15, x: 5248, y: 11436}]; - const opts = { - radius: 800 // about the width of a z15 tile - } - vtquery(tiles, [-122.3384, 47.6635], opts, function(err, result) { - assert.ifError(err); - assert.notOk(result.features.length); - assert.end(); - }); -}); - -test('error: throws on invalid tile that is missing geometry type', assert => { - const tiles = [{buffer: mvtf.get('004').buffer, z: 15, x: 5248, y: 11436}]; - const opts = { - radius: 800 // about the width of a z15 tile - } - vtquery(tiles, [-122.3384, 47.6635], opts, function(err, result) { - assert.ok(err); - assert.equal(err.message, 'Missing geometry field in feature (spec 4.2)', 'expected error message'); - assert.end(); - }); -}); +// +// test('options - geometry: successfully returns only points', assert => { +// const buffer = bufferSF; +// const ll = [-122.4477, 37.7665]; // direct hit +// vtquery([{buffer: buffer, z: 15, x: 5238, y: 12666}], ll, {radius: 2000, geometry: 'point'}, function(err, result) { +// assert.ifError(err); +// assert.equal(result.features.length, 5, 'expected number of features'); +// result.features.forEach(function(feature) { +// assert.equal(feature.properties.tilequery.geometry, 'point', 'expected original geometry'); +// }); +// assert.end(); +// }); +// }); +// +// test('options - geometry: successfully returns only linestrings', assert => { +// const buffer = bufferSF; +// const ll = [-122.4477, 37.7665]; // direct hit +// vtquery([{buffer: buffer, z: 15, x: 5238, y: 12666}], ll, {radius: 200, geometry: 'linestring'}, function(err, result) { +// assert.ifError(err); +// assert.equal(result.features.length, 5, 'expected number of features'); +// result.features.forEach(function(feature) { +// assert.equal(feature.properties.tilequery.geometry, 'linestring', 'expected original geometry'); +// }); +// assert.end(); +// }); +// }); +// +// +// test('options - geometry: successfully returns only polygons', assert => { +// const buffer = bufferSF; +// const ll = [-122.4477, 37.7665]; // direct hit +// vtquery([{buffer: buffer, z: 15, x: 5238, y: 12666}], ll, {radius: 200, geometry: 'polygon'}, function(err, result) { +// assert.ifError(err); +// assert.equal(result.features.length, 5, 'expected number of features'); +// result.features.forEach(function(feature) { +// assert.equal(feature.properties.tilequery.geometry, 'polygon', 'expected original geometry'); +// }); +// assert.end(); +// }); +// }); +// +// test('success: returns all possible data value types', assert => { +// const tiles = [{buffer: mvtf.get('038').buffer, z: 15, x: 5248, y: 11436}]; +// const opts = { +// radius: 800 // about the width of a z15 tile +// } +// vtquery(tiles, [-122.3384, 47.6635], opts, function(err, result) { +// const p = result.features[0].properties; +// +// assert.equal(typeof p.string_value, 'string', 'expected value type'); +// assert.equal(typeof p.bool_value, 'boolean', 'expected value type'); +// assert.equal(typeof p.int_value, 'number', 'expected value type'); +// assert.equal(typeof p.double_value, 'number', 'expected value type'); +// assert.ok(p.double_value % 1 !== 0, 'expected decimal'); +// assert.equal(typeof p.float_value, 'number', 'expected value type'); +// assert.ok(p.float_value % 1 !== 0, 'expected decimal'); +// assert.equal(typeof p.sint_value, 'number', 'expected value type'); +// assert.ok(p.sint_value < 0, 'expected signedness'); +// assert.equal(typeof p.uint_value, 'number', 'expected value type'); +// assert.ifError(err); +// assert.end(); +// }); +// }); +// +// test('success: does not throw on unknown geometry type', assert => { +// const tiles = [{buffer: mvtf.get('003').buffer, z: 15, x: 5248, y: 11436}]; +// const opts = { +// radius: 800 // about the width of a z15 tile +// } +// vtquery(tiles, [-122.3384, 47.6635], opts, function(err, result) { +// assert.ifError(err); +// assert.notOk(result.features.length); +// assert.end(); +// }); +// }); +// +// test('error: throws on invalid tile that is missing geometry type', assert => { +// const tiles = [{buffer: mvtf.get('004').buffer, z: 15, x: 5248, y: 11436}]; +// const opts = { +// radius: 800 // about the width of a z15 tile +// } +// vtquery(tiles, [-122.3384, 47.6635], opts, function(err, result) { +// assert.ok(err); +// assert.equal(err.message, 'Missing geometry field in feature (spec 4.2)', 'expected error message'); +// assert.end(); +// }); +// }); From 4e3ca66795906954f23d60ee27867538dfdd0a06 Mon Sep 17 00:00:00 2001 From: mapsam Date: Wed, 22 Nov 2017 09:54:01 -0800 Subject: [PATCH 05/22] initialize empty pqueue --- src/vtquery.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vtquery.cpp b/src/vtquery.cpp index f11e4ba9..8f09008f 100644 --- a/src/vtquery.cpp +++ b/src/vtquery.cpp @@ -175,7 +175,8 @@ struct Worker : Nan::AsyncWorker { Nan::Callback* cb) : Base(cb), query_data_(std::move(query_data)), - results_() {} + results_(), + results_queue_() {} // The Execute() function is getting called when the worker starts to run. // - You only have access to member variables stored in this worker. From d8f905864f5990b0b6e3b2bc9f6f5afdb09324ff Mon Sep 17 00:00:00 2001 From: mapsam Date: Wed, 22 Nov 2017 10:13:44 -0800 Subject: [PATCH 06/22] back() --- src/vtquery.cpp | 40 +++++----- test/vtquery.test.js | 176 +++++++++++++++++++++---------------------- 2 files changed, 106 insertions(+), 110 deletions(-) diff --git a/src/vtquery.cpp b/src/vtquery.cpp index 8f09008f..381f8ad4 100644 --- a/src/vtquery.cpp +++ b/src/vtquery.cpp @@ -269,13 +269,12 @@ struct Worker : Nan::AsyncWorker { if (results_queue_.size() < data.num_results) { auto qp = mapbox::geometry::point{data.longitude, data.latitude}; // original query lng/lat auto props = mapbox::vector_tile::extract_properties(feature); - ResultObject r(std::move(props), - layer_name, - std::move(qp), - 0.0, - original_geometry_type); - results_.emplace_back(std::move(r)); - results_queue_.emplace(&r); + results_.emplace_back(std::move(props), + layer_name, + std::move(qp), + 0.0, + original_geometry_type); + results_queue_.emplace(&results_.back()); } } else { // convert x/y into lng/lat point @@ -285,25 +284,22 @@ struct Worker : Nan::AsyncWorker { // if the distance is within the threshold, save it if (meters <= data.radius && meters >= 0.0) { if (results_queue_.size() < data.num_results) { - std::clog << "hit! layer name: " << layer_name << std::endl; auto props = mapbox::vector_tile::extract_properties(feature); - ResultObject r(std::move(props), - layer_name, - std::move(feature_lnglat), - meters, - original_geometry_type); - results_.emplace_back(std::move(r)); - results_queue_.emplace(&r); + results_.emplace_back(std::move(props), + layer_name, + std::move(feature_lnglat), + meters, + original_geometry_type); + results_queue_.emplace(&results_.back()); } else if (meters < results_queue_.top()->distance) { results_queue_.pop(); auto props = mapbox::vector_tile::extract_properties(feature); - ResultObject r(std::move(props), - layer_name, - std::move(feature_lnglat), - meters, - original_geometry_type); - results_.emplace_back(std::move(r)); - results_queue_.emplace(&r); + results_.emplace_back(std::move(props), + layer_name, + std::move(feature_lnglat), + meters, + original_geometry_type); + results_queue_.emplace(&results_.back()); } } } diff --git a/test/vtquery.test.js b/test/vtquery.test.js index 7b6e7cb4..08d40293 100644 --- a/test/vtquery.test.js +++ b/test/vtquery.test.js @@ -437,7 +437,7 @@ test('options - numResults: successfully limits results', assert => { test('options - layers: successfully returns only requested layers', assert => { const buffer = bufferSF; const ll = [-122.4477, 37.7665]; // direct hit - vtquery([{buffer: buffer, z: 15, x: 5238, y: 12666}], ll, {radius: 2000, layers: ['place_label']}, function(err, result) { + vtquery([{buffer: buffer, z: 15, x: 5238, y: 12666}], ll, {radius: 2000, layers: ['poi_label']}, function(err, result) { assert.ifError(err); result.features.forEach(function(feature) { console.log(feature); @@ -446,90 +446,90 @@ test('options - layers: successfully returns only requested layers', assert => { assert.end(); }); }); -// -// test('options - geometry: successfully returns only points', assert => { -// const buffer = bufferSF; -// const ll = [-122.4477, 37.7665]; // direct hit -// vtquery([{buffer: buffer, z: 15, x: 5238, y: 12666}], ll, {radius: 2000, geometry: 'point'}, function(err, result) { -// assert.ifError(err); -// assert.equal(result.features.length, 5, 'expected number of features'); -// result.features.forEach(function(feature) { -// assert.equal(feature.properties.tilequery.geometry, 'point', 'expected original geometry'); -// }); -// assert.end(); -// }); -// }); -// -// test('options - geometry: successfully returns only linestrings', assert => { -// const buffer = bufferSF; -// const ll = [-122.4477, 37.7665]; // direct hit -// vtquery([{buffer: buffer, z: 15, x: 5238, y: 12666}], ll, {radius: 200, geometry: 'linestring'}, function(err, result) { -// assert.ifError(err); -// assert.equal(result.features.length, 5, 'expected number of features'); -// result.features.forEach(function(feature) { -// assert.equal(feature.properties.tilequery.geometry, 'linestring', 'expected original geometry'); -// }); -// assert.end(); -// }); -// }); -// -// -// test('options - geometry: successfully returns only polygons', assert => { -// const buffer = bufferSF; -// const ll = [-122.4477, 37.7665]; // direct hit -// vtquery([{buffer: buffer, z: 15, x: 5238, y: 12666}], ll, {radius: 200, geometry: 'polygon'}, function(err, result) { -// assert.ifError(err); -// assert.equal(result.features.length, 5, 'expected number of features'); -// result.features.forEach(function(feature) { -// assert.equal(feature.properties.tilequery.geometry, 'polygon', 'expected original geometry'); -// }); -// assert.end(); -// }); -// }); -// -// test('success: returns all possible data value types', assert => { -// const tiles = [{buffer: mvtf.get('038').buffer, z: 15, x: 5248, y: 11436}]; -// const opts = { -// radius: 800 // about the width of a z15 tile -// } -// vtquery(tiles, [-122.3384, 47.6635], opts, function(err, result) { -// const p = result.features[0].properties; -// -// assert.equal(typeof p.string_value, 'string', 'expected value type'); -// assert.equal(typeof p.bool_value, 'boolean', 'expected value type'); -// assert.equal(typeof p.int_value, 'number', 'expected value type'); -// assert.equal(typeof p.double_value, 'number', 'expected value type'); -// assert.ok(p.double_value % 1 !== 0, 'expected decimal'); -// assert.equal(typeof p.float_value, 'number', 'expected value type'); -// assert.ok(p.float_value % 1 !== 0, 'expected decimal'); -// assert.equal(typeof p.sint_value, 'number', 'expected value type'); -// assert.ok(p.sint_value < 0, 'expected signedness'); -// assert.equal(typeof p.uint_value, 'number', 'expected value type'); -// assert.ifError(err); -// assert.end(); -// }); -// }); -// -// test('success: does not throw on unknown geometry type', assert => { -// const tiles = [{buffer: mvtf.get('003').buffer, z: 15, x: 5248, y: 11436}]; -// const opts = { -// radius: 800 // about the width of a z15 tile -// } -// vtquery(tiles, [-122.3384, 47.6635], opts, function(err, result) { -// assert.ifError(err); -// assert.notOk(result.features.length); -// assert.end(); -// }); -// }); -// -// test('error: throws on invalid tile that is missing geometry type', assert => { -// const tiles = [{buffer: mvtf.get('004').buffer, z: 15, x: 5248, y: 11436}]; -// const opts = { -// radius: 800 // about the width of a z15 tile -// } -// vtquery(tiles, [-122.3384, 47.6635], opts, function(err, result) { -// assert.ok(err); -// assert.equal(err.message, 'Missing geometry field in feature (spec 4.2)', 'expected error message'); -// assert.end(); -// }); -// }); + +test('options - geometry: successfully returns only points', assert => { + const buffer = bufferSF; + const ll = [-122.4477, 37.7665]; // direct hit + vtquery([{buffer: buffer, z: 15, x: 5238, y: 12666}], ll, {radius: 2000, geometry: 'point'}, function(err, result) { + assert.ifError(err); + assert.equal(result.features.length, 5, 'expected number of features'); + result.features.forEach(function(feature) { + assert.equal(feature.properties.tilequery.geometry, 'point', 'expected original geometry'); + }); + assert.end(); + }); +}); + +test('options - geometry: successfully returns only linestrings', assert => { + const buffer = bufferSF; + const ll = [-122.4477, 37.7665]; // direct hit + vtquery([{buffer: buffer, z: 15, x: 5238, y: 12666}], ll, {radius: 200, geometry: 'linestring'}, function(err, result) { + assert.ifError(err); + assert.equal(result.features.length, 5, 'expected number of features'); + result.features.forEach(function(feature) { + assert.equal(feature.properties.tilequery.geometry, 'linestring', 'expected original geometry'); + }); + assert.end(); + }); +}); + + +test('options - geometry: successfully returns only polygons', assert => { + const buffer = bufferSF; + const ll = [-122.4477, 37.7665]; // direct hit + vtquery([{buffer: buffer, z: 15, x: 5238, y: 12666}], ll, {radius: 200, geometry: 'polygon'}, function(err, result) { + assert.ifError(err); + assert.equal(result.features.length, 5, 'expected number of features'); + result.features.forEach(function(feature) { + assert.equal(feature.properties.tilequery.geometry, 'polygon', 'expected original geometry'); + }); + assert.end(); + }); +}); + +test('success: returns all possible data value types', assert => { + const tiles = [{buffer: mvtf.get('038').buffer, z: 15, x: 5248, y: 11436}]; + const opts = { + radius: 800 // about the width of a z15 tile + } + vtquery(tiles, [-122.3384, 47.6635], opts, function(err, result) { + const p = result.features[0].properties; + + assert.equal(typeof p.string_value, 'string', 'expected value type'); + assert.equal(typeof p.bool_value, 'boolean', 'expected value type'); + assert.equal(typeof p.int_value, 'number', 'expected value type'); + assert.equal(typeof p.double_value, 'number', 'expected value type'); + assert.ok(p.double_value % 1 !== 0, 'expected decimal'); + assert.equal(typeof p.float_value, 'number', 'expected value type'); + assert.ok(p.float_value % 1 !== 0, 'expected decimal'); + assert.equal(typeof p.sint_value, 'number', 'expected value type'); + assert.ok(p.sint_value < 0, 'expected signedness'); + assert.equal(typeof p.uint_value, 'number', 'expected value type'); + assert.ifError(err); + assert.end(); + }); +}); + +test('success: does not throw on unknown geometry type', assert => { + const tiles = [{buffer: mvtf.get('003').buffer, z: 15, x: 5248, y: 11436}]; + const opts = { + radius: 800 // about the width of a z15 tile + } + vtquery(tiles, [-122.3384, 47.6635], opts, function(err, result) { + assert.ifError(err); + assert.notOk(result.features.length); + assert.end(); + }); +}); + +test('error: throws on invalid tile that is missing geometry type', assert => { + const tiles = [{buffer: mvtf.get('004').buffer, z: 15, x: 5248, y: 11436}]; + const opts = { + radius: 800 // about the width of a z15 tile + } + vtquery(tiles, [-122.3384, 47.6635], opts, function(err, result) { + assert.ok(err); + assert.equal(err.message, 'Missing geometry field in feature (spec 4.2)', 'expected error message'); + assert.end(); + }); +}); From 5e2e943ecab453524bd3614e019b474d49080302 Mon Sep 17 00:00:00 2001 From: mapsam Date: Wed, 22 Nov 2017 10:27:02 -0800 Subject: [PATCH 07/22] radius 0.0 tests, update README.md --- README.md | 74 ++++++++++++++++++++++++-------------------- src/vtquery.cpp | 2 +- test/vtquery.test.js | 24 +++++++++++++- 3 files changed, 64 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 8cdd9e18..69fa6c46 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,33 @@ Get the closest features from a longitude/latitude in a set of vector tile buffe The two major use cases for this library are: 1. To get a list of the features closest to a query point -2. Point in polygon checks +2. Point in polygon checks (`radius=0`) + +# API + + + +## vtquery + +**Parameters** + +- `tiles` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)>** an array of tile objects with `buffer`, `z`, `x`, and `y` values +- `LngLat` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)>** a query point of longitude and latitude to query, `[lng, lat]` +- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** + - `options.radius` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** the radius to query for features. If your radius is larger than + the extent of an individual tile, include multiple nearby buffers to collect a realstic list of features (optional, default `0`) + - `options.results` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** the number of results/features returned from the query. (optional, default `5`) + - `options.layers` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)>?** an array of layer string names to query from. Default is all layers. + - `options.geometry` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** only return features of a particular geometry type. Can be `point`, `linestring`, or `polygon`. + Defaults to all geometry types. + +**Examples** + +```javascript +const vtquery = require('@mapbox/vtquery'); + +vtquery(tiles, lnglat, options, callback); +``` # Response object @@ -55,29 +81,13 @@ Here's an example response } ``` -# API +## Point in polygon queries -## vtquery +To perform a "point in polygon" query, set your radius value to `0`. This will only return polygons that your query point is _within_. -**Parameters** +GOTCHA 1: Be aware of the number of results you are returning - there may be overlapping polygons in a tile, especially if you are querying multiple layers. If a query point exists within multiple polygons there is no way to sort them so they come back in the order they were queried. If there are _more_ results than your `numResults` value specifies, they will just be cut off once the query hits the maximum number of results. -- `tiles` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array).<[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)>** an array of tile objects with `buffer`, `z`, `x`, and `y` values -- `LngLat` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array).<[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)>** a query point of longitude and latitude to query, `[lng, lat]` -- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)=** - - `options.radius` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)=** the radius to query for features. If your radius is larger than - the extent of an individual tile, include multiple nearby buffers to collect a realstic list of features (optional, default `0`) - - `options.results` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)=** the number of results/features returned from the query. (optional, default `5`) - - `options.layers` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array).<[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)>=** an array of layer string names to query from. Default is all layers. - - `options.geometry` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)=** only return features of a particular geometry type. Can be `point`, `linestring`, or `polygon`. - Defaults to all geometry types. - -**Examples** - -```javascript -const vtquery = require('@mapbox/vtquery'); - -vtquery(tiles, lnglat, options, callback); -``` +GOTCHA 2: Any query point that exists _directly_ along an edge of a polygon will _not_ return. # Develop @@ -109,22 +119,18 @@ npm run docs Benchmarks can be run with the bench/vtquery.bench.js script to test vtquery against common real-world fixtures (provided by mvt-fixtures). When making changes in a pull request, please provide the benchmarks from the master branch and the HEAD of your current branch. You can control the `concurrency` and `iterations` of the benchmarks with the following command: -``` -node bench/vtquery.bench.js --iterations 1000 --concurrency 5 -``` + node bench/vtquery.bench.js --iterations 1000 --concurrency 5 And the output will show how many times the library was able to execute per second, per fixture: -``` -1: 9 tiles, chicago, radius 1000 ... 191 runs/s (5240ms) -2: 9 tiles, chicago, only points ... 3257 runs/s (307ms) -3: mbx streets no radius ... 4049 runs/s (247ms) -4: mbx streets 2000 radius ... 240 runs/s (4159ms) -5: mbx streets only points ... 4673 runs/s (214ms) -6: mbx streets only linestrings ... 286 runs/s (3499ms) -7: mbx streets only polys ... 2801 runs/s (357ms) -8: complex multipolygon ... 1253 runs/s (798ms) -``` + 1: 9 tiles, chicago, radius 1000 ... 191 runs/s (5240ms) + 2: 9 tiles, chicago, only points ... 3257 runs/s (307ms) + 3: mbx streets no radius ... 4049 runs/s (247ms) + 4: mbx streets 2000 radius ... 240 runs/s (4159ms) + 5: mbx streets only points ... 4673 runs/s (214ms) + 6: mbx streets only linestrings ... 286 runs/s (3499ms) + 7: mbx streets only polys ... 2801 runs/s (357ms) + 8: complex multipolygon ... 1253 runs/s (798ms) # Viz diff --git a/src/vtquery.cpp b/src/vtquery.cpp index 381f8ad4..e1d933c8 100644 --- a/src/vtquery.cpp +++ b/src/vtquery.cpp @@ -31,7 +31,7 @@ const char* getGeomTypeString(int enumVal) { struct ResultObject { mapbox::feature::property_map properties; - std::string layer_name; + std::string const& layer_name; mapbox::geometry::point coordinates; double distance; GeomType original_geometry_type; diff --git a/test/vtquery.test.js b/test/vtquery.test.js index 08d40293..2bc84fef 100644 --- a/test/vtquery.test.js +++ b/test/vtquery.test.js @@ -424,6 +424,29 @@ test('options - radius: all results within radius', assert => { }); }); +test('options - radius=0: only returns "point in polygon" results (on a building)', assert => { + const buffer = bufferSF; + const ll = [-122.4527, 37.7689]; // direct hit on a building + vtquery([{buffer: buffer, z: 15, x: 5238, y: 12666}], ll, { radius: 0, layers: ['building'] }, function(err, result) { + assert.ifError(err); + assert.equal(result.features.length, 1, 'only one building returned'); + assert.deepEqual(result.features[0].properties.tilequery, { distance: 0.0, layer: 'building', geometry: 'polygon' }, 'expected tilequery info'); + assert.end(); + }); +}); + +test('options - radius=0: returns only radius 0.0 results', assert => { + const buffer = bufferSF; + const ll = [-122.4527, 37.7689]; // direct hit on a building + vtquery([{buffer: buffer, z: 15, x: 5238, y: 12666}], ll, { radius: 0, numResults: 100 }, function(err, result) { + assert.ifError(err); + result.features.forEach(function(feature) { + assert.ok(feature.properties.tilequery.distance == 0.0, 'radius 0.0'); + }); + assert.end(); + }); +}); + test('options - numResults: successfully limits results', assert => { const buffer = bufferSF; const ll = [-122.4477, 37.7665]; // direct hit @@ -440,7 +463,6 @@ test('options - layers: successfully returns only requested layers', assert => { vtquery([{buffer: buffer, z: 15, x: 5238, y: 12666}], ll, {radius: 2000, layers: ['poi_label']}, function(err, result) { assert.ifError(err); result.features.forEach(function(feature) { - console.log(feature); assert.equal(feature.properties.tilequery.layer, 'poi_label', 'proper layer'); }); assert.end(); From b37d0bd04af4ad564949e57c8fbd1ef8b512990e Mon Sep 17 00:00:00 2001 From: mapsam Date: Wed, 22 Nov 2017 10:31:53 -0800 Subject: [PATCH 08/22] tidy and format --- src/vtquery.cpp | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/vtquery.cpp b/src/vtquery.cpp index e1d933c8..153bdab8 100644 --- a/src/vtquery.cpp +++ b/src/vtquery.cpp @@ -5,13 +5,13 @@ #include #include #include -#include #include #include #include #include #include #include +#include #include #include #include @@ -129,36 +129,35 @@ struct QueryData { }; struct property_value_visitor { - v8::Local & properties_obj; + v8::Local& properties_obj; std::string const& key; template - void operator() (T) {} + void operator()(T) {} - void operator() (bool v) { + void operator()(bool v) { properties_obj->Set(Nan::New(key).ToLocalChecked(), Nan::New(v)); } - void operator() (uint64_t v) { + void operator()(uint64_t v) { properties_obj->Set(Nan::New(key).ToLocalChecked(), Nan::New(v)); } - void operator() (int64_t v) { + void operator()(int64_t v) { properties_obj->Set(Nan::New(key).ToLocalChecked(), Nan::New(v)); } - void operator() (double v) { + void operator()(double v) { properties_obj->Set(Nan::New(key).ToLocalChecked(), Nan::New(v)); } - void operator() (std::string const& v) { + void operator()(std::string const& v) { properties_obj->Set(Nan::New(key).ToLocalChecked(), Nan::New(v).ToLocalChecked()); } }; void set_property(mapbox::feature::property_map::value_type const& property, - v8::Local & properties_obj) { + v8::Local& properties_obj) { mapbox::util::apply_visitor(property_value_visitor{properties_obj, property.first}, property.second); } -struct CompareDistance -{ +struct CompareDistance { bool operator()(const ResultObject* r1, const ResultObject* r2) const { return r1->distance < r2->distance; } @@ -354,7 +353,7 @@ struct Worker : Nan::AsyncWorker { feature_obj->Set(Nan::New("properties").ToLocalChecked(), properties_obj); // add feature to features array - features_array->Set(static_cast(results_queue_.size()-1), feature_obj); + features_array->Set(static_cast(results_queue_.size() - 1), feature_obj); results_queue_.pop(); // remove item from results queue } From 727cdacb2e926ef17ccc70110c5d9e1792cd3aaf Mon Sep 17 00:00:00 2001 From: mapsam Date: Wed, 22 Nov 2017 14:35:07 -0800 Subject: [PATCH 09/22] const reference fix --- src/vtquery.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vtquery.cpp b/src/vtquery.cpp index 153bdab8..e474d1b7 100644 --- a/src/vtquery.cpp +++ b/src/vtquery.cpp @@ -31,7 +31,7 @@ const char* getGeomTypeString(int enumVal) { struct ResultObject { mapbox::feature::property_map properties; - std::string const& layer_name; + std::string layer_name; mapbox::geometry::point coordinates; double distance; GeomType original_geometry_type; @@ -39,7 +39,7 @@ struct ResultObject { // custom constructor ResultObject( mapbox::feature::property_map&& props_map, // specifies an r-value completely, whatever had the memory beforehand, this now controls it - std::string name, + std::string const& name, mapbox::geometry::point&& p, double distance0, GeomType geom_type) From 105f1d61c8c102563cdc14292d7923d8f9d42c7a Mon Sep 17 00:00:00 2001 From: mapsam Date: Mon, 27 Nov 2017 10:55:07 -0800 Subject: [PATCH 10/22] use spatial algorithms dev release --- scripts/install_deps.sh | 2 +- scripts/setup.sh | 2 +- src/util.hpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/install_deps.sh b/scripts/install_deps.sh index b9f12daf..0053d247 100755 --- a/scripts/install_deps.sh +++ b/scripts/install_deps.sh @@ -16,7 +16,7 @@ install geometry 96d3505 install variant 1.1.4 install vtzero 1c38ce7 install protozero 1.6.0 -install spatial-algorithms 2904283 +install spatial-algorithms cdda174 install boost 1.65.1 install cheap-ruler 2.5.3 install vector-tile 0390175 diff --git a/scripts/setup.sh b/scripts/setup.sh index 83a46f4f..e2a19e80 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -3,7 +3,7 @@ set -eu set -o pipefail -export MASON_RELEASE="${MASON_RELEASE:-v0.16.0}" +export MASON_RELEASE="${MASON_RELEASE:-f4c9c09}" export MASON_LLVM_RELEASE="${MASON_LLVM_RELEASE:-5.0.0}" PLATFORM=$(uname | tr A-Z a-z) diff --git a/src/util.hpp b/src/util.hpp index 54bbb8b4..4eef76de 100644 --- a/src/util.hpp +++ b/src/util.hpp @@ -83,7 +83,7 @@ mapbox::geometry::point convert_vt_to_ll(std::uint32_t extent, std::uint32_t z, std::uint32_t x, std::uint32_t y, - mapbox::geometry::algorithms::closest_point_info cp_info) { + mapbox::geometry::algorithms::closest_point_info cp_info) { double z2 = static_cast(static_cast(1) << z); double ex = static_cast(extent); double size = ex * z2; From 144339c7a71528d75fd5e1ce2d5b379489e1ab86 Mon Sep 17 00:00:00 2001 From: mapsam Date: Mon, 27 Nov 2017 12:18:10 -0800 Subject: [PATCH 11/22] reverse conditionals --- src/vtquery.cpp | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/src/vtquery.cpp b/src/vtquery.cpp index e474d1b7..d562aaa7 100644 --- a/src/vtquery.cpp +++ b/src/vtquery.cpp @@ -261,27 +261,24 @@ struct Worker : Nan::AsyncWorker { // implement closest point algorithm on query geometry and the query point auto const cp_info = mapbox::geometry::algorithms::closest_point(query_geometry, query_point); - // if the distance is exactly 0.0, add it to the list downright - // otherwise check if the radius is greater than 0.0 and less than the radius - if (cp_info.distance == 0.0) { - // we'll only add the first "n" results depending on the num_results - there's no way to sort otherwise - if (results_queue_.size() < data.num_results) { - auto qp = mapbox::geometry::point{data.longitude, data.latitude}; // original query lng/lat - auto props = mapbox::vector_tile::extract_properties(feature); - results_.emplace_back(std::move(props), - layer_name, - std::move(qp), - 0.0, - original_geometry_type); - results_queue_.emplace(&results_.back()); - } - } else { + // check if cp_info.distance isn't less than zero, if so, this is an error and we can move on + if (cp_info.distance < 0.0) { + continue; + } + + // if the distance is greater than 0.0, get the distance from the query point + // otherwise if the distance is 0.0, add it to the queue + if (cp_info.distance > 0.0) { // convert x/y into lng/lat point auto feature_lnglat = utils::convert_vt_to_ll(extent, tile_obj.z, tile_obj.x, tile_obj.y, cp_info); auto meters = utils::distance_in_meters(query_lnglat, feature_lnglat); // if the distance is within the threshold, save it - if (meters <= data.radius && meters >= 0.0) { + if (meters <= data.radius) { + + // if the queue size is still smaller than number of results, just add the hit + // if the queue size is at its max, compare the top() result and only add if this new + // hit is smaller than the top result if (results_queue_.size() < data.num_results) { auto props = mapbox::vector_tile::extract_properties(feature); results_.emplace_back(std::move(props), @@ -301,6 +298,18 @@ struct Worker : Nan::AsyncWorker { results_queue_.emplace(&results_.back()); } } + } else { + // we'll only add the first "n" results depending on the num_results - there's no way to sort otherwise + if (results_queue_.size() < data.num_results) { + auto qp = mapbox::geometry::point{data.longitude, data.latitude}; // original query lng/lat + auto props = mapbox::vector_tile::extract_properties(feature); + results_.emplace_back(std::move(props), + layer_name, + std::move(qp), + 0.0, + original_geometry_type); + results_queue_.emplace(&results_.back()); + } } } // end tile.layer.feature loop } // end tile.layer loop From 28ba72acc0a5d65dd2a8d711c735e69fce63fd2f Mon Sep 17 00:00:00 2001 From: mapsam Date: Mon, 27 Nov 2017 12:47:17 -0800 Subject: [PATCH 12/22] quick format fix --- src/vtquery.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vtquery.cpp b/src/vtquery.cpp index d562aaa7..a95d8764 100644 --- a/src/vtquery.cpp +++ b/src/vtquery.cpp @@ -263,7 +263,7 @@ struct Worker : Nan::AsyncWorker { // check if cp_info.distance isn't less than zero, if so, this is an error and we can move on if (cp_info.distance < 0.0) { - continue; + continue; } // if the distance is greater than 0.0, get the distance from the query point From 5479c9084d7b5620014850906cf078d72f09ed6a Mon Sep 17 00:00:00 2001 From: mapsam Date: Mon, 27 Nov 2017 13:29:42 -0800 Subject: [PATCH 13/22] allow 0.0 to be added always --- src/vtquery.cpp | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/vtquery.cpp b/src/vtquery.cpp index a95d8764..e83c5927 100644 --- a/src/vtquery.cpp +++ b/src/vtquery.cpp @@ -299,17 +299,21 @@ struct Worker : Nan::AsyncWorker { } } } else { - // we'll only add the first "n" results depending on the num_results - there's no way to sort otherwise - if (results_queue_.size() < data.num_results) { - auto qp = mapbox::geometry::point{data.longitude, data.latitude}; // original query lng/lat - auto props = mapbox::vector_tile::extract_properties(feature); - results_.emplace_back(std::move(props), - layer_name, - std::move(qp), - 0.0, - original_geometry_type); - results_queue_.emplace(&results_.back()); + auto qp = mapbox::geometry::point{data.longitude, data.latitude}; // original query lng/lat + auto props = mapbox::vector_tile::extract_properties(feature); + + // if our results list is already full, remove the top() element and add the new one - this way the + // we'll always be removing the largest element by distance and replacing with a 0.0 result + if (results_queue_.size() == data.num_results) { + results_queue_.pop(); } + + results_.emplace_back(std::move(props), + layer_name, + std::move(qp), + 0.0, + original_geometry_type); + results_queue_.emplace(&results_.back()); } } // end tile.layer.feature loop } // end tile.layer loop From 45f2afef6cf41aa9101e45b8da74ebbc16dad38f Mon Sep 17 00:00:00 2001 From: mapsam Date: Tue, 28 Nov 2017 12:18:47 -0800 Subject: [PATCH 14/22] update tests, springmeyer review edits --- src/vtquery.cpp | 6 ++-- test/fixtures/expected-order.json | 1 + test/vtquery.test.js | 47 +++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 test/fixtures/expected-order.json diff --git a/src/vtquery.cpp b/src/vtquery.cpp index e83c5927..ac755151 100644 --- a/src/vtquery.cpp +++ b/src/vtquery.cpp @@ -24,7 +24,7 @@ enum GeomType { point, polygon, all, unknown }; -static const char* GeomTypeStrings[] = {"point", "linestring", "polygon", "uknown", "unknown"}; +static const char* GeomTypeStrings[] = {"point", "linestring", "polygon", "unknown"}; const char* getGeomTypeString(int enumVal) { return GeomTypeStrings[enumVal]; } @@ -330,7 +330,7 @@ struct Worker : Nan::AsyncWorker { // number of results to loop through v8::Local results_object = Nan::New(); - v8::Local features_array = Nan::New(); + v8::Local features_array = Nan::New(results_queue_.size()); results_object->Set(Nan::New("type").ToLocalChecked(), Nan::New("FeatureCollection").ToLocalChecked()); // for each result object @@ -342,7 +342,7 @@ struct Worker : Nan::AsyncWorker { // create geometry object v8::Local geometry_obj = Nan::New(); geometry_obj->Set(Nan::New("type").ToLocalChecked(), Nan::New("Point").ToLocalChecked()); - v8::Local coordinates_array = Nan::New(); + v8::Local coordinates_array = Nan::New(2); coordinates_array->Set(0, Nan::New(feature->coordinates.x)); // latitude coordinates_array->Set(1, Nan::New(feature->coordinates.y)); // longitude geometry_obj->Set(Nan::New("coordinates").ToLocalChecked(), coordinates_array); diff --git a/test/fixtures/expected-order.json b/test/fixtures/expected-order.json new file mode 100644 index 00000000..cbd0c02a --- /dev/null +++ b/test/fixtures/expected-order.json @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[-87.7987,41.8451]},"properties":{"type":"school","class":"school","tilequery":{"distance":0,"geometry":"polygon","layer":"landuse"}}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-87.7987,41.8451]},"properties":{"type":"recreation_ground","class":"park","tilequery":{"distance":0,"geometry":"polygon","layer":"landuse"}}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-87.79833505548643,41.84510735740068]},"properties":{"name_fr":"Home Ave","name_zh-Hans":"Home Ave","name_es":"Home Ave","name_pt":"Home Ave","name_ar":"Home Ave","name":"Home Ave","name_zh":"Home Ave","name_de":"Home Ave","localrank":9,"name_ru":"Home Ave","len":4040,"name_en":"Home Ave","iso_3166_2":"US-IL","class":"street","tilequery":{"distance":30.32099616026162,"geometry":"linestring","layer":"road_label"}}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-87.79833505548643,41.84510735740068]},"properties":{"structure":"none","type":"residential","oneway":"false","class":"street","tilequery":{"distance":30.32099616026162,"geometry":"linestring","layer":"road"}}},{"type":"Feature","geometry":{"type":"Point","coordinates":[-87.79832482337952,41.84522846901311]},"properties":{"structure":"none","type":"service:alley","oneway":"false","class":"service","tilequery":{"distance":34.27147583628124,"geometry":"linestring","layer":"road"}}}]} diff --git a/test/vtquery.test.js b/test/vtquery.test.js index 2bc84fef..077ccb7d 100644 --- a/test/vtquery.test.js +++ b/test/vtquery.test.js @@ -424,6 +424,31 @@ test('options - radius: all results within radius', assert => { }); }); +test('options - radius: all results are in expected order', assert => { + const buffer = fs.readFileSync(path.resolve(__dirname+'/../node_modules/@mapbox/mvt-fixtures/real-world/chicago/13-2098-3045.mvt')); + const ll = [-87.7987, 41.8451]; + vtquery([{buffer: buffer, z: 13, x: 2098, y: 3045}], ll, { radius: 50 }, function(err, result) { + assert.ifError(err); + assert.deepEqual(result, JSON.parse(fs.readFileSync(__dirname + '/fixtures/expected-order.json')), 'expected results and order'); + assert.end(); + }); +}); + +test('results with same exact distance return in expected order', assert => { + // this query returns four results, three of which are the exact same distance and different types of features + const buffer = fs.readFileSync(path.resolve(__dirname+'/../node_modules/@mapbox/mvt-fixtures/real-world/chicago/13-2098-3045.mvt')); + const ll = [-87.7964, 41.8675]; + vtquery([{buffer: buffer, z: 13, x: 2098, y: 3045}], ll, { radius: 10, layers: ['road'] }, function(err, result) { + assert.equal(result.features[1].properties.type, 'pedestrian', 'is expected type'); + assert.equal(result.features[1].properties.tilequery.distance, 9.436356889343624, 'is the proper distance'); + assert.equal(result.features[2].properties.type, 'service:driveway', 'is expected type'); + assert.equal(result.features[2].properties.tilequery.distance, 9.436356889343624, 'is the proper distance'); + assert.equal(result.features[3].properties.type, 'turning_circle', 'is expected type'); + assert.equal(result.features[3].properties.tilequery.distance, 9.436356889343624, 'is the proper distance'); + assert.end(); + }); +}); + test('options - radius=0: only returns "point in polygon" results (on a building)', assert => { const buffer = bufferSF; const ll = [-122.4527, 37.7689]; // direct hit on a building @@ -469,6 +494,28 @@ test('options - layers: successfully returns only requested layers', assert => { }); }); +test('options - layers: returns zero results for a layer that does not exist - does not error', assert => { + const buffer = bufferSF; + const ll = [-122.4477, 37.7665]; // direct hit + vtquery([{buffer: buffer, z: 15, x: 5238, y: 12666}], ll, {radius: 2000, layers: ['i_am_not_real']}, function(err, result) { + assert.ifError(err); + assert.equal(result.features.length, 0, 'no features'); + assert.end(); + }); +}); + +test('options - radius: all results within radius', assert => { + const buffer = bufferSF; + const ll = [-122.4477, 37.7665]; // direct hit + vtquery([{buffer: buffer, z: 15, x: 5238, y: 12666}], ll, { numResults: 100, radius: 1000 }, function(err, result) { + assert.ifError(err); + result.features.forEach(function(feature) { + assert.ok(feature.properties.tilequery.distance <= 1000, 'less than radius'); + }); + assert.end(); + }); +}); + test('options - geometry: successfully returns only points', assert => { const buffer = bufferSF; const ll = [-122.4477, 37.7665]; // direct hit From a47854788c023606714019f6f3d51e6d5325e7ab Mon Sep 17 00:00:00 2001 From: mapsam Date: Tue, 28 Nov 2017 13:32:48 -0800 Subject: [PATCH 15/22] assert on equality in feature order, but not properties order --- test/vtquery.test.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/test/vtquery.test.js b/test/vtquery.test.js index 077ccb7d..aa086f83 100644 --- a/test/vtquery.test.js +++ b/test/vtquery.test.js @@ -1,3 +1,5 @@ +'use strict'; + const test = require('tape'); const path = require('path'); const vtquery = require('../lib/index.js'); @@ -425,11 +427,20 @@ test('options - radius: all results within radius', assert => { }); test('options - radius: all results are in expected order', assert => { + const expected = JSON.parse(fs.readFileSync(__dirname + '/fixtures/expected-order.json')); const buffer = fs.readFileSync(path.resolve(__dirname+'/../node_modules/@mapbox/mvt-fixtures/real-world/chicago/13-2098-3045.mvt')); const ll = [-87.7987, 41.8451]; vtquery([{buffer: buffer, z: 13, x: 2098, y: 3045}], ll, { radius: 50 }, function(err, result) { assert.ifError(err); - assert.deepEqual(result, JSON.parse(fs.readFileSync(__dirname + '/fixtures/expected-order.json')), 'expected results and order'); + result.features.forEach(function(feature, i) { + let e = expected.features[i].properties; + assert.equal(e.tilequery.distance, feature.properties.tilequery.distance, 'same distance'); + assert.equal(e.tilequery.layer, feature.properties.tilequery.layer, 'same layer'); + assert.equal(e.tilequery.geometry, feature.properties.tilequery.geometry, 'same geometry'); + if (feature.properties.type) { + assert.equal(e.type, feature.properties.type, 'same type'); + } + }); assert.end(); }); }); From a0bb76d1bd2d3d2e4d3c5f701492293fa8496fd4 Mon Sep 17 00:00:00 2001 From: mapsam Date: Tue, 28 Nov 2017 16:08:31 -0800 Subject: [PATCH 16/22] checkClose function --- test/vtquery.test.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/vtquery.test.js b/test/vtquery.test.js index aa086f83..fec62c8f 100644 --- a/test/vtquery.test.js +++ b/test/vtquery.test.js @@ -9,6 +9,10 @@ const fs = require('fs'); const bufferSF = fs.readFileSync(path.resolve(__dirname+'/../node_modules/@mapbox/mvt-fixtures/real-world/sanfrancisco/15-5238-12666.mvt')); +function checkClose(a, b, epsilon) { + return 1*(a-b) < epsilon; +} + test('failure: fails without callback function', assert => { try { vtquery(); @@ -434,6 +438,7 @@ test('options - radius: all results are in expected order', assert => { assert.ifError(err); result.features.forEach(function(feature, i) { let e = expected.features[i].properties; + assert.ok(checkClose(e.tilequery.distance, feature.properties.tilequery.distance, 1e-6)); assert.equal(e.tilequery.distance, feature.properties.tilequery.distance, 'same distance'); assert.equal(e.tilequery.layer, feature.properties.tilequery.layer, 'same layer'); assert.equal(e.tilequery.geometry, feature.properties.tilequery.geometry, 'same geometry'); From 7b79a90307d2ee2a049f340ef69fed8349563a0b Mon Sep 17 00:00:00 2001 From: mapsam Date: Tue, 28 Nov 2017 16:16:17 -0800 Subject: [PATCH 17/22] more checkClose --- test/vtquery.test.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test/vtquery.test.js b/test/vtquery.test.js index fec62c8f..f6736d50 100644 --- a/test/vtquery.test.js +++ b/test/vtquery.test.js @@ -438,8 +438,7 @@ test('options - radius: all results are in expected order', assert => { assert.ifError(err); result.features.forEach(function(feature, i) { let e = expected.features[i].properties; - assert.ok(checkClose(e.tilequery.distance, feature.properties.tilequery.distance, 1e-6)); - assert.equal(e.tilequery.distance, feature.properties.tilequery.distance, 'same distance'); + assert.ok(checkClose(e.tilequery.distance, feature.properties.tilequery.distance, 1e-6), 'expected distance'); assert.equal(e.tilequery.layer, feature.properties.tilequery.layer, 'same layer'); assert.equal(e.tilequery.geometry, feature.properties.tilequery.geometry, 'same geometry'); if (feature.properties.type) { @@ -456,11 +455,11 @@ test('results with same exact distance return in expected order', assert => { const ll = [-87.7964, 41.8675]; vtquery([{buffer: buffer, z: 13, x: 2098, y: 3045}], ll, { radius: 10, layers: ['road'] }, function(err, result) { assert.equal(result.features[1].properties.type, 'pedestrian', 'is expected type'); - assert.equal(result.features[1].properties.tilequery.distance, 9.436356889343624, 'is the proper distance'); + assert.ok(checkClose(result.features[1].properties.tilequery.distance, 9.436356889343624, 1e-6), 'is the proper distance'); assert.equal(result.features[2].properties.type, 'service:driveway', 'is expected type'); - assert.equal(result.features[2].properties.tilequery.distance, 9.436356889343624, 'is the proper distance'); + assert.ok(checkClose(result.features[2].properties.tilequery.distance, 9.436356889343624, 1e-6), 'is the proper distance'); assert.equal(result.features[3].properties.type, 'turning_circle', 'is expected type'); - assert.equal(result.features[3].properties.tilequery.distance, 9.436356889343624, 'is the proper distance'); + assert.ok(checkClose(result.features[3].properties.tilequery.distance, 9.436356889343624, 1e-6), 'is the proper distance'); assert.end(); }); }); From b080f21c0e539226ff1fbf10143580e3ee218ef9 Mon Sep 17 00:00:00 2001 From: mapsam Date: Wed, 29 Nov 2017 17:39:10 -0800 Subject: [PATCH 18/22] add dockerfile --- .dockerignore | 7 +++++++ Dockerfile | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..1b9005a9 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +mason_packages +node_modules +build +.toolchain +.mason +.git +local.env diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..b481e1ff --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +FROM ubuntu:16.04 + +# docker build -t vtquery . +# docker run -it vtquery + +RUN apt-get update -y && \ + apt-get install -y build-essential bash curl git-core ca-certificates software-properties-common vim python-software-properties --no-install-recommends + +RUN add-apt-repository -y ppa:ubuntu-toolchain-r/test && \ + apt-get update -y + +RUN curl https://nodejs.org/dist/v4.8.4/node-v4.8.4-linux-x64.tar.gz | tar zxC /usr/local --strip-components=1 + +ENV PATH=/usr/local/src/mason_packages/.link/bin:${PATH} CXX=clang++ CXXFLAGS="-fsanitize=address,undefined -fno-sanitize-recover=all" +WORKDIR /usr/local/src +COPY ./ ./ + +RUN ./scripts/setup.sh --config local.env +RUN /bin/bash -c "source local.env && make" From aa0da2320b5064fbf1050fe8e160aebb9fe264f2 Mon Sep 17 00:00:00 2001 From: mapsam Date: Wed, 29 Nov 2017 21:48:21 -0800 Subject: [PATCH 19/22] remove sanitizers --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index b481e1ff..4f039d60 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ RUN add-apt-repository -y ppa:ubuntu-toolchain-r/test && \ RUN curl https://nodejs.org/dist/v4.8.4/node-v4.8.4-linux-x64.tar.gz | tar zxC /usr/local --strip-components=1 -ENV PATH=/usr/local/src/mason_packages/.link/bin:${PATH} CXX=clang++ CXXFLAGS="-fsanitize=address,undefined -fno-sanitize-recover=all" +ENV PATH=/usr/local/src/mason_packages/.link/bin:${PATH} CXX=clang++ WORKDIR /usr/local/src COPY ./ ./ From f5d447a59ff911d603ab7f8c03cef17d71ad9224 Mon Sep 17 00:00:00 2001 From: mapsam Date: Thu, 30 Nov 2017 11:54:48 -0800 Subject: [PATCH 20/22] remove test --- .dockerignore | 1 + test/vtquery.test.js | 15 --------------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/.dockerignore b/.dockerignore index 1b9005a9..c43e2791 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,3 +5,4 @@ build .mason .git local.env +lib/binding diff --git a/test/vtquery.test.js b/test/vtquery.test.js index f6736d50..dffb6a51 100644 --- a/test/vtquery.test.js +++ b/test/vtquery.test.js @@ -449,21 +449,6 @@ test('options - radius: all results are in expected order', assert => { }); }); -test('results with same exact distance return in expected order', assert => { - // this query returns four results, three of which are the exact same distance and different types of features - const buffer = fs.readFileSync(path.resolve(__dirname+'/../node_modules/@mapbox/mvt-fixtures/real-world/chicago/13-2098-3045.mvt')); - const ll = [-87.7964, 41.8675]; - vtquery([{buffer: buffer, z: 13, x: 2098, y: 3045}], ll, { radius: 10, layers: ['road'] }, function(err, result) { - assert.equal(result.features[1].properties.type, 'pedestrian', 'is expected type'); - assert.ok(checkClose(result.features[1].properties.tilequery.distance, 9.436356889343624, 1e-6), 'is the proper distance'); - assert.equal(result.features[2].properties.type, 'service:driveway', 'is expected type'); - assert.ok(checkClose(result.features[2].properties.tilequery.distance, 9.436356889343624, 1e-6), 'is the proper distance'); - assert.equal(result.features[3].properties.type, 'turning_circle', 'is expected type'); - assert.ok(checkClose(result.features[3].properties.tilequery.distance, 9.436356889343624, 1e-6), 'is the proper distance'); - assert.end(); - }); -}); - test('options - radius=0: only returns "point in polygon" results (on a building)', assert => { const buffer = bufferSF; const ll = [-122.4527, 37.7689]; // direct hit on a building From aa3feacfd66368fe318f78d2faa75fbd563e2ab9 Mon Sep 17 00:00:00 2001 From: mapsam Date: Thu, 30 Nov 2017 12:39:31 -0800 Subject: [PATCH 21/22] move Dockerfile into tmp folder --- README.md | 17 +++++++++++++++++ .dockerignore => tmp/.dockerignore | 0 Dockerfile => tmp/Dockerfile | 0 3 files changed, 17 insertions(+) rename .dockerignore => tmp/.dockerignore (100%) rename Dockerfile => tmp/Dockerfile (100%) diff --git a/README.md b/README.md index 69fa6c46..5a5623ee 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,23 @@ npm install -g documentation npm run docs ``` +To install and test on a linux instance, you can use the Dockerfile provided. + +```shell +# build the image +docker build -t vtquery . + +# run it - this will put you inside the image +docker run -it vtquery + +# run tests +make test + +# edit files - helpful for debugging +vim src/vtquery.cpp +``` + + # Benchmarks Benchmarks can be run with the bench/vtquery.bench.js script to test vtquery against common real-world fixtures (provided by mvt-fixtures). When making changes in a pull request, please provide the benchmarks from the master branch and the HEAD of your current branch. You can control the `concurrency` and `iterations` of the benchmarks with the following command: diff --git a/.dockerignore b/tmp/.dockerignore similarity index 100% rename from .dockerignore rename to tmp/.dockerignore diff --git a/Dockerfile b/tmp/Dockerfile similarity index 100% rename from Dockerfile rename to tmp/Dockerfile From c793d31e717ab1a47f640950bd43b638c88afd29 Mon Sep 17 00:00:00 2001 From: mapsam Date: Thu, 30 Nov 2017 12:47:25 -0800 Subject: [PATCH 22/22] add LSAN flags --- tmp/.dockerignore => .dockerignore | 0 .travis.yml | 5 +++-- tmp/Dockerfile => Dockerfile | 0 3 files changed, 3 insertions(+), 2 deletions(-) rename tmp/.dockerignore => .dockerignore (100%) rename tmp/Dockerfile => Dockerfile (100%) diff --git a/tmp/.dockerignore b/.dockerignore similarity index 100% rename from tmp/.dockerignore rename to .dockerignore diff --git a/.travis.yml b/.travis.yml index 59bf2e92..bd0167a9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,7 +35,7 @@ before_script: # For reference, the default travis behavior which we override comes from https://github.com/travis-ci/travis-build/blob/e5a45cbf49e0d9e27398e76e5f25dd7706feb6aa/lib/travis/build/script/node_js.rb#L62-L69. script: [] -# the matrix allows you to specify different operating systems and environments to +# the matrix allows you to specify different operating systems and environments to # run your tests and build binaries matrix: include: @@ -83,6 +83,7 @@ matrix: before_script: - export LD_PRELOAD=${MASON_LLVM_RT_PRELOAD} - export ASAN_OPTIONS=fast_unwind_on_malloc=0:${ASAN_OPTIONS} + - export LSAN_OPTIONS=verbosity=1:log_threads=1 - npm test - unset LD_PRELOAD # after successful tests, publish binaries if specified in commit message @@ -142,4 +143,4 @@ matrix: # and get in the habit of running these locally before committing) - make tidy # Overrides `script`, no need to run tests - before_script: \ No newline at end of file + before_script: diff --git a/tmp/Dockerfile b/Dockerfile similarity index 100% rename from tmp/Dockerfile rename to Dockerfile