From 37cea119e90c2f6450f1d5a87a6a9d2b191736ea Mon Sep 17 00:00:00 2001 From: zmiao Date: Tue, 10 Mar 2020 00:01:34 +0200 Subject: [core] Introduce distance expression --- CMakeLists.txt | 6 +- include/mbgl/style/expression/distance.hpp | 37 ++++ include/mbgl/style/expression/expression.hpp | 3 +- platform/glfw/glfw_view.cpp | 64 +++++++ src/mbgl/style/expression/distance.cpp | 251 ++++++++++++++++++++++++++ src/mbgl/style/expression/is_constant.cpp | 4 + src/mbgl/style/expression/parsing_context.cpp | 2 + vendor/cheap-ruler-cpp | 2 +- 8 files changed, 366 insertions(+), 3 deletions(-) create mode 100644 include/mbgl/style/expression/distance.hpp create mode 100644 src/mbgl/style/expression/distance.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 588975e589..c070e88ca8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -166,6 +166,7 @@ add_library( ${PROJECT_SOURCE_DIR}/include/mbgl/style/expression/comparison.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/style/expression/compound_expression.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/style/expression/dsl.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/style/expression/distance.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/style/expression/error.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/style/expression/expression.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/style/expression/find_zoom_curve.hpp @@ -555,6 +556,7 @@ add_library( ${PROJECT_SOURCE_DIR}/src/mbgl/style/expression/collator_expression.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/expression/comparison.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/expression/compound_expression.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/style/expression/distance.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/expression/dsl.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/expression/dsl_impl.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/expression/expression.cpp @@ -940,6 +942,7 @@ include(${PROJECT_SOURCE_DIR}/vendor/protozero.cmake) include(${PROJECT_SOURCE_DIR}/vendor/unique_resource.cmake) include(${PROJECT_SOURCE_DIR}/vendor/vector-tile.cmake) include(${PROJECT_SOURCE_DIR}/vendor/wagyu.cmake) +include(${PROJECT_SOURCE_DIR}/vendor/cheap-ruler-cpp.cmake) target_link_libraries( mbgl-core @@ -959,6 +962,7 @@ target_link_libraries( mbgl-vendor-unique_resource mbgl-vendor-vector-tile mbgl-vendor-wagyu + mbgl-vendor-cheap-ruler-cpp PUBLIC Mapbox::Base::Extras::expected-lite Mapbox::Base::Extras::rapidjson @@ -994,7 +998,7 @@ if(MBGL_WITH_CORE_ONLY) return() endif() -include(${PROJECT_SOURCE_DIR}/scripts/license.cmake) +#include(${PROJECT_SOURCE_DIR}/scripts/license.cmake) if(MBGL_WITH_QT) include(${PROJECT_SOURCE_DIR}/platform/qt/qt.cmake) diff --git a/include/mbgl/style/expression/distance.hpp b/include/mbgl/style/expression/distance.hpp new file mode 100644 index 0000000000..b5bfe51494 --- /dev/null +++ b/include/mbgl/style/expression/distance.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include + +namespace mbgl { +namespace style { +namespace expression { + +class Distance final : public Expression { +public: + explicit Distance(GeoJSON geoJSONSource_, Feature::geometry_type geometries_); + + ~Distance() override; + + EvaluationResult evaluate(const EvaluationContext&) const override; + + static ParseResult parse(const mbgl::style::conversion::Convertible&, ParsingContext&); + + void eachChild(const std::function&) const override {} + + bool operator==(const Expression& e) const override; + + std::vector> possibleOutputs() const override; + + mbgl::Value serialize() const override; + std::string getOperator() const override; + +private: + GeoJSON geoJSONSource; + Feature::geometry_type geometries; +}; + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/expression/expression.hpp b/include/mbgl/style/expression/expression.hpp index e522821185..51cc3b0f15 100644 --- a/include/mbgl/style/expression/expression.hpp +++ b/include/mbgl/style/expression/expression.hpp @@ -169,7 +169,8 @@ enum class Kind : int32_t { NumberFormat, ImageExpression, In, - Within + Within, + Distance }; class Expression { diff --git a/platform/glfw/glfw_view.cpp b/platform/glfw/glfw_view.cpp index d6c85e3068..5c086bb242 100644 --- a/platform/glfw/glfw_view.cpp +++ b/platform/glfw/glfw_view.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -28,6 +29,8 @@ #include #include +#include + #if MBGL_USE_GLES2 #define GLFW_INCLUDE_ES2 #endif // MBGL_USE_GLES2 @@ -458,6 +461,67 @@ void GLFWView::onKey(GLFWwindow *window, int key, int /*scancode*/, int action, // Snapshot with overlay view->makeSnapshot(true); } break; + case GLFW_KEY_G: { + using namespace mbgl::style; + using namespace mbgl::style::conversion; + using namespace mbgl::style::expression::dsl; + + static const std::string points = R"({ + "type": "MultiPoint", + "coordinates": [ + [ + 24.919674396514893, + 60.16455037398201 + ], + [ + 24.921332001686096, + 60.16460108220136 + ], + [ + 24.924674034118652, + 60.16487063510643 + ], + [ + 24.931159615516663, + 60.16733387258923 + ] + ] + })"; + + static mapbox::geojson::geojson route{mapbox::geojson::parse(mbgl::platform::glfw::route)}; + + auto s2 = std::make_unique("line"); + s2->setGeoJSON(route); + view->map->getStyle().addSource(std::move(s2)); + +// auto fillLayer = std::make_unique("fill", "polygon"); +// fillLayer->setFillColor(mbgl::Color::black()); +// fillLayer->setFillOpacity(0.1); +// view->map->getStyle().addLayer(std::move(fillLayer)); + + auto lineLayer = std::make_unique("line", "line"); + lineLayer->setLineColor(mbgl::Color::red()); + view->map->getStyle().addLayer(std::move(lineLayer)); + + auto &style = view->map->getStyle(); + auto labelLayer = style.getLayer("poi-label"); + if (labelLayer) { + auto symbolLayer = static_cast(labelLayer); +// std::stringstream ss; +// ss << std::string(R"(["case", ["<", ["distance", )") << mbgl::platform::glfw::route << std::string(R"( ], 100.0], 1, 0.2] )"); +// auto expr = createExpression(ss.str().c_str()); +// if (expr) { +//// symbolLayer->setIconOpacity(PropertyExpression(std::move(expr))); +// symbolLayer->setTextOpacity(PropertyExpression(std::move(expr))); +// } + std::stringstream ss; + ss << std::string(R"([ "<" , ["distance", )") << mbgl::platform::glfw::route << std::string(R"( ], 100])"); + auto expr = createExpression(ss.str().c_str()); + if (expr) { + symbolLayer->setFilter(Filter(std::move(expr))); + } + } + } break; } } diff --git a/src/mbgl/style/expression/distance.cpp b/src/mbgl/style/expression/distance.cpp new file mode 100644 index 0000000000..21dc57a02e --- /dev/null +++ b/src/mbgl/style/expression/distance.cpp @@ -0,0 +1,251 @@ +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +namespace mbgl { +namespace { +// +////Returns the distance between a point P on a segment AB. +//double distanceToLineSegment(const mapbox::geometry::point& p, const mapbox::geometry::point& a,const mapbox::geometry::point& b) { +// mapbox::geometry::point vectorAB{(b.x - a.x), (b.y - a.y)}; +// mapbox::geometry::point vectorAP{(p.x - a.x), (p.y - a.y)}; +// const auto dotProduct = [](const mapbox::geometry::point& v,const mapbox::geometry::point& w) { +// return v.x * w.x + v.y * w.y; +// }; +// +// mapbox::cheap_ruler::CheapRuler ruler(p.y, mapbox::cheap_ruler::CheapRuler::Unit::Meters); +// // dotProduct equals to |vectorAB| * |vectorAP| * cos(); +// const double dot1 = dotProduct(vectorAB, vectorAP); +// if (dot1 < 0) return ruler.distance(p, a); +// +// const double dot2 = dotProduct(vectorAB, vectorAB); +// if (dot1 >= dot2) return ruler.distance(p, b); +// +// const double ratio = dot1 / dot2; +// const mapbox::geometry::point pointOnAB = {(a.x + vectorAB.x * ratio), (a.y + vectorAB.y * ratio)}; +// return ruler.distance(p, pointOnAB); +//} +// +//double distanceToLineInMeters(const mapbox::geometry::point& point, const mapbox::geometry::line_string& line){ +// double ret = std::numeric_limits::infinity(); +// for (size_t i = 0; i < line.size() - 1; ++i){ +// ret = std::min(ret, distanceToLineSegment(point, line[i], line[i+1])); +// } +// return ret; +//} +// +//double distanceToLinesInMeters(const mapbox::geometry::point& point, const mapbox::geometry::multi_line_string& lines){ +// double ret = std::numeric_limits::infinity(); +// for (const auto& line: lines) { +// ret = std::min(ret, distanceToLineInMeters(point, line)); +// } +// return ret; +//} + +double calculateDistance(const mbgl::GeometryTileFeature& feature, + const mbgl::CanonicalTileID& canonical, + const Feature::geometry_type& geoSet) { + return convertGeometry(feature, canonical).match( + [&geoSet](const mapbox::geometry::point& point) -> double { + mapbox::cheap_ruler::CheapRuler ruler(point.y, mapbox::cheap_ruler::CheapRuler::Unit::Meters); + return geoSet.match( + [&point, &ruler](const mapbox::geometry::point& point2) { + return ruler.distance(point, point2); + }, + [&point, &ruler](const mapbox::geometry::multi_point& points) { + double ret = std::numeric_limits::infinity(); + for (size_t i = 0; i < points.size() - 1; ++i){ + ret = std::min(ret, ruler.distance(point, points[i])); + } + return ret; + }, + [&point, &ruler](const mapbox::geometry::line_string& line) { + double ret = std::numeric_limits::infinity(); + for (size_t i = 0; i < line.size() - 1; ++i){ + ret = std::min(ret, ruler.distanceToLineSegment(point, line[i], line[i+1])); + } + return ret; + }, + [&point,&ruler](const mapbox::geometry::multi_line_string& lines) { + double ret = std::numeric_limits::infinity(); + for (const auto& line: lines) { + for (size_t i = 0; i < line.size() - 1; ++i){ + ret = std::min(ret, ruler.distanceToLineSegment(point, line[i], line[i+1])); + } + } + return ret; + }, + [](const auto&) -> double { return -1.0; }); + }, + [](const auto&) -> double { return -1.0; }); +} + +optional parseValue(const mbgl::style::conversion::Convertible& value_, + mbgl::style::expression::ParsingContext& ctx) { + if (isObject(value_)) { + mbgl::style::conversion::Error error; + auto geojson = toGeoJSON(value_, error); + if (geojson && error.message.empty()) { + return geojson; + } + ctx.error(error.message); + } + + ctx.error("'distance' expression requires valid geojson source that contains polygon geometry type."); + return nullopt; +} + +optional getGeometry(const Feature& feature, mbgl::style::expression::ParsingContext& ctx) { + const auto type = apply_visitor(ToFeatureType(), feature.geometry); + if (type == FeatureType::Point || type == FeatureType::LineString) { + return feature.geometry; + } + ctx.error("'distance' expression requires valid geojson source that contains Point/LineString geometry type."); + return nullopt; + +} +} // namespace + +namespace style { +namespace expression { + +Distance::Distance(GeoJSON geojson, Feature::geometry_type geometries_) + : Expression(Kind::Distance, type::Number), + geoJSONSource(std::move(geojson)), + geometries(std::move(geometries_)){} + +Distance::~Distance() = default; + +using namespace mbgl::style::conversion; + +EvaluationResult Distance::evaluate(const EvaluationContext& params) const { + if (!params.feature || !params.canonical) { + return -1.0; + } + auto geometryType = params.feature->getType(); + // Currently only support Point/Points distance to LineString + if (geometryType == FeatureType::Point) { + auto distance = calculateDistance(*params.feature, *params.canonical, geometries); + return distance; + } + mbgl::Log::Warning(mbgl::Event::General, + "distance expression currently only support feature with Point geometry."); + + return -1.0; +} + +ParseResult Distance::parse(const Convertible& value, ParsingContext& ctx) { + if (isArray(value)) { + // object value, quoted with ["Distance", value] + if (arrayLength(value) != 2) { + ctx.error("'distance' expression requires exactly one argument, but found " + + util::toString(arrayLength(value) - 1) + " instead."); + return ParseResult(); + } + + auto parsedValue = parseValue(arrayMember(value, 1), ctx); + if (!parsedValue) { + return ParseResult(); + } + + return parsedValue->match( + [&parsedValue, &ctx](const mapbox::geometry::geometry& geometrySet) { + if (auto ret = getGeometry(mbgl::Feature(geometrySet), ctx)) { + return ParseResult(std::make_unique(*parsedValue, std::move(*ret))); + } + return ParseResult(); + }, + [&parsedValue, &ctx](const mapbox::feature::feature& feature) { + if (auto ret = getGeometry(mbgl::Feature(feature), ctx)) { + return ParseResult(std::make_unique(*parsedValue, std::move(*ret))); + } + return ParseResult(); + }, + [&parsedValue, &ctx](const mapbox::feature::feature_collection& features) { + for (const auto& feature : features) { + if (auto ret = getGeometry(mbgl::Feature(feature), ctx)) { + return ParseResult(std::make_unique(*parsedValue, std::move(*ret))); + } + } + return ParseResult(); + }, + [&ctx](const auto&) { + ctx.error("'distance' expression requires valid geojson that contains LineString/Point geometries."); + return ParseResult(); + }); + } + ctx.error("'distance' expression needs to be an array with exactly one argument."); + return ParseResult(); +} + +Value convertValue(const mapbox::geojson::rapidjson_value& v) { + if (v.IsDouble()) { + return v.GetDouble(); + } + if (v.IsString()) { + return std::string(v.GetString()); + } + if (v.IsArray()) { + std::vector result; + result.reserve(v.Size()); + for (const auto& m : v.GetArray()) { + result.push_back(convertValue(m)); + } + return result; + } + if (v.IsObject()) { + std::unordered_map result; + for (const auto& m : v.GetObject()) { + result.emplace(m.name.GetString(), convertValue(m.value)); + } + return result; + } + // Ignore other types as valid geojson only contains above types. + return Null; +} + +mbgl::Value Distance::serialize() const { + std::unordered_map serialized; + rapidjson::CrtAllocator allocator; + const mapbox::geojson::rapidjson_value value = mapbox::geojson::convert(geoJSONSource, allocator); + if (value.IsObject()) { + for (const auto& m : value.GetObject()) { + serialized.emplace(m.name.GetString(), convertValue(m.value)); + } + } else { + mbgl::Log::Error(mbgl::Event::General, + "Failed to serialize 'distance' expression, converted rapidJSON is not an object"); + } + return std::vector{{getOperator(), *fromExpressionValue(serialized)}}; +} + +bool Distance::operator==(const Expression& e) const { + if (e.getKind() == Kind::Distance) { + auto rhs = static_cast(&e); + return geoJSONSource == rhs->geoJSONSource && geometries == rhs->geometries; + } + return false; +} + +std::vector> Distance::possibleOutputs() const { + return { nullopt }; +} + +std::string Distance::getOperator() const { + return "distance"; +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/is_constant.cpp b/src/mbgl/style/expression/is_constant.cpp index e6bd05418f..7b14e79eda 100644 --- a/src/mbgl/style/expression/is_constant.cpp +++ b/src/mbgl/style/expression/is_constant.cpp @@ -33,6 +33,10 @@ bool isFeatureConstant(const Expression& expression) { return false; } + if (expression.getKind() == Kind::Distance) { + return false; + } + if (expression.getKind() == Kind::CollatorExpression) { // Although the results of a Collator expression with fixed arguments // generally shouldn't change between executions, we can't serialize them diff --git a/src/mbgl/style/expression/parsing_context.cpp b/src/mbgl/style/expression/parsing_context.cpp index 2f1e1c1820..a6dc05f883 100644 --- a/src/mbgl/style/expression/parsing_context.cpp +++ b/src/mbgl/style/expression/parsing_context.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -120,6 +121,7 @@ MAPBOX_ETERNAL_CONSTEXPR const auto expressionRegistry = {"case", Case::parse}, {"coalesce", Coalesce::parse}, {"collator", CollatorExpression::parse}, + {"distance", Distance::parse}, {"format", FormatExpression::parse}, {"image", ImageExpression::parse}, {"interpolate", parseInterpolate}, diff --git a/vendor/cheap-ruler-cpp b/vendor/cheap-ruler-cpp index 98cbe70dab..a65a1438c7 160000 --- a/vendor/cheap-ruler-cpp +++ b/vendor/cheap-ruler-cpp @@ -1 +1 @@ -Subproject commit 98cbe70dab74c5b77f139013ea5b3623d3ba3dc6 +Subproject commit a65a1438c70bc2433abda383e3bd64d98cc50d02 -- cgit v1.2.1