summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAsheem Mamoowala <asheem.mamoowala@mapbox.com>2017-11-22 10:18:53 -0800
committerAsheem Mamoowala <asheem.mamoowala@mapbox.com>2017-11-22 13:56:38 -0800
commitacae19386129056b9425b114b01f062feecd297e (patch)
tree8b0db927fe5a78de3393f735d9317aba1034ba7e
parente3f1d8e3a0beb648ec90ac4d9baa5f27c6cf3935 (diff)
downloadqtlocation-mapboxgl-acae19386129056b9425b114b01f062feecd297e.tar.gz
[core] Custom Geometry Sources
-rw-r--r--cmake/core-files.cmake11
-rw-r--r--cmake/test-files.cmake2
-rw-r--r--include/mbgl/style/conversion/custom_geometry_source_options.hpp64
-rw-r--r--include/mbgl/style/sources/custom_geometry_source.hpp56
-rw-r--r--include/mbgl/style/sources/geojson_source.hpp2
-rw-r--r--include/mbgl/style/types.hpp3
-rw-r--r--platform/default/mbgl/storage/offline_download.cpp2
-rw-r--r--src/mbgl/renderer/render_source.cpp3
-rw-r--r--src/mbgl/renderer/sources/render_custom_geometry_source.cpp86
-rw-r--r--src/mbgl/renderer/sources/render_custom_geometry_source.hpp50
-rw-r--r--src/mbgl/style/custom_tile_loader.cpp114
-rw-r--r--src/mbgl/style/custom_tile_loader.hpp47
-rw-r--r--src/mbgl/style/sources/custom_geometry_source.cpp45
-rw-r--r--src/mbgl/style/sources/custom_geometry_source_impl.cpp40
-rw-r--r--src/mbgl/style/sources/custom_geometry_source_impl.hpp29
-rw-r--r--src/mbgl/style/types.cpp1
-rw-r--r--src/mbgl/tile/custom_geometry_tile.cpp81
-rw-r--r--src/mbgl/tile/custom_geometry_tile.hpp34
-rw-r--r--test/api/custom_geometry_source.test.cpp71
-rw-r--r--test/fixtures/custom_geometry_source/grid/expected.pngbin0 -> 8302 bytes
-rw-r--r--test/style/source.test.cpp37
-rw-r--r--test/tile/custom_geometry_tile.test.cpp130
22 files changed, 907 insertions, 1 deletions
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 <mbgl/style/conversion.hpp>
+#include <mbgl/style/sources/custom_geometry_source.hpp>
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+template <>
+struct Converter<CustomGeometrySource::Options> {
+
+ template <class V>
+ optional<CustomGeometrySource::Options> 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<uint8_t>(*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<uint8_t>(*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<uint16_t>(*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<double>(*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 <mbgl/style/source.hpp>
+#include <mbgl/util/geo.hpp>
+#include <mbgl/util/geojson.hpp>
+#include <mbgl/util/range.hpp>
+#include <mbgl/util/constants.hpp>
+#include <mbgl/actor/mailbox.hpp>
+
+namespace mbgl {
+
+class OverscaledTileID;
+class CanonicalTileID;
+
+namespace style {
+
+using TileFunction = std::function<void(const CanonicalTileID&)>;
+
+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<uint8_t> 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> mailbox;
+ std::unique_ptr<CustomTileLoader> loader;
+};
+
+template <>
+inline bool Source::is<CustomGeometrySource>() 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 <mbgl/style/source.hpp>
#include <mbgl/util/geojson.hpp>
#include <mbgl/util/optional.hpp>
+#include <mbgl/util/constants.hpp>
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 <mbgl/renderer/tile_parameters.hpp>
#include <mbgl/annotation/render_annotation_source.hpp>
#include <mbgl/renderer/sources/render_image_source.hpp>
+#include <mbgl/renderer/sources/render_custom_geometry_source.hpp>
#include <mbgl/tile/tile.hpp>
namespace mbgl {
@@ -27,6 +28,8 @@ std::unique_ptr<RenderSource> RenderSource::create(Immutable<Source::Impl> impl)
return std::make_unique<RenderAnnotationSource>(staticImmutableCast<AnnotationSource::Impl>(impl));
case SourceType::Image:
return std::make_unique<RenderImageSource>(staticImmutableCast<ImageSource::Impl>(impl));
+ case SourceType::CustomVector:
+ return std::make_unique<RenderCustomGeometrySource>(staticImmutableCast<CustomGeometrySource::Impl>(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 <mbgl/renderer/sources/render_custom_geometry_source.hpp>
+#include <mbgl/renderer/render_tile.hpp>
+#include <mbgl/renderer/paint_parameters.hpp>
+#include <mbgl/tile/custom_geometry_tile.hpp>
+
+#include <mbgl/algorithm/generate_clip_ids.hpp>
+#include <mbgl/algorithm/generate_clip_ids_impl.hpp>
+
+namespace mbgl {
+
+using namespace style;
+
+RenderCustomGeometrySource::RenderCustomGeometrySource(Immutable<style::CustomGeometrySource::Impl> impl_)
+ : RenderSource(impl_) {
+ tilePyramid.setObserver(this);
+}
+
+const style::CustomGeometrySource::Impl& RenderCustomGeometrySource::impl() const {
+ return static_cast<const style::CustomGeometrySource::Impl&>(*baseImpl);
+}
+
+bool RenderCustomGeometrySource::isLoaded() const {
+ return tilePyramid.isLoaded();
+}
+
+void RenderCustomGeometrySource::update(Immutable<style::Source::Impl> baseImpl_,
+ const std::vector<Immutable<Layer::Impl>>& 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<CustomGeometryTile>(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<std::reference_wrapper<RenderTile>> RenderCustomGeometrySource::getRenderTiles() {
+ return tilePyramid.getRenderTiles();
+}
+
+std::unordered_map<std::string, std::vector<Feature>>
+RenderCustomGeometrySource::queryRenderedFeatures(const ScreenLineString& geometry,
+ const TransformState& transformState,
+ const std::vector<const RenderLayer*>& layers,
+ const RenderedQueryOptions& options,
+ const CollisionIndex& collisionIndex) const {
+ return tilePyramid.queryRenderedFeatures(geometry, transformState, layers, options, collisionIndex);
+}
+
+std::vector<Feature> 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 <mbgl/renderer/render_source.hpp>
+#include <mbgl/renderer/tile_pyramid.hpp>
+#include <mbgl/style/sources/custom_geometry_source_impl.hpp>
+
+namespace mbgl {
+
+class RenderCustomGeometrySource : public RenderSource {
+public:
+ RenderCustomGeometrySource(Immutable<style::CustomGeometrySource::Impl>);
+
+ bool isLoaded() const final;
+
+ void update(Immutable<style::Source::Impl>,
+ const std::vector<Immutable<style::Layer::Impl>>&,
+ bool needsRendering,
+ bool needsRelayout,
+ const TileParameters&) final;
+
+ void startRender(PaintParameters&) final;
+ void finishRender(PaintParameters&) final;
+
+ std::vector<std::reference_wrapper<RenderTile>> getRenderTiles() final;
+
+ std::unordered_map<std::string, std::vector<Feature>>
+ queryRenderedFeatures(const ScreenLineString& geometry,
+ const TransformState& transformState,
+ const std::vector<const RenderLayer*>& layers,
+ const RenderedQueryOptions& options,
+ const CollisionIndex& collisionIndex) const final;
+
+ std::vector<Feature>
+ 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<RenderCustomGeometrySource>() 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 <mbgl/style/custom_tile_loader.hpp>
+
+namespace mbgl {
+namespace style {
+
+CustomTileLoader::CustomTileLoader(const TileFunction& fetchTileFn, const TileFunction& cancelTileFn) {
+ fetchTileFunction = fetchTileFn;
+ cancelTileFunction = cancelTileFn;
+}
+
+void CustomTileLoader::fetchTile(const OverscaledTileID& tileID, ActorRef<SetTileDataFunction> callbackRef) {
+ std::lock_guard<std::mutex> 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<OverscaledIDFunctionTuple>(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<std::mutex> lock(dataCacheMutex);
+ tileCallbackMap.erase(tileCallbacks);
+ dataCache.erase(tileID.canonical);
+ }
+}
+
+void CustomTileLoader::setTileData(const CanonicalTileID& tileID, const GeoJSON& data) {
+ std::lock_guard<std::mutex> lock(dataCacheMutex);
+
+ auto iter = tileCallbackMap.find(tileID);
+ if (iter == tileCallbackMap.end()) return;
+ dataCache[tileID] = std::make_unique<mapbox::geojson::geojson>(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<std::mutex> lock(dataCacheMutex);
+ dataCache.erase(tileID);
+ }
+}
+
+void CustomTileLoader::invalidateRegion(const LatLngBounds& bounds, Range<uint8_t> ) {
+ 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<std::mutex> 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 <mbgl/style/sources/custom_geometry_source.hpp>
+#include <mbgl/tile/tile_id.hpp>
+#include <mbgl/util/geo.hpp>
+#include <mbgl/util/geojson.hpp>
+#include <mbgl/actor/actor_ref.hpp>
+
+#include <map>
+#include <mutex>
+
+namespace mbgl {
+namespace style {
+
+using SetTileDataFunction = std::function<void(const GeoJSON&)>;
+
+class CustomTileLoader : private util::noncopyable {
+public:
+
+ using OverscaledIDFunctionTuple = std::tuple<uint8_t, int16_t, ActorRef<SetTileDataFunction>>;
+
+ CustomTileLoader(const TileFunction& fetchTileFn, const TileFunction& cancelTileFn);
+
+ void fetchTile(const OverscaledTileID& tileID, ActorRef<SetTileDataFunction> 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<uint8_t>);
+
+private:
+ void invokeTileFetch(const CanonicalTileID& tileID);
+ void invokeTileCancel(const CanonicalTileID& tileID);
+
+ TileFunction fetchTileFunction;
+ TileFunction cancelTileFunction;
+ std::unordered_map<CanonicalTileID, std::vector<OverscaledIDFunctionTuple>> tileCallbackMap;
+ // Keep around a cache of tile data to serve back for wrapped and over-zooomed tiles
+ std::map<CanonicalTileID, std::unique_ptr<GeoJSON>> 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 <mbgl/style/sources/custom_geometry_source.hpp>
+#include <mbgl/style/custom_tile_loader.hpp>
+#include <mbgl/style/sources/custom_geometry_source_impl.hpp>
+#include <mbgl/actor/scheduler.hpp>
+#include <mbgl/tile/tile_id.hpp>
+
+#include <tuple>
+#include <map>
+
+namespace mbgl {
+namespace style {
+
+CustomGeometrySource::CustomGeometrySource(std::string id,
+ const CustomGeometrySource::Options options)
+ : Source(makeMutable<CustomGeometrySource::Impl>(std::move(id), options)),
+ mailbox(std::make_shared<Mailbox>(*Scheduler::GetCurrent())),
+ loader(std::make_unique<CustomTileLoader>(options.fetchTileFunction, options.cancelTileFunction)) {
+}
+
+CustomGeometrySource::~CustomGeometrySource() = default;
+
+const CustomGeometrySource::Impl& CustomGeometrySource::impl() const {
+ return static_cast<const CustomGeometrySource::Impl&>(*baseImpl);
+}
+
+void CustomGeometrySource::loadDescription(FileSource&) {
+ baseImpl = makeMutable<CustomGeometrySource::Impl>(impl(), ActorRef<CustomTileLoader>(*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 <mbgl/style/sources/custom_geometry_source_impl.hpp>
+#include <mbgl/style/source_observer.hpp>
+
+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<CustomTileLoader> loaderRef_)
+ : Source::Impl(impl),
+ tileOptions(impl.tileOptions),
+ zoomRange(impl.zoomRange),
+ loaderRef(loaderRef_){
+
+}
+
+optional<std::string> CustomGeometrySource::Impl::getAttribution() const {
+ return {};
+}
+
+CustomGeometrySource::TileOptions CustomGeometrySource::Impl::getTileOptions() const {
+ return tileOptions;
+}
+
+Range<uint8_t> CustomGeometrySource::Impl::getZoomRange() const {
+ return zoomRange;
+}
+
+optional<ActorRef<CustomTileLoader>> 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 <mbgl/style/source_impl.hpp>
+#include <mbgl/style/sources/custom_geometry_source.hpp>
+#include <mbgl/style/custom_tile_loader.hpp>
+#include <mbgl/actor/actor_ref.hpp>
+
+namespace mbgl {
+namespace style {
+
+class CustomGeometrySource::Impl : public Source::Impl {
+public:
+ Impl(std::string id, CustomGeometrySource::Options options);
+ Impl(const Impl&, ActorRef<CustomTileLoader>);
+
+ optional<std::string> getAttribution() const final;
+
+ CustomGeometrySource::TileOptions getTileOptions() const;
+ Range<uint8_t> getZoomRange() const;
+ optional<ActorRef<CustomTileLoader>> getTileLoader() const;
+
+private:
+ CustomGeometrySource::TileOptions tileOptions;
+ Range<uint8_t> zoomRange;
+ optional<ActorRef<CustomTileLoader>> 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 <mbgl/tile/custom_geometry_tile.hpp>
+#include <mbgl/tile/geojson_tile_data.hpp>
+#include <mbgl/renderer/query.hpp>
+#include <mbgl/renderer/tile_parameters.hpp>
+#include <mbgl/actor/scheduler.hpp>
+#include <mbgl/style/filter_evaluator.hpp>
+
+#include <mapbox/geojsonvt.hpp>
+
+namespace mbgl {
+
+CustomGeometryTile::CustomGeometryTile(const OverscaledTileID& overscaledTileID,
+ std::string sourceID_,
+ const TileParameters& parameters,
+ const style::CustomGeometrySource::TileOptions options_,
+ ActorRef<style::CustomTileLoader> 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<int16_t>();
+ if (geoJSON.is<FeatureCollection>() && !geoJSON.get<FeatureCollection>().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<GeoJSONTileData>(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<Feature>& 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 <mbgl/tile/geometry_tile.hpp>
+#include <mbgl/util/feature.hpp>
+#include <mbgl/style/custom_tile_loader.hpp>
+
+namespace mbgl {
+
+class TileParameters;
+
+class CustomGeometryTile: public GeometryTile {
+public:
+ CustomGeometryTile(const OverscaledTileID&,
+ std::string sourceID,
+ const TileParameters&,
+ const style::CustomGeometrySource::TileOptions,
+ ActorRef<style::CustomTileLoader> loader);
+ ~CustomGeometryTile() override;
+ void setTileData(const GeoJSON& data);
+
+ void setNecessity(TileNecessity) final;
+
+ void querySourceFeatures(
+ std::vector<Feature>& result,
+ const SourceQueryOptions&) override;
+
+private:
+ TileNecessity necessity;
+ const style::CustomGeometrySource::TileOptions options;
+ ActorRef<style::CustomTileLoader> loader;
+ Actor<style::SetTileDataFunction> 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 <mbgl/test/util.hpp>
+
+#include <mbgl/gl/gl.hpp>
+#include <mbgl/map/map.hpp>
+#include <mbgl/util/shared_thread_pool.hpp>
+#include <mbgl/storage/default_file_source.hpp>
+#include <mbgl/gl/headless_frontend.hpp>
+#include <mbgl/style/style.hpp>
+#include <mbgl/style/sources/custom_geometry_source.hpp>
+#include <mbgl/style/layers/fill_layer.hpp>
+#include <mbgl/style/layers/line_layer.hpp>
+#include <mbgl/util/geojson.hpp>
+#include <mbgl/util/io.hpp>
+#include <mbgl/util/mat4.hpp>
+#include <mbgl/util/run_loop.hpp>
+
+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<CustomGeometrySource *>(map.getStyle().getSource("custom"));
+ if (source) {
+ source->setTileData(tileID, features);
+ }
+ };
+
+ map.getStyle().addSource(std::make_unique<CustomGeometrySource>("custom", options));
+
+ auto fillLayer = std::make_unique<FillLayer>("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<LineLayer>("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
--- /dev/null
+++ b/test/fixtures/custom_geometry_source/grid/expected.png
Binary files 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 <mbgl/style/sources/vector_source.hpp>
#include <mbgl/style/sources/geojson_source.hpp>
#include <mbgl/style/sources/image_source.hpp>
+#include <mbgl/style/sources/custom_geometry_source.hpp>
#include <mbgl/style/layers/raster_layer.cpp>
#include <mbgl/style/layers/line_layer.hpp>
@@ -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<Immutable<Layer::Impl>> 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 <mbgl/test/util.hpp>
+#include <mbgl/test/fake_file_source.hpp>
+#include <mbgl/test/stub_tile_observer.hpp>
+#include <mbgl/style/sources/custom_geometry_source.hpp>
+#include <mbgl/tile/custom_geometry_tile.hpp>
+#include <mbgl/style/custom_tile_loader.hpp>
+
+#include <mbgl/util/default_thread_pool.hpp>
+#include <mbgl/util/run_loop.hpp>
+#include <mbgl/map/transform.hpp>
+#include <mbgl/renderer/tile_parameters.hpp>
+#include <mbgl/style/style.hpp>
+#include <mbgl/style/layers/circle_layer.hpp>
+#include <mbgl/annotation/annotation_manager.hpp>
+#include <mbgl/renderer/image_manager.hpp>
+#include <mbgl/text/glyph_manager.hpp>
+
+#include <memory>
+
+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<double> features;
+ features.push_back(mapbox::geometry::feature<double> {
+ mapbox::geometry::point<double>(0, 0)
+ });
+ CustomTileLoader loader([&](const CanonicalTileID& tileId) {
+ EXPECT_EQ(tileId, CanonicalTileID(0,0,0));
+ test.loop.stop();
+ }, [&](const CanonicalTileID&) {
+
+ });
+ auto mb =std::make_shared<Mailbox>(*Scheduler::GetCurrent());
+ ActorRef<CustomTileLoader> 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<double> features;
+ features.push_back(mapbox::geometry::feature<double> {
+ mapbox::geometry::point<double>(0, 0)
+ });
+
+ CustomTileLoader loader([&](const CanonicalTileID&) { }, [&](const CanonicalTileID& tileId) {
+ EXPECT_EQ(tileId, CanonicalTileID(0,0,0));
+ test.loop.stop();
+ });
+ auto mb =std::make_shared<Mailbox>(*Scheduler::GetCurrent());
+ ActorRef<CustomTileLoader> 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<double> features;
+ features.push_back(mapbox::geometry::feature<double> {
+ mapbox::geometry::point<double>(0, 0)
+ });
+
+ CustomTileLoader loader(nullptr, nullptr);
+ auto mb =std::make_shared<Mailbox>(*Scheduler::GetCurrent());
+ ActorRef<CustomTileLoader> 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();
+ }
+}