From acae19386129056b9425b114b01f062feecd297e Mon Sep 17 00:00:00 2001 From: Asheem Mamoowala Date: Wed, 22 Nov 2017 10:18:53 -0800 Subject: [PATCH] [core] Custom Geometry Sources --- cmake/core-files.cmake | 11 ++ cmake/test-files.cmake | 2 + .../custom_geometry_source_options.hpp | 64 +++++++++ .../style/sources/custom_geometry_source.hpp | 56 ++++++++ include/mbgl/style/sources/geojson_source.hpp | 2 + include/mbgl/style/types.hpp | 3 +- .../default/mbgl/storage/offline_download.cpp | 2 + src/mbgl/renderer/render_source.cpp | 3 + .../sources/render_custom_geometry_source.cpp | 86 ++++++++++++ .../sources/render_custom_geometry_source.hpp | 50 +++++++ src/mbgl/style/custom_tile_loader.cpp | 114 +++++++++++++++ src/mbgl/style/custom_tile_loader.hpp | 47 +++++++ .../style/sources/custom_geometry_source.cpp | 45 ++++++ .../sources/custom_geometry_source_impl.cpp | 40 ++++++ .../sources/custom_geometry_source_impl.hpp | 29 ++++ src/mbgl/style/types.cpp | 1 + src/mbgl/tile/custom_geometry_tile.cpp | 81 +++++++++++ src/mbgl/tile/custom_geometry_tile.hpp | 34 +++++ test/api/custom_geometry_source.test.cpp | 71 ++++++++++ .../custom_geometry_source/grid/expected.png | Bin 0 -> 8302 bytes test/style/source.test.cpp | 37 +++++ test/tile/custom_geometry_tile.test.cpp | 130 ++++++++++++++++++ 22 files changed, 907 insertions(+), 1 deletion(-) create mode 100644 include/mbgl/style/conversion/custom_geometry_source_options.hpp create mode 100644 include/mbgl/style/sources/custom_geometry_source.hpp create mode 100644 src/mbgl/renderer/sources/render_custom_geometry_source.cpp create mode 100644 src/mbgl/renderer/sources/render_custom_geometry_source.hpp create mode 100644 src/mbgl/style/custom_tile_loader.cpp create mode 100644 src/mbgl/style/custom_tile_loader.hpp create mode 100644 src/mbgl/style/sources/custom_geometry_source.cpp create mode 100644 src/mbgl/style/sources/custom_geometry_source_impl.cpp create mode 100644 src/mbgl/style/sources/custom_geometry_source_impl.hpp create mode 100644 src/mbgl/tile/custom_geometry_tile.cpp create mode 100644 src/mbgl/tile/custom_geometry_tile.hpp create mode 100644 test/api/custom_geometry_source.test.cpp create mode 100644 test/fixtures/custom_geometry_source/grid/expected.png create mode 100644 test/tile/custom_geometry_tile.test.cpp diff --git a/cmake/core-files.cmake b/cmake/core-files.cmake index 2af07f48c..42c25c8ea 100644 --- a/cmake/core-files.cmake +++ b/cmake/core-files.cmake @@ -242,6 +242,8 @@ set(MBGL_CORE_FILES src/mbgl/renderer/layers/render_symbol_layer.hpp # renderer/sources + src/mbgl/renderer/sources/render_custom_geometry_source.cpp + src/mbgl/renderer/sources/render_custom_geometry_source.hpp src/mbgl/renderer/sources/render_geojson_source.cpp src/mbgl/renderer/sources/render_geojson_source.hpp src/mbgl/renderer/sources/render_image_source.cpp @@ -334,6 +336,8 @@ set(MBGL_CORE_FILES include/mbgl/style/types.hpp include/mbgl/style/undefined.hpp src/mbgl/style/collection.hpp + src/mbgl/style/custom_tile_loader.cpp + src/mbgl/style/custom_tile_loader.hpp src/mbgl/style/image.cpp src/mbgl/style/image_impl.cpp src/mbgl/style/image_impl.hpp @@ -364,6 +368,7 @@ set(MBGL_CORE_FILES # style/conversion include/mbgl/style/conversion/constant.hpp include/mbgl/style/conversion/coordinate.hpp + include/mbgl/style/conversion/custom_geometry_source_options.hpp include/mbgl/style/conversion/data_driven_property_value.hpp include/mbgl/style/conversion/expression.hpp include/mbgl/style/conversion/filter.hpp @@ -506,10 +511,14 @@ set(MBGL_CORE_FILES src/mbgl/style/layers/symbol_layer_properties.hpp # style/sources + include/mbgl/style/sources/custom_geometry_source.hpp include/mbgl/style/sources/geojson_source.hpp include/mbgl/style/sources/image_source.hpp include/mbgl/style/sources/raster_source.hpp include/mbgl/style/sources/vector_source.hpp + src/mbgl/style/sources/custom_geometry_source.cpp + src/mbgl/style/sources/custom_geometry_source_impl.cpp + src/mbgl/style/sources/custom_geometry_source_impl.hpp src/mbgl/style/sources/geojson_source.cpp src/mbgl/style/sources/geojson_source_impl.cpp src/mbgl/style/sources/geojson_source_impl.hpp @@ -555,6 +564,8 @@ set(MBGL_CORE_FILES # tile include/mbgl/tile/tile_id.hpp include/mbgl/tile/tile_necessity.hpp + src/mbgl/tile/custom_geometry_tile.cpp + src/mbgl/tile/custom_geometry_tile.hpp src/mbgl/tile/geojson_tile.cpp src/mbgl/tile/geojson_tile.hpp src/mbgl/tile/geojson_tile_data.hpp diff --git a/cmake/test-files.cmake b/cmake/test-files.cmake index b2ddc2b36..43bc1210f 100644 --- a/cmake/test-files.cmake +++ b/cmake/test-files.cmake @@ -15,6 +15,7 @@ set(MBGL_TEST_FILES # api test/api/annotations.test.cpp test/api/api_misuse.test.cpp + test/api/custom_geometry_source.test.cpp test/api/custom_layer.test.cpp test/api/query.test.cpp test/api/recycle_map.cpp @@ -117,6 +118,7 @@ set(MBGL_TEST_FILES test/text/quads.test.cpp # tile + test/tile/custom_geometry_tile.test.cpp test/tile/geojson_tile.test.cpp test/tile/geometry_tile_data.test.cpp test/tile/raster_tile.test.cpp diff --git a/include/mbgl/style/conversion/custom_geometry_source_options.hpp b/include/mbgl/style/conversion/custom_geometry_source_options.hpp new file mode 100644 index 000000000..73b141e79 --- /dev/null +++ b/include/mbgl/style/conversion/custom_geometry_source_options.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include +#include + +namespace mbgl { +namespace style { +namespace conversion { + +template <> +struct Converter { + + template + optional operator()(const V& value, Error& error) const { + CustomGeometrySource::Options options; + + const auto minzoomValue = objectMember(value, "minzoom"); + if (minzoomValue) { + if (toNumber(*minzoomValue)) { + options.zoomRange.min = static_cast(*toNumber(*minzoomValue)); + } else { + error = { "GeoJSON source minzoom value must be a number" }; + return {}; + } + } + + const auto maxzoomValue = objectMember(value, "maxzoom"); + if (maxzoomValue) { + if (toNumber(*maxzoomValue)) { + options.zoomRange.max = static_cast(*toNumber(*maxzoomValue)); + } else { + error = { "GeoJSON source maxzoom value must be a number" }; + return {}; + } + } + + const auto bufferValue = objectMember(value, "buffer"); + if (bufferValue) { + if (toNumber(*bufferValue)) { + options.tileOptions.buffer = static_cast(*toNumber(*bufferValue)); + } else { + error = { "GeoJSON source buffer value must be a number" }; + return {}; + } + } + + const auto toleranceValue = objectMember(value, "tolerance"); + if (toleranceValue) { + if (toNumber(*toleranceValue)) { + options.tileOptions.tolerance = static_cast(*toNumber(*toleranceValue)); + } else { + error = { "GeoJSON source tolerance value must be a number" }; + return {}; + } + } + + return { options }; + } + +}; + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/sources/custom_geometry_source.hpp b/include/mbgl/style/sources/custom_geometry_source.hpp new file mode 100644 index 000000000..853526ac2 --- /dev/null +++ b/include/mbgl/style/sources/custom_geometry_source.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace mbgl { + +class OverscaledTileID; +class CanonicalTileID; + +namespace style { + +using TileFunction = std::function; + +class CustomTileLoader; + +class CustomGeometrySource : public Source { +public: + struct TileOptions { + double tolerance = 0.375; + uint16_t tileSize = util::tileSize; + uint16_t buffer = 128; + }; + + struct Options { + TileFunction fetchTileFunction; + TileFunction cancelTileFunction; + Range zoomRange = { 0, 18}; + TileOptions tileOptions; + }; +public: + CustomGeometrySource(std::string id, CustomGeometrySource::Options options); + ~CustomGeometrySource() final; + void loadDescription(FileSource&) final; + void setTileData(const CanonicalTileID&, const GeoJSON&); + void invalidateTile(const CanonicalTileID&); + void invalidateRegion(const LatLngBounds&); + // Private implementation + class Impl; + const Impl& impl() const; +private: + std::shared_ptr mailbox; + std::unique_ptr loader; +}; + +template <> +inline bool Source::is() const { + return getType() == SourceType::CustomVector; +} + +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/sources/geojson_source.hpp b/include/mbgl/style/sources/geojson_source.hpp index 5bdf1ef95..372e7c7a7 100644 --- a/include/mbgl/style/sources/geojson_source.hpp +++ b/include/mbgl/style/sources/geojson_source.hpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace mbgl { @@ -14,6 +15,7 @@ struct GeoJSONOptions { // GeoJSON-VT options uint8_t minzoom = 0; uint8_t maxzoom = 18; + uint16_t tileSize = util::tileSize; uint16_t buffer = 128; double tolerance = 0.375; diff --git a/include/mbgl/style/types.hpp b/include/mbgl/style/types.hpp index 2ed95f08b..6fe457e18 100644 --- a/include/mbgl/style/types.hpp +++ b/include/mbgl/style/types.hpp @@ -13,7 +13,8 @@ enum class SourceType : uint8_t { GeoJSON, Video, Annotations, - Image + Image, + CustomVector }; enum class VisibilityType : bool { diff --git a/platform/default/mbgl/storage/offline_download.cpp b/platform/default/mbgl/storage/offline_download.cpp index ff6111488..8bb16993a 100644 --- a/platform/default/mbgl/storage/offline_download.cpp +++ b/platform/default/mbgl/storage/offline_download.cpp @@ -129,6 +129,7 @@ OfflineRegionStatus OfflineDownload::getStatus() const { case SourceType::Video: case SourceType::Annotations: + case SourceType::CustomVector: break; } } @@ -214,6 +215,7 @@ void OfflineDownload::activateDownload() { case SourceType::Video: case SourceType::Annotations: + case SourceType::CustomVector: break; } } diff --git a/src/mbgl/renderer/render_source.cpp b/src/mbgl/renderer/render_source.cpp index 7723a1c7c..6624bb7d9 100644 --- a/src/mbgl/renderer/render_source.cpp +++ b/src/mbgl/renderer/render_source.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include namespace mbgl { @@ -27,6 +28,8 @@ std::unique_ptr RenderSource::create(Immutable impl) return std::make_unique(staticImmutableCast(impl)); case SourceType::Image: return std::make_unique(staticImmutableCast(impl)); + case SourceType::CustomVector: + return std::make_unique(staticImmutableCast(impl)); } // Not reachable, but placate GCC. diff --git a/src/mbgl/renderer/sources/render_custom_geometry_source.cpp b/src/mbgl/renderer/sources/render_custom_geometry_source.cpp new file mode 100644 index 000000000..111f0234e --- /dev/null +++ b/src/mbgl/renderer/sources/render_custom_geometry_source.cpp @@ -0,0 +1,86 @@ +#include +#include +#include +#include + +#include +#include + +namespace mbgl { + +using namespace style; + +RenderCustomGeometrySource::RenderCustomGeometrySource(Immutable impl_) + : RenderSource(impl_) { + tilePyramid.setObserver(this); +} + +const style::CustomGeometrySource::Impl& RenderCustomGeometrySource::impl() const { + return static_cast(*baseImpl); +} + +bool RenderCustomGeometrySource::isLoaded() const { + return tilePyramid.isLoaded(); +} + +void RenderCustomGeometrySource::update(Immutable baseImpl_, + const std::vector>& layers, + const bool needsRendering, + const bool needsRelayout, + const TileParameters& parameters) { + std::swap(baseImpl, baseImpl_); + + enabled = needsRendering; + + auto tileLoader = impl().getTileLoader(); + if (!tileLoader) { + return; + } + + tilePyramid.update(layers, + needsRendering, + needsRelayout, + parameters, + SourceType::CustomVector, + util::tileSize, + impl().getZoomRange(), + [&] (const OverscaledTileID& tileID) { + return std::make_unique(tileID, impl().id, parameters, impl().getTileOptions(), *tileLoader); + }); +} + +void RenderCustomGeometrySource::startRender(PaintParameters& parameters) { + parameters.clipIDGenerator.update(tilePyramid.getRenderTiles()); + tilePyramid.startRender(parameters); +} + +void RenderCustomGeometrySource::finishRender(PaintParameters& parameters) { + tilePyramid.finishRender(parameters); +} + +std::vector> RenderCustomGeometrySource::getRenderTiles() { + return tilePyramid.getRenderTiles(); +} + +std::unordered_map> +RenderCustomGeometrySource::queryRenderedFeatures(const ScreenLineString& geometry, + const TransformState& transformState, + const std::vector& layers, + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) const { + return tilePyramid.queryRenderedFeatures(geometry, transformState, layers, options, collisionIndex); +} + +std::vector RenderCustomGeometrySource::querySourceFeatures(const SourceQueryOptions& options) const { + return tilePyramid.querySourceFeatures(options); +} + +void RenderCustomGeometrySource::onLowMemory() { + tilePyramid.onLowMemory(); +} + +void RenderCustomGeometrySource::dumpDebugLogs() const { + tilePyramid.dumpDebugLogs(); +} + +} // namespace mbgl diff --git a/src/mbgl/renderer/sources/render_custom_geometry_source.hpp b/src/mbgl/renderer/sources/render_custom_geometry_source.hpp new file mode 100644 index 000000000..82e691d5c --- /dev/null +++ b/src/mbgl/renderer/sources/render_custom_geometry_source.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include + +namespace mbgl { + +class RenderCustomGeometrySource : public RenderSource { +public: + RenderCustomGeometrySource(Immutable); + + bool isLoaded() const final; + + void update(Immutable, + const std::vector>&, + bool needsRendering, + bool needsRelayout, + const TileParameters&) final; + + void startRender(PaintParameters&) final; + void finishRender(PaintParameters&) final; + + std::vector> getRenderTiles() final; + + std::unordered_map> + queryRenderedFeatures(const ScreenLineString& geometry, + const TransformState& transformState, + const std::vector& layers, + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) const final; + + std::vector + querySourceFeatures(const SourceQueryOptions&) const final; + + void onLowMemory() final; + void dumpDebugLogs() const final; + +private: + const style::CustomGeometrySource::Impl& impl() const; + + TilePyramid tilePyramid; +}; + +template <> +inline bool RenderSource::is() const { + return baseImpl->type == style::SourceType::CustomVector; +} + +} // namespace mbgl diff --git a/src/mbgl/style/custom_tile_loader.cpp b/src/mbgl/style/custom_tile_loader.cpp new file mode 100644 index 000000000..2fc94c9d5 --- /dev/null +++ b/src/mbgl/style/custom_tile_loader.cpp @@ -0,0 +1,114 @@ +#include + +namespace mbgl { +namespace style { + +CustomTileLoader::CustomTileLoader(const TileFunction& fetchTileFn, const TileFunction& cancelTileFn) { + fetchTileFunction = fetchTileFn; + cancelTileFunction = cancelTileFn; +} + +void CustomTileLoader::fetchTile(const OverscaledTileID& tileID, ActorRef callbackRef) { + std::lock_guard lock(dataCacheMutex); + auto cachedTileData = dataCache.find(tileID.canonical); + if (cachedTileData != dataCache.end()) { + callbackRef.invoke(&SetTileDataFunction::operator(), *(cachedTileData->second)); + } + auto tileCallbacks = tileCallbackMap.find(tileID.canonical); + if (tileCallbacks == tileCallbackMap.end()) { + auto tuple = std::make_tuple(tileID.overscaledZ, tileID.wrap, callbackRef); + tileCallbackMap.insert({ tileID.canonical, std::vector(1, tuple) }); + } else { + for (auto iter = tileCallbacks->second.begin(); iter != tileCallbacks->second.end(); iter++) { + if (std::get<0>(*iter) == tileID.overscaledZ && std::get<1>(*iter) == tileID.wrap ) { + std::get<2>(*iter) = callbackRef; + return; + } + } + tileCallbacks->second.emplace_back(std::make_tuple(tileID.overscaledZ, tileID.wrap, callbackRef)); + } + if (cachedTileData == dataCache.end()) { + invokeTileFetch(tileID.canonical); + } +} + +void CustomTileLoader::cancelTile(const OverscaledTileID& tileID) { + if (tileCallbackMap.find(tileID.canonical) != tileCallbackMap.end()) { + invokeTileCancel(tileID.canonical); + } +} + +void CustomTileLoader::removeTile(const OverscaledTileID& tileID) { + auto tileCallbacks = tileCallbackMap.find(tileID.canonical); + if (tileCallbacks == tileCallbackMap.end()) return; + for (auto iter = tileCallbacks->second.begin(); iter != tileCallbacks->second.end(); iter++) { + if (std::get<0>(*iter) == tileID.overscaledZ && std::get<1>(*iter) == tileID.wrap ) { + tileCallbacks->second.erase(iter); + invokeTileCancel(tileID.canonical); + break; + } + } + if (tileCallbacks->second.size() == 0) { + std::lock_guard lock(dataCacheMutex); + tileCallbackMap.erase(tileCallbacks); + dataCache.erase(tileID.canonical); + } +} + +void CustomTileLoader::setTileData(const CanonicalTileID& tileID, const GeoJSON& data) { + std::lock_guard lock(dataCacheMutex); + + auto iter = tileCallbackMap.find(tileID); + if (iter == tileCallbackMap.end()) return; + dataCache[tileID] = std::make_unique(std::move(data));; + for (auto tuple : iter->second) { + auto actor = std::get<2>(tuple); + actor.invoke(&SetTileDataFunction::operator(), data); + } +} + +void CustomTileLoader::invalidateTile(const CanonicalTileID& tileID) { + auto tileCallbacks = tileCallbackMap.find(tileID); + if (tileCallbacks == tileCallbackMap.end()) { return; } + for (auto iter = tileCallbacks->second.begin(); iter != tileCallbacks->second.end(); iter++) { + auto actor = std::get<2>(*iter); + actor.invoke(&SetTileDataFunction::operator(), mapbox::geojson::feature_collection()); + invokeTileCancel(tileID); + } + tileCallbackMap.erase(tileCallbacks); + { + std::lock_guard lock(dataCacheMutex); + dataCache.erase(tileID); + } +} + +void CustomTileLoader::invalidateRegion(const LatLngBounds& bounds, Range ) { + for (auto idtuple= tileCallbackMap.begin(); idtuple != tileCallbackMap.end(); idtuple++) { + const LatLngBounds tileBounds(idtuple->first); + if (tileBounds.intersects(bounds) || bounds.contains(tileBounds) || tileBounds.contains(bounds)) { + for (auto iter = idtuple->second.begin(); iter != idtuple->second.end(); iter++) { + std::lock_guard lock(dataCacheMutex); + auto actor = std::get<2>(*iter); + actor.invoke(&SetTileDataFunction::operator(), mapbox::geojson::feature_collection()); + invokeTileCancel(idtuple->first); + dataCache.erase(idtuple->first); + } + idtuple->second.clear(); + } + } +} + +void CustomTileLoader::invokeTileFetch(const CanonicalTileID& tileID) { + if (fetchTileFunction != nullptr) { + fetchTileFunction(tileID); + } +} + +void CustomTileLoader::invokeTileCancel(const CanonicalTileID& tileID) { + if (cancelTileFunction != nullptr) { + cancelTileFunction(tileID); + } +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/custom_tile_loader.hpp b/src/mbgl/style/custom_tile_loader.hpp new file mode 100644 index 000000000..fa3173ffc --- /dev/null +++ b/src/mbgl/style/custom_tile_loader.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include + +namespace mbgl { +namespace style { + +using SetTileDataFunction = std::function; + +class CustomTileLoader : private util::noncopyable { +public: + + using OverscaledIDFunctionTuple = std::tuple>; + + CustomTileLoader(const TileFunction& fetchTileFn, const TileFunction& cancelTileFn); + + void fetchTile(const OverscaledTileID& tileID, ActorRef callbackRef); + void cancelTile(const OverscaledTileID& tileID); + + void removeTile(const OverscaledTileID& tileID); + void setTileData(const CanonicalTileID& tileID, const GeoJSON& data); + + void invalidateTile(const CanonicalTileID&); + void invalidateRegion(const LatLngBounds&, Range); + +private: + void invokeTileFetch(const CanonicalTileID& tileID); + void invokeTileCancel(const CanonicalTileID& tileID); + + TileFunction fetchTileFunction; + TileFunction cancelTileFunction; + std::unordered_map> tileCallbackMap; + // Keep around a cache of tile data to serve back for wrapped and over-zooomed tiles + std::map> dataCache; + std::mutex dataCacheMutex; + +}; + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/sources/custom_geometry_source.cpp b/src/mbgl/style/sources/custom_geometry_source.cpp new file mode 100644 index 000000000..657573b1f --- /dev/null +++ b/src/mbgl/style/sources/custom_geometry_source.cpp @@ -0,0 +1,45 @@ +#include +#include +#include +#include +#include + +#include +#include + +namespace mbgl { +namespace style { + +CustomGeometrySource::CustomGeometrySource(std::string id, + const CustomGeometrySource::Options options) + : Source(makeMutable(std::move(id), options)), + mailbox(std::make_shared(*Scheduler::GetCurrent())), + loader(std::make_unique(options.fetchTileFunction, options.cancelTileFunction)) { +} + +CustomGeometrySource::~CustomGeometrySource() = default; + +const CustomGeometrySource::Impl& CustomGeometrySource::impl() const { + return static_cast(*baseImpl); +} + +void CustomGeometrySource::loadDescription(FileSource&) { + baseImpl = makeMutable(impl(), ActorRef(*loader, mailbox)); + loaded = true; +} + +void CustomGeometrySource::setTileData(const CanonicalTileID& tileID, + const GeoJSON& data) { + loader->setTileData(tileID, data); +} + +void CustomGeometrySource::invalidateTile(const CanonicalTileID& tileID) { + loader->invalidateTile(tileID); +} + +void CustomGeometrySource::invalidateRegion(const LatLngBounds& bounds) { + loader->invalidateRegion(bounds, impl().getZoomRange()); +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/sources/custom_geometry_source_impl.cpp b/src/mbgl/style/sources/custom_geometry_source_impl.cpp new file mode 100644 index 000000000..67d52bdc2 --- /dev/null +++ b/src/mbgl/style/sources/custom_geometry_source_impl.cpp @@ -0,0 +1,40 @@ +#include +#include + +namespace mbgl { +namespace style { + +CustomGeometrySource::Impl::Impl(std::string id_, + const CustomGeometrySource::Options options) + : Source::Impl(SourceType::CustomVector, std::move(id_)), + tileOptions(options.tileOptions), + zoomRange(options.zoomRange), + loaderRef({}) { +} + +CustomGeometrySource::Impl::Impl(const Impl& impl, ActorRef loaderRef_) + : Source::Impl(impl), + tileOptions(impl.tileOptions), + zoomRange(impl.zoomRange), + loaderRef(loaderRef_){ + +} + +optional CustomGeometrySource::Impl::getAttribution() const { + return {}; +} + +CustomGeometrySource::TileOptions CustomGeometrySource::Impl::getTileOptions() const { + return tileOptions; +} + +Range CustomGeometrySource::Impl::getZoomRange() const { + return zoomRange; +} + +optional> CustomGeometrySource::Impl::getTileLoader() const { + return loaderRef; +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/sources/custom_geometry_source_impl.hpp b/src/mbgl/style/sources/custom_geometry_source_impl.hpp new file mode 100644 index 000000000..ce7187202 --- /dev/null +++ b/src/mbgl/style/sources/custom_geometry_source_impl.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include +#include + +namespace mbgl { +namespace style { + +class CustomGeometrySource::Impl : public Source::Impl { +public: + Impl(std::string id, CustomGeometrySource::Options options); + Impl(const Impl&, ActorRef); + + optional getAttribution() const final; + + CustomGeometrySource::TileOptions getTileOptions() const; + Range getZoomRange() const; + optional> getTileLoader() const; + +private: + CustomGeometrySource::TileOptions tileOptions; + Range zoomRange; + optional> loaderRef; +}; + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/types.cpp b/src/mbgl/style/types.cpp index 0a1781e01..cd5e597fc 100644 --- a/src/mbgl/style/types.cpp +++ b/src/mbgl/style/types.cpp @@ -12,6 +12,7 @@ MBGL_DEFINE_ENUM(SourceType, { { SourceType::Video, "video" }, { SourceType::Annotations, "annotations" }, { SourceType::Image, "image" }, + { SourceType::CustomVector, "customvector" } }); MBGL_DEFINE_ENUM(VisibilityType, { diff --git a/src/mbgl/tile/custom_geometry_tile.cpp b/src/mbgl/tile/custom_geometry_tile.cpp new file mode 100644 index 000000000..0d0ff5be6 --- /dev/null +++ b/src/mbgl/tile/custom_geometry_tile.cpp @@ -0,0 +1,81 @@ +#include +#include +#include +#include +#include +#include + +#include + +namespace mbgl { + +CustomGeometryTile::CustomGeometryTile(const OverscaledTileID& overscaledTileID, + std::string sourceID_, + const TileParameters& parameters, + const style::CustomGeometrySource::TileOptions options_, + ActorRef loader_) + : GeometryTile(overscaledTileID, sourceID_, parameters), + necessity(TileNecessity::Optional), + options(options_), + loader(loader_), + actor(*Scheduler::GetCurrent(), std::bind(&CustomGeometryTile::setTileData, this, std::placeholders::_1)) { +} + +CustomGeometryTile::~CustomGeometryTile() { + loader.invoke(&style::CustomTileLoader::removeTile, id); +} + +void CustomGeometryTile::setTileData(const GeoJSON& geoJSON) { + + auto featureData = mapbox::geometry::feature_collection(); + if (geoJSON.is() && !geoJSON.get().empty()) { + const double scale = util::EXTENT / options.tileSize; + + mapbox::geojsonvt::TileOptions vtOptions; + vtOptions.extent = util::EXTENT; + vtOptions.buffer = std::round(scale * options.buffer); + vtOptions.tolerance = scale * options.tolerance; + featureData = mapbox::geojsonvt::geoJSONToTile(geoJSON, id.canonical.z, id.canonical.x, id.canonical.y, vtOptions).features; + } else { + setNecessity(TileNecessity::Optional); + } + setData(std::make_unique(std::move(featureData))); +} + +//Fetching tile data for custom sources is assumed to be an expensive operation. +// Only required tiles make fetchTile requests. Attempt to cancel a tile +// that is no longer required. +void CustomGeometryTile::setNecessity(TileNecessity newNecessity) { + if (newNecessity != necessity) { + necessity = newNecessity; + if (necessity == TileNecessity::Required) { + loader.invoke(&style::CustomTileLoader::fetchTile, id, actor.self()); + } else if (!isRenderable()) { + loader.invoke(&style::CustomTileLoader::cancelTile, id); + } + } +} + +void CustomGeometryTile::querySourceFeatures( + std::vector& result, + const SourceQueryOptions& queryOptions) { + + // Ignore the sourceLayer, there is only one + auto layer = getData()->getLayer({}); + + if (layer) { + auto featureCount = layer->featureCount(); + for (std::size_t i = 0; i < featureCount; i++) { + auto feature = layer->getFeature(i); + + // Apply filter, if any + if (queryOptions.filter && !(*queryOptions.filter)(*feature)) { + continue; + } + + result.push_back(convertFeature(*feature, id.canonical)); + } + } +} + +} // namespace mbgl diff --git a/src/mbgl/tile/custom_geometry_tile.hpp b/src/mbgl/tile/custom_geometry_tile.hpp new file mode 100644 index 000000000..66cc412e8 --- /dev/null +++ b/src/mbgl/tile/custom_geometry_tile.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include + +namespace mbgl { + +class TileParameters; + +class CustomGeometryTile: public GeometryTile { +public: + CustomGeometryTile(const OverscaledTileID&, + std::string sourceID, + const TileParameters&, + const style::CustomGeometrySource::TileOptions, + ActorRef loader); + ~CustomGeometryTile() override; + void setTileData(const GeoJSON& data); + + void setNecessity(TileNecessity) final; + + void querySourceFeatures( + std::vector& result, + const SourceQueryOptions&) override; + +private: + TileNecessity necessity; + const style::CustomGeometrySource::TileOptions options; + ActorRef loader; + Actor actor; +}; + +} // namespace mbgl diff --git a/test/api/custom_geometry_source.test.cpp b/test/api/custom_geometry_source.test.cpp new file mode 100644 index 000000000..83d1543a0 --- /dev/null +++ b/test/api/custom_geometry_source.test.cpp @@ -0,0 +1,71 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace mbgl; +using namespace mbgl::style; + +TEST(CustomGeometrySource, Grid) { + util::RunLoop loop; + + DefaultFileSource fileSource(":memory:", "test/fixtures/api/assets"); + auto threadPool = sharedThreadPool(); + float pixelRatio { 1 }; + HeadlessFrontend frontend { pixelRatio, fileSource, *threadPool }; + Map map(frontend, MapObserver::nullObserver(), frontend.getSize(), pixelRatio, fileSource, + *threadPool, MapMode::Static); + map.getStyle().loadJSON(util::read_file("test/fixtures/api/water.json")); + map.setLatLngZoom({ 37.8, -122.5 }, 10); + + CustomGeometrySource::Options options; + options.fetchTileFunction = [&map](const mbgl::CanonicalTileID& tileID) { + double gridSpacing = 0.1; + FeatureCollection features; + const LatLngBounds bounds(tileID); + for (double y = ceil(bounds.north() / gridSpacing) * gridSpacing; y >= floor(bounds.south() / gridSpacing) * gridSpacing; y -= gridSpacing) { + + mapbox::geojson::line_string gridLine; + gridLine.emplace_back(bounds.west(), y); + gridLine.emplace_back(bounds.east(), y); + + features.emplace_back(gridLine); + } + + for (double x = floor(bounds.west() / gridSpacing) * gridSpacing; x <= ceil(bounds.east() / gridSpacing) * gridSpacing; x += gridSpacing) { + mapbox::geojson::line_string gridLine; + gridLine.emplace_back(x, bounds.south()); + gridLine.emplace_back(x, bounds.north()); + + features.emplace_back(gridLine); + } + auto source = static_cast(map.getStyle().getSource("custom")); + if (source) { + source->setTileData(tileID, features); + } + }; + + map.getStyle().addSource(std::make_unique("custom", options)); + + auto fillLayer = std::make_unique("landcover", "mapbox"); + fillLayer->setSourceLayer("landcover"); + fillLayer->setFillColor(Color{ 1.0, 1.0, 0.0, 1.0 }); + map.getStyle().addLayer(std::move(fillLayer)); + + auto layer = std::make_unique("grid", "custom"); + layer->setLineColor(Color{ 1.0, 1.0, 1.0, 1.0 }); + map.getStyle().addLayer(std::move(layer)); + + test::checkImage("test/fixtures/custom_geometry_source/grid", frontend.render(map), 0.0006, 0.1); +} diff --git a/test/fixtures/custom_geometry_source/grid/expected.png b/test/fixtures/custom_geometry_source/grid/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..628a3b11fb92b6ef0b3901089d65f01766660c49 GIT binary patch literal 8302 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 literal 0 HcmV?d00001 diff --git a/test/style/source.test.cpp b/test/style/source.test.cpp index bee1b867b..6a2122161 100644 --- a/test/style/source.test.cpp +++ b/test/style/source.test.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -548,3 +549,39 @@ TEST(Source, ImageSourceImageUpdate) { test.run(); } + +TEST(Source, CustomGeometrySourceSetTileData) { + SourceTest test; + + CustomGeometrySource source("source", CustomGeometrySource::Options()); + source.loadDescription(test.fileSource); + + LineLayer layer("id", "source"); + layer.setSourceLayer("water"); + std::vector> layers {{ layer.baseImpl }}; + + test.renderSourceObserver.tileChanged = [&] (RenderSource& source_, const OverscaledTileID&) { + EXPECT_EQ("source", source_.baseImpl->id); + test.end(); + }; + + test.renderSourceObserver.tileError = [&] (RenderSource&, const OverscaledTileID&, std::exception_ptr) { + FAIL() << "Should never be called"; + }; + + auto renderSource = RenderSource::create(source.baseImpl); + renderSource->setObserver(&test.renderSourceObserver); + renderSource->update(source.baseImpl, + layers, + true, + true, + test.tileParameters); + + test.loop.invoke([&] () { + // Set Tile Data + source.setTileData(CanonicalTileID(0, 0, 0), GeoJSON{ FeatureCollection{} }); + }); + + test.run(); +} + diff --git a/test/tile/custom_geometry_tile.test.cpp b/test/tile/custom_geometry_tile.test.cpp new file mode 100644 index 000000000..21a3dd795 --- /dev/null +++ b/test/tile/custom_geometry_tile.test.cpp @@ -0,0 +1,130 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace mbgl; +using namespace mbgl::style; + +class CustomTileTest { +public: + FakeFileSource fileSource; + TransformState transformState; + util::RunLoop loop; + ThreadPool threadPool { 1 }; + style::Style style { loop, fileSource, 1 }; + AnnotationManager annotationManager { style }; + ImageManager imageManager; + GlyphManager glyphManager { fileSource }; + + TileParameters tileParameters { + 1.0, + MapDebugOptions(), + transformState, + threadPool, + fileSource, + MapMode::Continuous, + annotationManager, + imageManager, + glyphManager, + 0 + }; +}; + +TEST(CustomGeometryTile, InvokeFetchTile) { + CustomTileTest test; + + CircleLayer layer("circle", "source"); + + mapbox::geometry::feature_collection features; + features.push_back(mapbox::geometry::feature { + mapbox::geometry::point(0, 0) + }); + CustomTileLoader loader([&](const CanonicalTileID& tileId) { + EXPECT_EQ(tileId, CanonicalTileID(0,0,0)); + test.loop.stop(); + }, [&](const CanonicalTileID&) { + + }); + auto mb =std::make_shared(*Scheduler::GetCurrent()); + ActorRef loaderActor(loader, mb); + + CustomGeometryTile tile(OverscaledTileID(0, 0, 0), "source", test.tileParameters, CustomGeometrySource::TileOptions(), + loaderActor); + + tile.setNecessity(TileNecessity::Required); + + test.loop.run(); +} + +TEST(CustomGeometryTile, InvokeCancelTile) { + CustomTileTest test; + + CircleLayer layer("circle", "source"); + + mapbox::geometry::feature_collection features; + features.push_back(mapbox::geometry::feature { + mapbox::geometry::point(0, 0) + }); + + CustomTileLoader loader([&](const CanonicalTileID&) { }, [&](const CanonicalTileID& tileId) { + EXPECT_EQ(tileId, CanonicalTileID(0,0,0)); + test.loop.stop(); + }); + auto mb =std::make_shared(*Scheduler::GetCurrent()); + ActorRef loaderActor(loader, mb); + + CustomGeometryTile tile(OverscaledTileID(0, 0, 0), "source", test.tileParameters, CustomGeometrySource::TileOptions(), + loaderActor); + + tile.setNecessity(TileNecessity::Required); + tile.setNecessity(TileNecessity::Optional); + test.loop.run(); +} + +TEST(CustomGeometryTile, InvokeTileChanged) { + CustomTileTest test; + + CircleLayer layer("circle", "source"); + + mapbox::geometry::feature_collection features; + features.push_back(mapbox::geometry::feature { + mapbox::geometry::point(0, 0) + }); + + CustomTileLoader loader(nullptr, nullptr); + auto mb =std::make_shared(*Scheduler::GetCurrent()); + ActorRef loaderActor(loader, mb); + + CustomGeometryTile tile(OverscaledTileID(0, 0, 0), "source", test.tileParameters, CustomGeometrySource::TileOptions(), + loaderActor); + + StubTileObserver observer; + observer.tileChanged = [&] (const Tile&) { + // Once present, the bucket should never "disappear", which would cause + // flickering. + ASSERT_NE(nullptr, tile.getBucket(*layer.baseImpl)); + }; + + tile.setLayers({{ layer.baseImpl }}); + tile.setObserver(&observer); + tile.setTileData(features); + + while (!tile.isComplete()) { + test.loop.runOnce(); + } +}