From 947971df10e14228897fb64f587b62139ea24684 Mon Sep 17 00:00:00 2001 From: Ryan Cook Date: Wed, 4 Sep 2019 14:53:47 -0400 Subject: [PATCH 1/2] Feature - Basic Filtering [publish binary] * Implemented a basic optional filtering mechanism into VTQuery * This allows VTQuery to ignore features that don't match the filters * Enabled filtering for features that pass 'all' or 'any' of the filters. * Remove Node v4/v6 targets * Update to mvt-features to version 3.6.0 --- .travis.yml | 32 +----- package-lock.json | 50 ++++----- package.json | 4 +- src/vtquery.cpp | 248 ++++++++++++++++++++++++++++++++++++++++++- test/vtquery.test.js | 199 +++++++++++++++++++++++++++++++++- 5 files changed, 475 insertions(+), 58 deletions(-) diff --git a/.travis.yml b/.travis.yml index 22905353..40e8ebbe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,22 +39,6 @@ script: # run your tests and build binaries matrix: include: - # linux publishable node v4/release - - os: linux - env: BUILDTYPE=release - node_js: 4 - # linux publishable node v4/debug - - os: linux - env: BUILDTYPE=debug - node_js: 4 - # linux publishable node v6 - - os: linux - env: BUILDTYPE=release - node_js: 6 - # linux publishable node v6/debug - - os: linux - env: BUILDTYPE=debug - node_js: 6 # linux publishable node v8 - os: linux env: BUILDTYPE=release @@ -71,27 +55,17 @@ matrix: - os: linux env: BUILDTYPE=debug node_js: 10 - # osx publishable node v4 - - os: osx - osx_image: xcode8.2 - env: BUILDTYPE=release - node_js: 4 - # osx publishable node v6 - - os: osx - osx_image: xcode8.2 - env: BUILDTYPE=release - node_js: 6 # osx publishable node v8 - os: osx - osx_image: xcode8.2 + osx_image: xcode8.3 env: BUILDTYPE=release node_js: 8 # osx publishable node v10 - os: osx - osx_image: xcode8.2 + osx_image: xcode8.3 env: BUILDTYPE=release node_js: 10 - # Sanitizer build node v4/Debug + # Sanitizer build node v10/Debug - os: linux env: BUILDTYPE=debug TOOLSET=asan sudo: required # workaround https://github.com/mapbox/node-cpp-skel/issues/93 diff --git a/package-lock.json b/package-lock.json index 8950b2c5..3ec01051 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,20 +1,20 @@ { "name": "@mapbox/vtquery", - "version": "0.3.0", + "version": "0.3.1-basic-filter", "lockfileVersion": 1, "requires": true, "dependencies": { "@mapbox/mvt-fixtures": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@mapbox/mvt-fixtures/-/mvt-fixtures-3.4.0.tgz", - "integrity": "sha512-NBZ4vQuY2p67cKa21CjtZuhMlHX2gu8RG4EoCglFapQtQJXauV2lxsEFq5hzIgFI8gEo/lIS0ewMQHNwnLhwfw==", + "version": "3.6.0-dev1", + "resolved": "https://registry.npmjs.org/@mapbox/mvt-fixtures/-/mvt-fixtures-3.6.0-dev1.tgz", + "integrity": "sha512-zr2ysE0ge1zbKo2niIrs6m331BEdCRhptXAj17MBnk29UFW7ppkyGRKoVk+3IgkPrtWbjUNOvuQFNnJBhVckqQ==", "dev": true, "requires": { "@mapbox/sphericalmercator": "^1.0.5", "@mapbox/vector-tile": "^1.3.0", "d3-queue": "^3.0.7", "pbf": "^3.0.5", - "protocol-buffers-schema": "^3.3.1" + "protocol-buffers-schema": "^3.3.2" } }, "@mapbox/point-geometry": { @@ -394,9 +394,9 @@ "dev": true }, "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", "dev": true }, "minimatch": { @@ -576,13 +576,21 @@ "dev": true }, "pbf": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.1.0.tgz", - "integrity": "sha512-/hYJmIsTmh7fMkHAWWXJ5b8IKLWdjdlAFb3IHkRBn1XUhIYBChVGfVwmHEAV3UfXTxsP/AKfYTXTS/dCPxJd5w==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.0.tgz", + "integrity": "sha512-98Eh7rsJNJF/Im6XYMLaOW3cLnNyedlOd6hu3iWMD5I7FZGgpw8yN3vQBrmLbLodu7G784Irb9Qsv2yFrxSAGw==", "dev": true, "requires": { - "ieee754": "^1.1.6", - "resolve-protobuf-schema": "^2.0.0" + "ieee754": "^1.1.12", + "resolve-protobuf-schema": "^2.1.0" + }, + "dependencies": { + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "dev": true + } } }, "process-nextick-args": { @@ -643,20 +651,12 @@ } }, "resolve-protobuf-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.0.0.tgz", - "integrity": "sha1-5nsGKmfwLRG9aIbnDv2niEB+D7Q=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", + "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", "dev": true, "requires": { - "protocol-buffers-schema": "^2.0.2" - }, - "dependencies": { - "protocol-buffers-schema": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-2.2.0.tgz", - "integrity": "sha1-0pxs1z+2VZePtpiWkRgNuEQRn2E=", - "dev": true - } + "protocol-buffers-schema": "^3.3.1" } }, "resumer": { diff --git a/package.json b/package.json index 638f7696..19394a93 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@mapbox/vtquery", - "version": "0.3.0", + "version": "0.3.1-basic-filter", "description": "Get features from Mapbox Vector Tiles from a lng/lat query point", "url": "http://github.com/mapbox/vtquery", "main": "./lib/index.js", @@ -20,7 +20,7 @@ "node-pre-gyp": "~0.10.2" }, "devDependencies": { - "@mapbox/mvt-fixtures": "^3.2.0", + "@mapbox/mvt-fixtures": "3.6.0", "aws-sdk": "^2.163.0", "d3-queue": "^3.0.7", "minimist": "^1.2.0", diff --git a/src/vtquery.cpp b/src/vtquery.cpp index 7e42f9fa..893cbdb1 100644 --- a/src/vtquery.cpp +++ b/src/vtquery.cpp @@ -97,6 +97,44 @@ struct TileObject { Nan::Persistent buffer_ref; }; +using value_type = boost::variant; + +enum BasicFilterType { + ne, + eq, + lt, + lte, + gt, + gte +}; + +struct basic_filter_struct { + explicit basic_filter_struct() + : key(""), + type(eq), + value(false) { + } + + std::string key; + BasicFilterType type; + value_type value; +}; + +enum BasicMetaFilterType { + filter_all, + filter_any +}; + +struct meta_filter_struct { + explicit meta_filter_struct() + : type(filter_all), + filters() { + } + + BasicMetaFilterType type; + std::vector filters; +}; + /// the baton of data to be passed from the v8 thread into the cpp threadpool struct QueryData { explicit QueryData(std::uint32_t num_tiles) @@ -107,7 +145,8 @@ struct QueryData { radius(0.0), num_results(5), dedupe(true), - geometry_filter_type(GeomType::all) { + geometry_filter_type(GeomType::all), + basic_filter() { tiles.reserve(num_tiles); } @@ -128,6 +167,7 @@ struct QueryData { std::uint32_t num_results; bool dedupe; GeomType geometry_filter_type; + meta_filter_struct basic_filter; }; /// convert properties to v8 types @@ -219,6 +259,100 @@ std::vector get_properties_vector(vtzero::feature& f) { return v; } +double convert_to_double(value_type value) { + // float + if (value.which() == 0) { + return double(boost::get(value)); + } + // double + if (value.which() == 1) { + return boost::get(value); + } + // int64_t + if (value.which() == 2) { + return double(boost::get(value)); + } + // uint64_t + if (value.which() == 3) { + return double(boost::get(value)); + } + return 0.0f; +} + +/// Evaluates a single filter on a feature - Returns true if it passes filter +bool single_filter_feature(basic_filter_struct filter, value_type feature_value) { + double epsilon = 0.001; + if (feature_value.which() <= 3 && filter.value.which() <= 3) { // Numeric Types + double parameter_double = convert_to_double(feature_value); + double filter_double = convert_to_double(filter.value); + if ((filter.type == eq) && (std::abs(parameter_double - filter_double) < epsilon)) { + return true; + } else if ((filter.type == ne) && (std::abs(parameter_double - filter_double) >= epsilon)) { + return true; + } else if ((filter.type == gte) && (parameter_double >= filter_double)) { + return true; + } else if ((filter.type == gt) && (parameter_double > filter_double)) { + return true; + } else if ((filter.type == lte) && (parameter_double <= filter_double)) { + return true; + } else if ((filter.type == lt) && (parameter_double < filter_double)) { + return true; + } + } else if (feature_value.which() == 4 && filter.value.which() == 4) { // Boolean Types + bool feature_bool = boost::get(feature_value); + bool filter_bool = boost::get(filter.value); + if ((filter.type == eq) && (feature_bool == filter_bool)) { + return true; + } else if ((filter.type == ne) && (feature_bool != filter_bool)) { + return true; + } + } + return false; +} + +/// apply filters to a feature - Returns true if feature matches all features +bool filter_feature_all(vtzero::feature& feature, std::vector filters) { + using key_type = std::string; + using map_type = std::map; + auto features_property_map = vtzero::create_properties_map(feature); + for (auto filter : filters) { + auto it = features_property_map.find(filter.key); + if (it != features_property_map.end()) { + value_type feature_value = it->second; + if (!single_filter_feature(filter, feature_value)) { + return false; + } + } + } + return true; +} + +/// apply filters to a feature - Returns true if feature matches any features +bool filter_feature_any(vtzero::feature& feature, std::vector filters) { + using key_type = std::string; + using map_type = std::map; + auto features_property_map = vtzero::create_properties_map(feature); + for (auto filter : filters) { + auto it = features_property_map.find(filter.key); + if (it != features_property_map.end()) { + value_type feature_value = it->second; + if (single_filter_feature(filter, feature_value)) { + return true; + } + } + } + return false; +} + +/// apply filters to a feature - Returns true if a feature matches the filters +bool filter_feature(vtzero::feature& feature, std::vector filters, BasicMetaFilterType filter_type) { + if (filter_type == filter_all) { + return filter_feature_all(feature, filters); + } else { + return filter_feature_any(feature, filters); + } +} + /// compare two features to determine if they are duplicates bool value_is_duplicate(ResultObject const& r, vtzero::feature const& candidate_feature, @@ -263,6 +397,9 @@ struct Worker : Nan::AsyncWorker { try { QueryData const& data = *query_data_; + std::vector filters = data.basic_filter.filters; + bool filter_enabled = filters.size() > 0; + // reserve the query results and fill with empty objects results_queue_.reserve(data.num_results); for (std::size_t i = 0; i < data.num_results; ++i) { @@ -308,6 +445,10 @@ struct Worker : Nan::AsyncWorker { while (auto feature = layer.next_feature()) { auto original_geometry_type = get_geometry_type(feature); + if (filter_enabled && !filter_feature(feature, filters, data.basic_filter.type)) { // If we have filters and the feature doesn't pass the filters, skip this feature + continue; + } + // check if this a geometry type we want to keep if (data.geometry_filter_type != GeomType::all && data.geometry_filter_type != original_geometry_type) { continue; @@ -664,6 +805,111 @@ NAN_METHOD(vtquery) { return utils::CallbackError("'geometry' must be 'point', 'linestring', or 'polygon'", callback); } } + + if (options->Has(Nan::New("basic-filters").ToLocalChecked())) { + v8::Local basic_filter_val = options->Get(Nan::New("basic-filters").ToLocalChecked()); + if (!basic_filter_val->IsArray()) { + return utils::CallbackError("'basic-filters' must be of the form [type, [filters]]", callback); + } + + v8::Local basic_filter_array = basic_filter_val.As(); + unsigned basic_filter_length = basic_filter_array->Length(); + + // gather filters from an array + if (basic_filter_length == 2) { + v8::Local basic_filter_type = basic_filter_array->Get(0); + if (!basic_filter_type->IsString()) { + return utils::CallbackError("'basic-filters' must be of the form [string, [filters]]", callback); + } + Nan::Utf8String basic_filter_type_utf8_value(basic_filter_type); + std::int32_t basic_filter_type_str_len = basic_filter_type_utf8_value.length(); + std::string basic_filter_type_str(*basic_filter_type_utf8_value, static_cast(basic_filter_type_str_len)); + if (basic_filter_type_str == "all") { + query_data->basic_filter.type = filter_all; + } else if (basic_filter_type_str == "any") { + query_data->basic_filter.type = filter_any; + } else { + return utils::CallbackError("'basic-filters[0] must be 'any' or 'all'", callback); + } + + v8::Local filters_array_val = basic_filter_array->Get(1); + if (!filters_array_val->IsArray()) { + return utils::CallbackError("'basic-filters' must be of the form [type, [filters]]", callback); + } + + v8::Local filters_array = filters_array_val.As(); + unsigned num_filters = filters_array->Length(); + for (unsigned j = 0; j < num_filters; ++j) { + basic_filter_struct filter; + v8::Local filter_val = filters_array->Get(j); + if (!filter_val->IsArray()) { + return utils::CallbackError("filters must be of the form [parameter, condition, value]", callback); + } + v8::Local filter_array = filter_val.As(); + unsigned filter_length = filter_array->Length(); + + if (filter_length != 3) { + return utils::CallbackError("filters must be of the form [parameter, condition, value]", callback); + } + + v8::Local filter_parameter_val = filter_array->Get(0); + if (!filter_parameter_val->IsString()) { + return utils::CallbackError("parameter filter option must be a string", callback); + } + + Nan::Utf8String filter_parameter_utf8_value(filter_parameter_val); + std::int32_t filter_parameter_len = filter_parameter_utf8_value.length(); + if (filter_parameter_len <= 0) { + return utils::CallbackError("parameter filter value must be a non-empty string", callback); + } + + std::string filter_parameter(*filter_parameter_utf8_value, static_cast(filter_parameter_len)); + filter.key.assign(filter_parameter); + + v8::Local filter_condition_val = filter_array->Get(1); + if (!filter_condition_val->IsString()) { + return utils::CallbackError("condition filter option must be a string", callback); + } + + Nan::Utf8String filter_condition_utf8_value(filter_condition_val); + std::int32_t filter_condition_len = filter_condition_utf8_value.length(); + if (filter_condition_len <= 0) { + return utils::CallbackError("condition filter value must be a non-empty string", callback); + } + + std::string filter_condition(*filter_condition_utf8_value, static_cast(filter_condition_len)); + + if (filter_condition == "=") { + filter.type = eq; + } else if (filter_condition == "!=") { + filter.type = ne; + } else if (filter_condition == "<") { + filter.type = lt; + } else if (filter_condition == "<=") { + filter.type = lte; + } else if (filter_condition == ">") { + filter.type = gt; + } else if (filter_condition == ">=") { + filter.type = gte; + } else { + return utils::CallbackError("condition filter value must be =, !=, <, <=, >, or >=", callback); + } + + v8::Local filter_value_val = filter_array->Get(2); + if (filter_value_val->IsNumber()) { + double filter_value_double = filter_value_val->NumberValue(); + filter.value = filter_value_double; + } else if (filter_value_val->IsBoolean()) { + filter.value = filter_value_val->BooleanValue(); + } else { + return utils::CallbackError("value filter value must be a number or boolean", callback); + } + query_data->basic_filter.filters.push_back(filter); + } + } else { + return utils::CallbackError("'basic-filters' must be of the form [type, [filters]]", callback); + } + } } auto* worker = new Worker{std::move(query_data), new Nan::Callback{callback}}; diff --git a/test/vtquery.test.js b/test/vtquery.test.js index 329fc6e1..98af02a9 100644 --- a/test/vtquery.test.js +++ b/test/vtquery.test.js @@ -681,11 +681,208 @@ test('options - dedupe: compare fields from real-world tiles (increases coverage }); }); +test('options - filter: Empty List', 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 + 'basic-filters': ['all', []] + }; + vtquery(tiles, [-122.3384, 47.6635], opts, function(err, result) { + assert.equal(result.features.length, 1, 'expected one feature'); + assert.ifError(err); + assert.end(); + }); +}); + +test('options - filter: Invalid Filter Condition', 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 + 'basic-filters': ['all', [['int_value', '==', 6]]] + }; + vtquery(tiles, [-122.3384, 47.6635], opts, function(err, result) { + assert.ok(err); + assert.equal(err.message, 'condition filter value must be =, !=, <, <=, >, or >='); + assert.end(); + }); +}); + +test('options - filter: Invalid Filter String', 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 + 'basic-filters': ['all', [['string_value', '=',"Strings_Not_Allowed"]]] + }; + vtquery(tiles, [-122.3384, 47.6635], opts, function(err, result) { + assert.ok(err); + assert.equal(err.message, 'value filter value must be a number or boolean'); + assert.end(); + }); +}); + +test('options - filter: Invalid Filter Array', 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 + 'basic-filters': ['all', ['int_value', '=', 6]] + }; + vtquery(tiles, [-122.3384, 47.6635], opts, function(err, result) { + assert.ok(err); + assert.equal(err.message, 'filters must be of the form [parameter, condition, value]'); + assert.end(); + }); +}); + +test('options - filter: Test Int Equals', 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 + 'basic-filters': ['all', [['int_value', '=', 6]]] + }; + vtquery(tiles, [-122.3384, 47.6635], opts, function(err, result) { + assert.ifError(err); + assert.equal(result.features.length, 1, 'expected one feature'); + const p = result.features[0].properties; + assert.equal(p.int_value, 6, 'expected int value'); + assert.end(); + }); +}); + +test('options - filter: Test Boolean Not Equals', 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 + 'basic-filters': ['all', [['bool_value', '!=', false]]] + }; + vtquery(tiles, [-122.3384, 47.6635], opts, function(err, result) { + assert.equal(result.features.length, 1, 'expected one feature'); + const p = result.features[0].properties; + assert.equal(p.bool_value, true, 'expected value type'); + assert.ifError(err); + assert.end(); + }); +}); + +test('options - filter: Test Float Approximate Equals', 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 + 'basic-filters': ['all', [['float_value', '=', 3.1]]] + }; + vtquery(tiles, [-122.3384, 47.6635], opts, function(err, result) { + assert.equal(result.features.length, 1, 'expected one feature'); + const p = result.features[0].properties; + assert.equal(p.float_value, 3.0999999046325684, 'expected value type'); + assert.ifError(err); + assert.end(); + }); +}); + +test('options - filter: Test No Results Less Than Equals', 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 + 'basic-filters': ['all', [['uint_value', '<', 0]]] + }; + vtquery(tiles, [-122.3384, 47.6635], opts, function(err, result) { + assert.equal(result.features.length, 0, 'expected one feature'); + assert.ifError(err); + assert.end(); + }); +}); + +test('options - filter: Test No Results Greater Than Equals', 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 + 'basic-filters': ['all', [['sint_value', '>', 0]]] + }; + vtquery(tiles, [-122.3384, 47.6635], opts, function(err, result) { + assert.equal(result.features.length, 0, 'expected one feature'); + assert.ifError(err); + assert.end(); + }); +}); + +test('options - filter: Test Double Int Greater Than Equal', 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 + 'basic-filters': ['all', [['double_value', '>',-2]]] + }; + vtquery(tiles, [-122.3384, 47.6635], opts, function(err, result) { + assert.equal(result.features.length, 1, 'expected one feature'); + const p = result.features[0].properties; + assert.equal(p.double_value, 1.23, 'expected value type'); + assert.ifError(err); + assert.end(); + }); +}); + +test('options - filter: Test filter with multiple features', assert => { + const tiles = [{buffer: mvtf.get('062').buffer, z: 15, x: 5248, y: 11436}]; + const opts = { + radius: 800, // about the width of a z15 tile + 'basic-filters': ['all', [['population', '>', 10]]] + }; + vtquery(tiles, [-122.3384, 47.6635], opts, function(err, result) { + assert.equal(result.features.length, 3, 'expected one feature'); + let cities = []; + for (let i = 0; i < 3; i++) { + cities.push(result.features[i].properties.name); + } + assert.assert(cities.includes('RadEstablishment'), 'Missing RadEstablishment for population check'); + assert.assert(cities.includes('AwesomeCity'), 'Missing AwesomeCity for population check'); + assert.assert(cities.includes('TubularTown'), 'Missing TubularTown for population check'); + assert.ifError(err); + assert.end(); + }); +}); + +test('options - filter: Test all filter with multiple features', assert => { + const tiles = [{buffer: mvtf.get('062').buffer, z: 15, x: 5248, y: 11436}]; + const opts = { + radius: 800, // about the width of a z15 tile + 'basic-filters': ['all', [['population', '>', 10], ['population', '<', 1000]]] + }; + vtquery(tiles, [-122.3384, 47.6635], opts, function(err, result) { + assert.equal(result.features.length, 2, 'expected two features'); + let cities = []; + for (let i = 0; i < 2; i++) { + cities.push(result.features[i].properties.name); + } + assert.assert(cities.includes('RadEstablishment'), 'Missing RadEstablishment for population check'); + assert.assert(cities.includes('AwesomeCity'), 'Missing AwesomeCity for population check'); + assert.ifError(err); + assert.end(); + }); +}); + +test('options - filter: Test any filter with multiple features', assert => { + const tiles = [{buffer: mvtf.get('062').buffer, z: 15, x: 5248, y: 11436}]; + const opts = { + radius: 800, // about the width of a z15 tile + 'basic-filters': ['any', [['population', '<=', 10], ['population', '>', 1000]]] + }; + vtquery(tiles, [-122.3384, 47.6635], opts, function(err, result) { + assert.equal(result.features.length, 3, 'expected three features'); + let cities = []; + for (let i = 0; i < 3; i++) { + cities.push(result.features[i].properties.name); + } + assert.assert(cities.includes('Neatville'), 'Missing Neatville for population check'); + assert.assert(cities.includes('CoolVillage'), 'Missing CoolVillage for population check'); + assert.assert(cities.includes('TubularTown'), 'Missing TubularTown for population check'); + assert.ifError(err); + 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; From 10c55e7bcd2d619ab632f5959ccfa8d7b5d041e8 Mon Sep 17 00:00:00 2001 From: Ryan Cook Date: Tue, 24 Sep 2019 12:54:52 -0400 Subject: [PATCH 2/2] Fixed codecov --- .travis.yml | 4 +++- scripts/coverage.sh | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 40e8ebbe..476676a2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -111,12 +111,14 @@ matrix: node_js: 10 # Overrides `script` to publish coverage data to codecov before_script: - - npm test + - pip install --user codecov - mason install llvm-cov ${MASON_LLVM_RELEASE} - mason link llvm-cov ${MASON_LLVM_RELEASE} - which llvm-cov - curl -S -f https://codecov.io/bash -o codecov - chmod +x codecov + script: + - make coverage - ./codecov -x "llvm-cov gcov" -Z # Clang format build - os: linux diff --git a/scripts/coverage.sh b/scripts/coverage.sh index 40718fce..0380c924 100755 --- a/scripts/coverage.sh +++ b/scripts/coverage.sh @@ -26,4 +26,5 @@ llvm-profdata merge -output=code.profdata code-*.profraw llvm-cov report ${CXX_MODULE} -instr-profile=code.profdata -use-color llvm-cov show ${CXX_MODULE} -instr-profile=code.profdata src/*.cpp -filename-equivalence -use-color llvm-cov show ${CXX_MODULE} -instr-profile=code.profdata src/*.cpp -filename-equivalence -use-color --format html > /tmp/coverage.html +llvm-cov show ${CXX_MODULE} -instr-profile=code.profdata src/*.cpp -filename-equivalence > coverage.txt echo "open /tmp/coverage.html for HTML version of this report"