From acae19386129056b9425b114b01f062feecd297e Mon Sep 17 00:00:00 2001 From: Asheem Mamoowala Date: Wed, 22 Nov 2017 10:18:53 -0800 Subject: [core] Custom Geometry Sources --- cmake/core-files.cmake | 11 ++ cmake/test-files.cmake | 2 + .../conversion/custom_geometry_source_options.hpp | 64 ++++++++++ .../mbgl/style/sources/custom_geometry_source.hpp | 56 +++++++++ include/mbgl/style/sources/geojson_source.hpp | 2 + include/mbgl/style/types.hpp | 3 +- platform/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 ++++++++ src/mbgl/style/sources/custom_geometry_source.cpp | 45 +++++++ .../style/sources/custom_geometry_source_impl.cpp | 40 +++++++ .../style/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 2af07f48c3..42c25c8ea9 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 b2ddc2b36d..43bc1210fd 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 0000000000..73b141e799 --- /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 0000000000..853526ac2d --- /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 5bdf1ef957..372e7c7a78 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 2ed95f08b8..6fe457e181 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 ff61114888..8bb16993a5 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 7723a1c7ca..6624bb7d96 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 0000000000..111f0234ed --- /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 0000000000..82e691d5c9 --- /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 0000000000..2fc94c9d5a --- /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 0000000000..fa3173ffc0 --- /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 0000000000..657573b1f4 --- /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 0000000000..67d52bdc24 --- /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 0000000000..ce7187202d --- /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 0a1781e01b..cd5e597fc0 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 0000000000..0d0ff5be61 --- /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 0000000000..66cc412e8c --- /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 0000000000..83d1543a0a --- /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 0000000000..628a3b11fb Binary files /dev/null and b/test/fixtures/custom_geometry_source/grid/expected.png differ diff --git a/test/style/source.test.cpp b/test/style/source.test.cpp index bee1b867b8..6a2122161d 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 0000000000..21a3dd7953 --- /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(); + } +} -- cgit v1.2.1