[core] Custom Geometry Sources

This commit is contained in:
Asheem Mamoowala
2017-11-22 10:18:53 -08:00
parent e3f1d8e3a0
commit acae193861
22 changed files with 907 additions and 1 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -13,7 +13,8 @@ enum class SourceType : uint8_t {
GeoJSON,
Video,
Annotations,
Image
Image,
CustomVector
};
enum class VisibilityType : bool {

View File

@@ -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;
}
}

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -12,6 +12,7 @@ MBGL_DEFINE_ENUM(SourceType, {
{ SourceType::Video, "video" },
{ SourceType::Annotations, "annotations" },
{ SourceType::Image, "image" },
{ SourceType::CustomVector, "customvector" }
});
MBGL_DEFINE_ENUM(VisibilityType, {

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@@ -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();
}

View File

@@ -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();
}
}