core: add CustomVectorSource for binary tile data delivery (#4377)
Co-authored-by: Vladyslav Odobesku <VladyslavOdobesku@intellias.com> Co-authored-by: Claude Opus 4 <noreply@anthropic.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
25e4f6acbf
commit
9ef0f1c790
@@ -378,6 +378,7 @@ list(APPEND INCLUDE_FILES
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/style/rotation.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/style/source.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/style/sources/custom_geometry_source.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/style/sources/custom_vector_source.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/style/sources/geojson_source.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/style/sources/image_source.hpp
|
||||
${PROJECT_SOURCE_DIR}/include/mbgl/style/sources/raster_dem_source.hpp
|
||||
@@ -628,6 +629,8 @@ list(APPEND SRC_FILES
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/renderer_state.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/sources/render_custom_geometry_source.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/sources/render_custom_geometry_source.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/sources/render_custom_vector_source.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/sources/render_custom_vector_source.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/sources/render_geojson_source.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/sources/render_geojson_source.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/sources/render_image_source.cpp
|
||||
@@ -699,6 +702,8 @@ list(APPEND SRC_FILES
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/conversion/transition_options.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/custom_tile_loader.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/custom_tile_loader.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/custom_vector_tile_loader.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/custom_vector_tile_loader.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/expression/assertion.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/expression/at.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/expression/boolean_operator.cpp
|
||||
@@ -815,6 +820,9 @@ list(APPEND SRC_FILES
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/sources/custom_geometry_source.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/sources/custom_geometry_source_impl.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/sources/custom_geometry_source_impl.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/sources/custom_vector_source.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/sources/custom_vector_source_impl.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/sources/custom_vector_source_impl.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/sources/geojson_source.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/sources/geojson_source_impl.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/style/sources/geojson_source_impl.hpp
|
||||
@@ -869,6 +877,8 @@ list(APPEND SRC_FILES
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/text/tagged_string.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/tile/custom_geometry_tile.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/tile/custom_geometry_tile.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/tile/custom_vector_tile.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/tile/custom_vector_tile.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/tile/geojson_tile.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/tile/geojson_tile.hpp
|
||||
${PROJECT_SOURCE_DIR}/src/mbgl/tile/geojson_tile_data.hpp
|
||||
|
||||
@@ -285,6 +285,8 @@ MLN_CORE_SOURCE = [
|
||||
"src/mbgl/renderer/renderer_state.cpp",
|
||||
"src/mbgl/renderer/sources/render_custom_geometry_source.cpp",
|
||||
"src/mbgl/renderer/sources/render_custom_geometry_source.hpp",
|
||||
"src/mbgl/renderer/sources/render_custom_vector_source.cpp",
|
||||
"src/mbgl/renderer/sources/render_custom_vector_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",
|
||||
@@ -357,6 +359,8 @@ MLN_CORE_SOURCE = [
|
||||
"src/mbgl/style/conversion/sprite.cpp",
|
||||
"src/mbgl/style/custom_tile_loader.cpp",
|
||||
"src/mbgl/style/custom_tile_loader.hpp",
|
||||
"src/mbgl/style/custom_vector_tile_loader.cpp",
|
||||
"src/mbgl/style/custom_vector_tile_loader.hpp",
|
||||
"src/mbgl/style/expression/assertion.cpp",
|
||||
"src/mbgl/style/expression/at.cpp",
|
||||
"src/mbgl/style/expression/boolean_operator.cpp",
|
||||
@@ -448,6 +452,9 @@ MLN_CORE_SOURCE = [
|
||||
"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/custom_vector_source.cpp",
|
||||
"src/mbgl/style/sources/custom_vector_source_impl.cpp",
|
||||
"src/mbgl/style/sources/custom_vector_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",
|
||||
@@ -497,6 +504,8 @@ MLN_CORE_SOURCE = [
|
||||
"src/mbgl/text/harfbuzz.hpp",
|
||||
"src/mbgl/tile/custom_geometry_tile.cpp",
|
||||
"src/mbgl/tile/custom_geometry_tile.hpp",
|
||||
"src/mbgl/tile/custom_vector_tile.cpp",
|
||||
"src/mbgl/tile/custom_vector_tile.hpp",
|
||||
"src/mbgl/tile/geojson_tile.cpp",
|
||||
"src/mbgl/tile/geojson_tile.hpp",
|
||||
"src/mbgl/tile/geojson_tile_data.hpp",
|
||||
@@ -780,6 +789,7 @@ MLN_CORE_HEADERS = [
|
||||
"include/mbgl/style/rotation.hpp",
|
||||
"include/mbgl/style/source.hpp",
|
||||
"include/mbgl/style/sources/custom_geometry_source.hpp",
|
||||
"include/mbgl/style/sources/custom_vector_source.hpp",
|
||||
"include/mbgl/style/sources/geojson_source.hpp",
|
||||
"include/mbgl/style/sources/image_source.hpp",
|
||||
"include/mbgl/style/sources/raster_dem_source.hpp",
|
||||
|
||||
184
design-proposals/2025-07-05-custom-vector-source.md
Normal file
184
design-proposals/2025-07-05-custom-vector-source.md
Normal file
@@ -0,0 +1,184 @@
|
||||
# Custom Vector Source
|
||||
|
||||
## Motivation
|
||||
|
||||
MapLibre Native currently supports two ways to provide vector tile data:
|
||||
|
||||
1. **URL-based sources** (`vector` source type) — tiles are fetched according to the protocol of the URL (`http://`,
|
||||
`file://`, etc.).
|
||||
2. **CustomGeometrySource** — accepts GeoJSON features programmatically, but internally re-encodes them into vector
|
||||
tiles, adding overhead and limiting control over the tile encoding.
|
||||
|
||||
This proposal comes from the need for an efficient and kotlin native way to asynchronously connect a tile data source
|
||||
similar to vector tile.
|
||||
CustomGeometrySource was considered, but it has some obvious drawbacks:
|
||||
|
||||
1. Tile requests are in the format of a bounding box — since it's not a tile X/Y/Z it is pretty unclear what logic it
|
||||
conforms to.
|
||||
1. Is it just a tile X/Y/Z but converted to geo bounds?
|
||||
2. Can it ever request data in a bounding box that is bigger than just one X/Y/Z tile
|
||||
3. Additional boilerplate of mapping geo bounds into tile X/Y/Z
|
||||
2. Internally CustomGeometrySource spawns a 4 thread threadpool and runs tasks one by one inside. It's a good old
|
||||
technique
|
||||
but Kotlin has things like coroutines which are a more ergonomic way to handle suspendable operations (like network
|
||||
requests).
|
||||
3. CustomGeometrySource operates over GeoJSON. So in case of a vector tiles like format it needs to be converted to
|
||||
GeoJSON and then internally it will be cut down into vector tile format back.
|
||||
|
||||
## Proposed Change
|
||||
|
||||
Introduce a new source type, `CustomVectorSource`, that lets the application deliver raw binary tile data (starting with
|
||||
MVT) directly to the rendering pipeline. The source participates in the standard tile loading lifecycle — the core
|
||||
requests tiles by `CanonicalTileID`, and the application responds asynchronously with encoded data or an error.
|
||||
|
||||
### Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Platform layer (Kotlin) │
|
||||
│ │
|
||||
│ CustomVectorTileProvider.fetchTile(z, x, y) → TileData │
|
||||
│ ↕ (JNI / platform bridge) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Core C++ layer │
|
||||
│ │
|
||||
│ CustomVectorSource │
|
||||
│ ├── Options { fetchTileFunction, cancelTileFunction, │
|
||||
│ │ zoomRange } │
|
||||
│ ├── setTileData(CanonicalTileID, shared_ptr<string>, │
|
||||
│ │ TileDataFormat) │
|
||||
│ ├── setTileError(CanonicalTileID, exception_ptr) │
|
||||
│ └── invalidateTile(CanonicalTileID) │
|
||||
│ │
|
||||
│ CustomVectorTileLoader (Actor on Scheduler::GetBackground) │
|
||||
│ └── Receives fetch/cancel requests from tiles │
|
||||
│ │
|
||||
│ CustomVectorTile (extends Tile) │
|
||||
│ └── Parses MVT data via VectorTileData, delegates to │
|
||||
│ standard GeometryTileWorker for symbol/line/fill │
|
||||
│ layout │
|
||||
│ │
|
||||
│ RenderCustomVectorSource (extends RenderTileSetSource) │
|
||||
│ └── Standard tile pyramid rendering │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Existing rendering pipeline (unchanged) │
|
||||
│ GeometryTileWorker → Buckets → Drawables → GPU │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Key Design Decisions
|
||||
|
||||
1. **Binary data, not GeoJSON.** For the Kotlin layer to send data to C++, it needs to be marshaled into some
|
||||
data format that can be passed across languages. One could come up with a new binary format, implement an encoder in
|
||||
Kotlin and a decoder in C++. But it turns out MVT is already such a format — just a binary blob that can be passed
|
||||
simply as a sized buffer. Existing infrastructure of `MVT` tile data is just reused as is.
|
||||
|
||||
2. **Actor-based concurrency.** `CustomVectorTileLoader` runs on a background thread via MapLibre's Actor system. The
|
||||
platform callback (fetch/cancel) is invoked on this thread; the platform layer is responsible for dispatching to its
|
||||
own preferred context (e.g., coroutines on Android).
|
||||
|
||||
3. **Cooperative cancellation.** When a tile is no longer needed (e.g., user panned away), the core calls
|
||||
`cancelTileFunction`. The platform layer can abort in-flight work.
|
||||
|
||||
4. **Standard tile pipeline reuse.** Once binary data arrives, it goes through the same `VectorTileData` →
|
||||
`GeometryTileWorker` path as HTTP-fetched vector tiles. No custom rendering code is needed.
|
||||
|
||||
5. **Extensible format enum.** `TileDataFormat` is an enum starting with `MVT`. Future formats (e.g., a hypothetical
|
||||
compact binary format) can be added without API changes. And obvious next candidate is `MLT` format support in
|
||||
CustomVectorSource.
|
||||
|
||||
### Threading Model
|
||||
|
||||
```
|
||||
Main thread: Source::update() → tile needs data
|
||||
↓
|
||||
Background Actor: CustomVectorTileLoader::fetchTile()
|
||||
↓
|
||||
Platform bridge: fetchTileFunction callback → platform code
|
||||
↓ (async, any thread)
|
||||
setTileData() → mailbox → CustomVectorTile::setData()
|
||||
↓
|
||||
GeometryTileWorker: parse MVT, create buckets (background pool)
|
||||
↓
|
||||
Main thread: Tile ready → render
|
||||
```
|
||||
|
||||
## API Modifications
|
||||
|
||||
### Core C++ (public header)
|
||||
|
||||
```cpp
|
||||
namespace mbgl::style {
|
||||
|
||||
enum class TileDataFormat : uint8_t { MVT = 0 };
|
||||
|
||||
using TileFunction = std::function<void(const CanonicalTileID&)>;
|
||||
|
||||
class CustomVectorSource final : public Source {
|
||||
public:
|
||||
struct Options {
|
||||
TileFunction fetchTileFunction;
|
||||
TileFunction cancelTileFunction;
|
||||
Range<uint8_t> zoomRange = {0, 18};
|
||||
};
|
||||
|
||||
CustomVectorSource(std::string id, const Options& options);
|
||||
|
||||
void setTileData(const CanonicalTileID&,
|
||||
const std::shared_ptr<const std::string>& data,
|
||||
TileDataFormat format = TileDataFormat::MVT);
|
||||
void setTileError(const CanonicalTileID&, std::exception_ptr error);
|
||||
void invalidateTile(const CanonicalTileID&);
|
||||
};
|
||||
|
||||
} // namespace mbgl::style
|
||||
```
|
||||
|
||||
### Android (Kotlin)
|
||||
|
||||
```kotlin
|
||||
interface CustomVectorTileProvider {
|
||||
suspend fun fetchTile(z: Int, x: Int, y: Int): TileData
|
||||
}
|
||||
|
||||
sealed class TileData {
|
||||
class Mvt(val data: ByteArray) : TileData()
|
||||
}
|
||||
|
||||
class CustomVectorSource(
|
||||
id: String,
|
||||
provider: CustomVectorTileProvider,
|
||||
scope: CoroutineScope,
|
||||
minZoom: Int = 0,
|
||||
maxZoom: Int = 18
|
||||
) : Source()
|
||||
```
|
||||
|
||||
The Android API uses Kotlin coroutines for async tile fetching. The `CoroutineScope` is caller-provided. Meaning
|
||||
`CustomVectorSource` can run on a threadpool but not limited to a threadpool. `CoroutineScope` abstracts it away.
|
||||
|
||||
### iOS / macOS
|
||||
|
||||
Not included in this initial implementation. The core C++ API is platform-agnostic and can be wrapped in Swift/Obj-C
|
||||
following the same pattern.
|
||||
|
||||
## Migration Plan and Compatibility
|
||||
|
||||
All changes are **additive**. No existing APIs are modified or removed. The new source type is opt-in — applications
|
||||
that don't use it are unaffected.
|
||||
|
||||
- A new `SourceType::CustomMVTVector` enum value is added to the core.
|
||||
- Existing `CustomGeometrySource` remains unchanged and is not deprecated.
|
||||
|
||||
## Rejected Alternatives
|
||||
|
||||
### Extend CustomGeometrySource to accept binary data
|
||||
|
||||
`CustomGeometrySource` is designed around GeoJSON features and internally manages geometry encoding. Bolting binary tile
|
||||
support onto it would conflate two different data models and complicate its already non-trivial implementation.
|
||||
|
||||
### Use a local file source with a custom URL scheme
|
||||
|
||||
This approach (e.g., `mbtiles://` or `custom://`) requires the application to write tile data to disk or implement a
|
||||
custom `FileSource`. It adds I/O overhead, doesn't support cooperative cancellation, and is harder to integrate with
|
||||
structured concurrency frameworks like Kotlin coroutines.
|
||||
65
include/mbgl/style/sources/custom_vector_source.hpp
Normal file
65
include/mbgl/style/sources/custom_vector_source.hpp
Normal file
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/style/source.hpp>
|
||||
#include <mbgl/util/range.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
class OverscaledTileID;
|
||||
class CanonicalTileID;
|
||||
template <class T>
|
||||
class Actor;
|
||||
|
||||
namespace style {
|
||||
|
||||
using TileFunction = std::function<void(const CanonicalTileID&)>;
|
||||
|
||||
class CustomVectorTileLoader;
|
||||
|
||||
enum class TileDataFormat : uint8_t {
|
||||
MVT = 0,
|
||||
};
|
||||
|
||||
class CustomVectorSource final : public Source {
|
||||
public:
|
||||
struct Options {
|
||||
TileFunction fetchTileFunction;
|
||||
TileFunction cancelTileFunction;
|
||||
Range<uint8_t> zoomRange = {0, 18};
|
||||
};
|
||||
|
||||
CustomVectorSource(std::string id, const Options& options);
|
||||
~CustomVectorSource() final;
|
||||
|
||||
void loadDescription(FileSource&) final;
|
||||
void setTileData(const CanonicalTileID&,
|
||||
const std::shared_ptr<const std::string>& data,
|
||||
TileDataFormat format = TileDataFormat::MVT);
|
||||
void setTileError(const CanonicalTileID&, std::exception_ptr error);
|
||||
void invalidateTile(const CanonicalTileID&);
|
||||
|
||||
class Impl;
|
||||
const Impl& impl() const;
|
||||
bool supportsLayerType(const mbgl::style::LayerTypeInfo*) const override;
|
||||
mapbox::base::WeakPtr<Source> makeWeakPtr() override { return weakFactory.makeWeakPtr(); }
|
||||
|
||||
protected:
|
||||
Mutable<Source::Impl> createMutable() const noexcept final;
|
||||
|
||||
private:
|
||||
std::unique_ptr<Actor<CustomVectorTileLoader>> loader;
|
||||
mapbox::base::WeakPtrFactory<Source> weakFactory{this};
|
||||
};
|
||||
|
||||
template <>
|
||||
inline bool Source::is<CustomVectorSource>() const {
|
||||
return getType() == SourceType::CustomMVTVector;
|
||||
}
|
||||
|
||||
} // namespace style
|
||||
} // namespace mbgl
|
||||
@@ -15,7 +15,8 @@ enum class SourceType : uint8_t {
|
||||
Video,
|
||||
Annotations,
|
||||
Image,
|
||||
CustomVector
|
||||
CustomVector,
|
||||
CustomMVTVector
|
||||
};
|
||||
|
||||
enum class VisibilityType : bool {
|
||||
|
||||
@@ -22,6 +22,7 @@ dependencies {
|
||||
implementation(libs.okhttp3)
|
||||
implementation(libs.timber)
|
||||
implementation(libs.interpolator)
|
||||
implementation(libs.kotlinxCoroutinesCore)
|
||||
|
||||
testImplementation(libs.junit)
|
||||
testImplementation(libs.mockito)
|
||||
|
||||
@@ -177,6 +177,8 @@ add_library(maplibre SHARED
|
||||
${PROJECT_SOURCE_DIR}/style/position.hpp
|
||||
${PROJECT_SOURCE_DIR}/style/sources/custom_geometry_source.cpp
|
||||
${PROJECT_SOURCE_DIR}/style/sources/custom_geometry_source.hpp
|
||||
${PROJECT_SOURCE_DIR}/style/sources/custom_vector_source.cpp
|
||||
${PROJECT_SOURCE_DIR}/style/sources/custom_vector_source.hpp
|
||||
${PROJECT_SOURCE_DIR}/style/sources/geojson_source.cpp
|
||||
${PROJECT_SOURCE_DIR}/style/sources/geojson_source.hpp
|
||||
${PROJECT_SOURCE_DIR}/style/sources/image_source.cpp
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
#include "custom_vector_source.hpp"
|
||||
#include "attach_env.hpp"
|
||||
|
||||
#include <mbgl/style/sources/custom_vector_source.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace mbgl {
|
||||
namespace android {
|
||||
|
||||
CustomVectorSource::CustomVectorSource(jni::JNIEnv& env,
|
||||
const jni::String& sourceId,
|
||||
jni::jint minZoom,
|
||||
jni::jint maxZoom)
|
||||
: Source(env,
|
||||
std::make_unique<mbgl::style::CustomVectorSource>(
|
||||
jni::Make<std::string>(env, sourceId),
|
||||
mbgl::style::CustomVectorSource::Options{
|
||||
std::bind(&CustomVectorSource::fetchTile, this, std::placeholders::_1),
|
||||
std::bind(&CustomVectorSource::cancelTile, this, std::placeholders::_1),
|
||||
{static_cast<uint8_t>(minZoom), static_cast<uint8_t>(maxZoom)}})) {}
|
||||
|
||||
CustomVectorSource::~CustomVectorSource() {
|
||||
onRemovedFromMap();
|
||||
}
|
||||
|
||||
void CustomVectorSource::fetchTile(const mbgl::CanonicalTileID& tileID) {
|
||||
android::UniqueEnv _env = android::AttachEnv();
|
||||
|
||||
static auto& javaClass = jni::Class<CustomVectorSource>::Singleton(*_env);
|
||||
static auto fetchTile = javaClass.GetMethod<void(jni::jint, jni::jint, jni::jint)>(*_env, "fetchTile");
|
||||
|
||||
if (!javaPeer) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto peer = jni::Cast(*_env, javaClass, javaPeer);
|
||||
peer.Call(*_env, fetchTile, (int)tileID.z, (int)tileID.x, (int)tileID.y);
|
||||
}
|
||||
|
||||
void CustomVectorSource::cancelTile(const mbgl::CanonicalTileID& tileID) {
|
||||
android::UniqueEnv _env = android::AttachEnv();
|
||||
|
||||
static auto& javaClass = jni::Class<CustomVectorSource>::Singleton(*_env);
|
||||
static auto cancelTile = javaClass.GetMethod<void(jni::jint, jni::jint, jni::jint)>(*_env, "cancelTile");
|
||||
|
||||
if (!javaPeer) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto peer = jni::Cast(*_env, javaClass, javaPeer);
|
||||
peer.Call(*_env, cancelTile, (int)tileID.z, (int)tileID.x, (int)tileID.y);
|
||||
}
|
||||
|
||||
void CustomVectorSource::onAddedToMap() {
|
||||
android::UniqueEnv _env = android::AttachEnv();
|
||||
|
||||
static auto& javaClass = jni::Class<CustomVectorSource>::Singleton(*_env);
|
||||
static auto onAddedToMap = javaClass.GetMethod<void()>(*_env, "onAddedToMap");
|
||||
|
||||
assert(javaPeer);
|
||||
|
||||
auto peer = jni::Cast(*_env, javaClass, javaPeer);
|
||||
peer.Call(*_env, onAddedToMap);
|
||||
}
|
||||
|
||||
void CustomVectorSource::onRemovedFromMap() {
|
||||
android::UniqueEnv _env = android::AttachEnv();
|
||||
|
||||
static auto& javaClass = jni::Class<CustomVectorSource>::Singleton(*_env);
|
||||
static auto onRemovedFromMap = javaClass.GetMethod<void()>(*_env, "onRemovedFromMap");
|
||||
|
||||
if (javaPeer) {
|
||||
auto peer = jni::Cast(*_env, javaClass, javaPeer);
|
||||
peer.Call(*_env, onRemovedFromMap);
|
||||
}
|
||||
}
|
||||
|
||||
void CustomVectorSource::setTileData(
|
||||
jni::JNIEnv& env, jni::jint z, jni::jint x, jni::jint y, const jni::Array<jni::jbyte>& jData, jni::jint format) {
|
||||
std::shared_ptr<std::string> data;
|
||||
jsize length = jData.Length(env);
|
||||
if (length > 0) {
|
||||
data = std::make_shared<std::string>(length, char());
|
||||
jni::GetArrayRegion(env, *jData, 0, length, reinterpret_cast<jbyte*>(&(*data)[0]));
|
||||
}
|
||||
|
||||
source.as<mbgl::style::CustomVectorSource>()->setTileData(
|
||||
CanonicalTileID(z, x, y), data, static_cast<mbgl::style::TileDataFormat>(format));
|
||||
}
|
||||
|
||||
void CustomVectorSource::setTileError(
|
||||
jni::JNIEnv& env, jni::jint z, jni::jint x, jni::jint y, const jni::String& message) {
|
||||
auto msg = jni::Make<std::string>(env, message);
|
||||
source.as<mbgl::style::CustomVectorSource>()->setTileError(CanonicalTileID(z, x, y),
|
||||
std::make_exception_ptr(std::runtime_error(msg)));
|
||||
}
|
||||
|
||||
void CustomVectorSource::invalidateTile(jni::JNIEnv&, jni::jint z, jni::jint x, jni::jint y) {
|
||||
source.as<mbgl::style::CustomVectorSource>()->invalidateTile(CanonicalTileID(z, x, y));
|
||||
}
|
||||
|
||||
void CustomVectorSource::addToMap(JNIEnv& env,
|
||||
const jni::Object<Source>& obj,
|
||||
mbgl::Map& map,
|
||||
AndroidRendererFrontend& frontend) {
|
||||
Source::addToMap(env, obj, map, frontend);
|
||||
onAddedToMap();
|
||||
}
|
||||
|
||||
bool CustomVectorSource::removeFromMap(JNIEnv& env, const jni::Object<Source>& source, mbgl::Map& map) {
|
||||
bool successfullyRemoved = Source::removeFromMap(env, source, map);
|
||||
if (successfullyRemoved) {
|
||||
onRemovedFromMap();
|
||||
}
|
||||
return successfullyRemoved;
|
||||
}
|
||||
|
||||
void CustomVectorSource::registerNative(jni::JNIEnv& env) {
|
||||
static auto& javaClass = jni::Class<CustomVectorSource>::Singleton(env);
|
||||
|
||||
#define METHOD(MethodPtr, name) jni::MakeNativePeerMethod<decltype(MethodPtr), (MethodPtr)>(name)
|
||||
|
||||
jni::RegisterNativePeer<CustomVectorSource>(
|
||||
env,
|
||||
javaClass,
|
||||
"nativePtr",
|
||||
jni::MakePeer<CustomVectorSource, const jni::String&, jni::jint, jni::jint>,
|
||||
"initialize",
|
||||
"finalize",
|
||||
METHOD(&CustomVectorSource::setTileData, "nativeSetTileData"),
|
||||
METHOD(&CustomVectorSource::setTileError, "nativeSetTileError"),
|
||||
METHOD(&CustomVectorSource::invalidateTile, "nativeInvalidateTile"));
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
} // namespace mbgl
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "source.hpp"
|
||||
#include <mbgl/style/sources/custom_vector_source.hpp>
|
||||
#include <mbgl/tile/tile_id.hpp>
|
||||
#include <jni/jni.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
namespace android {
|
||||
|
||||
class CustomVectorSource : public Source {
|
||||
public:
|
||||
using SuperTag = Source;
|
||||
static constexpr auto Name() { return "org/maplibre/android/style/sources/CustomVectorSource"; }
|
||||
|
||||
static void registerNative(jni::JNIEnv&);
|
||||
|
||||
CustomVectorSource(jni::JNIEnv&, const jni::String&, jni::jint minZoom, jni::jint maxZoom);
|
||||
~CustomVectorSource();
|
||||
|
||||
bool removeFromMap(JNIEnv&, const jni::Object<Source>&, mbgl::Map&) override;
|
||||
void addToMap(JNIEnv&, const jni::Object<Source>&, mbgl::Map&, AndroidRendererFrontend&) override;
|
||||
|
||||
void fetchTile(const mbgl::CanonicalTileID& tileID);
|
||||
void cancelTile(const mbgl::CanonicalTileID& tileID);
|
||||
void onAddedToMap();
|
||||
void onRemovedFromMap();
|
||||
|
||||
private:
|
||||
void setTileData(
|
||||
jni::JNIEnv&, jni::jint z, jni::jint x, jni::jint y, const jni::Array<jni::jbyte>& data, jni::jint format);
|
||||
void setTileError(jni::JNIEnv&, jni::jint z, jni::jint x, jni::jint y, const jni::String& message);
|
||||
void invalidateTile(jni::JNIEnv&, jni::jint z, jni::jint x, jni::jint y);
|
||||
};
|
||||
|
||||
} // namespace android
|
||||
} // namespace mbgl
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <string>
|
||||
|
||||
// Core Sources
|
||||
#include <mbgl/style/sources/custom_vector_source.hpp>
|
||||
#include <mbgl/style/sources/geojson_source.hpp>
|
||||
#include <mbgl/style/sources/image_source.hpp>
|
||||
#include <mbgl/style/sources/raster_source.hpp>
|
||||
@@ -29,6 +30,7 @@
|
||||
#include "unknown_source.hpp"
|
||||
#include "vector_source.hpp"
|
||||
#include "custom_geometry_source.hpp"
|
||||
#include "custom_vector_source.hpp"
|
||||
#include "raster_dem_source.hpp"
|
||||
|
||||
namespace mbgl {
|
||||
@@ -319,6 +321,7 @@ void Source::registerNative(jni::JNIEnv& env) {
|
||||
UnknownSource::registerNative(env);
|
||||
VectorSource::registerNative(env);
|
||||
CustomGeometrySource::registerNative(env);
|
||||
CustomVectorSource::registerNative(env);
|
||||
RasterDEMSource::registerNative(env);
|
||||
}
|
||||
} // namespace android
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
package org.maplibre.android.style.sources
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import androidx.annotation.UiThread
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
@UiThread
|
||||
class CustomVectorSource(
|
||||
id: String,
|
||||
private val provider: CustomVectorTileProvider,
|
||||
private val scope: CoroutineScope,
|
||||
minZoom: Int = 0,
|
||||
maxZoom: Int = 18
|
||||
) : Source() {
|
||||
|
||||
private val activeJobs = ConcurrentHashMap<TileID, Job>()
|
||||
|
||||
init {
|
||||
initialize(id, minZoom, maxZoom)
|
||||
}
|
||||
|
||||
fun setTileData(z: Int, x: Int, y: Int, data: TileData) {
|
||||
nativeSetTileData(z, x, y, data.bytes, data.formatId)
|
||||
}
|
||||
|
||||
fun invalidateTile(z: Int, x: Int, y: Int) {
|
||||
nativeInvalidateTile(z, x, y)
|
||||
}
|
||||
|
||||
@Keep
|
||||
private fun fetchTile(z: Int, x: Int, y: Int) {
|
||||
val tileId = TileID(z, x, y)
|
||||
activeJobs.remove(tileId)?.cancel()
|
||||
|
||||
val job = scope.launch(start = CoroutineStart.LAZY) {
|
||||
try {
|
||||
val tileData = provider.fetchTile(z, x, y)
|
||||
nativeSetTileData(z, x, y, tileData.bytes, tileData.formatId)
|
||||
} catch (e: CancellationException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
nativeSetTileError(z, x, y, e.message ?: "Tile fetch failed")
|
||||
} finally {
|
||||
val self = coroutineContext[Job]!!
|
||||
activeJobs.remove(tileId, self)
|
||||
}
|
||||
}
|
||||
activeJobs[tileId] = job
|
||||
job.start()
|
||||
}
|
||||
|
||||
@Keep
|
||||
private fun cancelTile(z: Int, x: Int, y: Int) {
|
||||
activeJobs.remove(TileID(z, x, y))?.cancel()
|
||||
}
|
||||
|
||||
@Keep
|
||||
private fun onAddedToMap() { /* no-op: scope is user-provided */ }
|
||||
|
||||
@Keep
|
||||
private fun onRemovedFromMap() {
|
||||
activeJobs.values.forEach { it.cancel() }
|
||||
activeJobs.clear()
|
||||
}
|
||||
|
||||
@Keep
|
||||
private external fun initialize(sourceId: String, minZoom: Int, maxZoom: Int)
|
||||
|
||||
@Keep
|
||||
private external fun nativeSetTileData(z: Int, x: Int, y: Int, data: ByteArray, format: Int)
|
||||
|
||||
@Keep
|
||||
private external fun nativeSetTileError(z: Int, x: Int, y: Int, message: String)
|
||||
|
||||
@Keep
|
||||
private external fun nativeInvalidateTile(z: Int, x: Int, y: Int)
|
||||
|
||||
@Keep
|
||||
@Throws(Throwable::class)
|
||||
protected external fun finalize()
|
||||
|
||||
internal data class TileID(val z: Int, val x: Int, val y: Int)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package org.maplibre.android.style.sources
|
||||
|
||||
interface CustomVectorTileProvider {
|
||||
suspend fun fetchTile(z: Int, x: Int, y: Int): TileData
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.maplibre.android.style.sources
|
||||
|
||||
sealed class TileData {
|
||||
class Mvt(val data: ByteArray) : TileData()
|
||||
|
||||
internal val formatId: Int get() = when (this) {
|
||||
is Mvt -> FORMAT_MVT
|
||||
}
|
||||
|
||||
internal val bytes: ByteArray get() = when (this) {
|
||||
is Mvt -> data
|
||||
}
|
||||
|
||||
companion object {
|
||||
internal const val FORMAT_MVT = 0
|
||||
}
|
||||
}
|
||||
@@ -746,6 +746,18 @@
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".activity.FeatureOverviewActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.style.CustomVectorSourceActivity"
|
||||
android:description="@string/description_custom_vector_source"
|
||||
android:exported="true"
|
||||
android:label="@string/activity_custom_vector_source">
|
||||
<meta-data
|
||||
android:name="@string/category"
|
||||
android:value="@string/category_style" />
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".activity.FeatureOverviewActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activity.style.NoStyleActivity"
|
||||
android:description="@string/description_no_style"
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
package org.maplibre.android.testapp.activity.style
|
||||
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import org.maplibre.android.maps.MapView
|
||||
import org.maplibre.android.maps.MapLibreMap
|
||||
import org.maplibre.android.maps.OnMapReadyCallback
|
||||
import org.maplibre.android.maps.Style
|
||||
import org.maplibre.android.style.layers.LineLayer
|
||||
import org.maplibre.android.style.layers.PropertyFactory
|
||||
import org.maplibre.android.style.sources.CustomVectorSource
|
||||
import org.maplibre.android.style.sources.TileData
|
||||
import org.maplibre.android.style.sources.CustomVectorTileProvider
|
||||
import org.maplibre.android.testapp.R
|
||||
import org.maplibre.android.testapp.styles.TestStyles
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
/**
|
||||
* Demo activity that uses CustomVectorSource with a fake MVT tile provider.
|
||||
* Generates a diagonal cross pattern per tile as synthetic MVT data.
|
||||
* Toggle the source on/off with the FAB.
|
||||
*/
|
||||
class CustomVectorSourceActivity : AppCompatActivity(), OnMapReadyCallback {
|
||||
|
||||
private lateinit var mapView: MapView
|
||||
private var maplibreMap: MapLibreMap? = null
|
||||
private var sourceActive = false
|
||||
private val tileScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_custom_vector_source)
|
||||
|
||||
mapView = findViewById(R.id.mapView)
|
||||
mapView.onCreate(savedInstanceState)
|
||||
mapView.getMapAsync(this)
|
||||
|
||||
findViewById<FloatingActionButton>(R.id.fabToggle).setOnClickListener {
|
||||
toggleSource()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMapReady(map: MapLibreMap) {
|
||||
maplibreMap = map
|
||||
map.setStyle(Style.Builder().fromUri(TestStyles.DEMOTILES))
|
||||
}
|
||||
|
||||
private fun toggleSource() {
|
||||
val map = maplibreMap ?: return
|
||||
val style = map.style ?: return
|
||||
|
||||
if (sourceActive) {
|
||||
style.removeLayer(LAYER_ID)
|
||||
style.removeSource(SOURCE_ID)
|
||||
sourceActive = false
|
||||
Toast.makeText(this, "CustomVectorSource removed", Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
val source = CustomVectorSource(SOURCE_ID, DiagonalTileProvider(), tileScope, minZoom = 0, maxZoom = 14)
|
||||
val layer = LineLayer(LAYER_ID, SOURCE_ID).apply {
|
||||
setSourceLayer(SOURCE_LAYER_NAME)
|
||||
setProperties(
|
||||
PropertyFactory.lineColor(Color.RED),
|
||||
PropertyFactory.lineWidth(2f),
|
||||
PropertyFactory.lineOpacity(0.8f)
|
||||
)
|
||||
}
|
||||
style.addSource(source)
|
||||
style.addLayer(layer)
|
||||
sourceActive = true
|
||||
Toast.makeText(this, "CustomVectorSource added", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A fake tile provider that generates MVT tiles containing diagonal lines.
|
||||
* Simulates async work with a small delay.
|
||||
*/
|
||||
private class DiagonalTileProvider : CustomVectorTileProvider {
|
||||
override suspend fun fetchTile(z: Int, x: Int, y: Int): TileData {
|
||||
// Simulate network latency
|
||||
delay(50)
|
||||
val mvtBytes = buildDiagonalMvtTile(z, x, y)
|
||||
return TileData.Mvt(mvtBytes)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() { super.onStart(); mapView.onStart() }
|
||||
override fun onResume() { super.onResume(); mapView.onResume() }
|
||||
override fun onPause() { super.onPause(); mapView.onPause() }
|
||||
override fun onStop() { super.onStop(); mapView.onStop() }
|
||||
override fun onDestroy() { tileScope.cancel(); super.onDestroy(); mapView.onDestroy() }
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
mapView.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SOURCE_ID = "custom-vector-source"
|
||||
const val LAYER_ID = "custom-vector-layer"
|
||||
const val SOURCE_LAYER_NAME = "diagonal"
|
||||
private const val EXTENT = 4096
|
||||
|
||||
/**
|
||||
* Builds a minimal MVT tile with two diagonal lines forming an X.
|
||||
* MVT protobuf encoding done by hand (no protobuf library needed).
|
||||
*/
|
||||
private fun buildDiagonalMvtTile(z: Int, x: Int, y: Int): ByteArray {
|
||||
// Geometry: two lines forming an X across the tile
|
||||
// Line 1: (0,0) -> (4096,4096)
|
||||
// Line 2: (4096,0) -> (0,4096)
|
||||
val geom1 = encodeMvtGeometry(
|
||||
listOf(Pair(0, 0), Pair(EXTENT, EXTENT))
|
||||
)
|
||||
val geom2 = encodeMvtGeometry(
|
||||
listOf(Pair(EXTENT, 0), Pair(0, EXTENT))
|
||||
)
|
||||
|
||||
// Also add a border rectangle
|
||||
val border = encodeMvtGeometry(
|
||||
listOf(Pair(0, 0), Pair(EXTENT, 0), Pair(EXTENT, EXTENT), Pair(0, EXTENT), Pair(0, 0))
|
||||
)
|
||||
|
||||
val feature1 = encodeMvtFeature(id = 1, geometry = geom1, geomType = 2)
|
||||
val feature2 = encodeMvtFeature(id = 2, geometry = geom2, geomType = 2)
|
||||
val feature3 = encodeMvtFeature(id = 3, geometry = border, geomType = 2)
|
||||
|
||||
val layer = encodeMvtLayer(
|
||||
name = SOURCE_LAYER_NAME,
|
||||
extent = EXTENT,
|
||||
features = listOf(feature1, feature2, feature3)
|
||||
)
|
||||
|
||||
// Tile: repeated Layer layers = 3;
|
||||
val tile = ByteArrayOutputStream()
|
||||
writeTag(tile, 3, 2) // field 3, wire type 2 (length-delimited)
|
||||
writeBytes(tile, layer)
|
||||
return tile.toByteArray()
|
||||
}
|
||||
|
||||
private fun encodeMvtGeometry(points: List<Pair<Int, Int>>): ByteArray {
|
||||
val commands = mutableListOf<Int>()
|
||||
for (i in points.indices) {
|
||||
if (i == 0) {
|
||||
// MoveTo command: id=1, count=1
|
||||
commands.add(commandInteger(1, 1))
|
||||
commands.add(zigzag(points[0].first))
|
||||
commands.add(zigzag(points[0].second))
|
||||
} else {
|
||||
if (i == 1) {
|
||||
// LineTo command: id=2, count=remaining points
|
||||
commands.add(commandInteger(2, points.size - 1))
|
||||
}
|
||||
val dx = points[i].first - points[i - 1].first
|
||||
val dy = points[i].second - points[i - 1].second
|
||||
commands.add(zigzag(dx))
|
||||
commands.add(zigzag(dy))
|
||||
}
|
||||
}
|
||||
return commands.flatMap { encodeVarint(it) }.toByteArray()
|
||||
}
|
||||
|
||||
private fun encodeMvtFeature(id: Long, geometry: ByteArray, geomType: Int): ByteArray {
|
||||
val out = ByteArrayOutputStream()
|
||||
// optional uint64 id = 1
|
||||
writeTag(out, 1, 0)
|
||||
writeVarint(out, id)
|
||||
// optional GeomType type = 3
|
||||
writeTag(out, 3, 0)
|
||||
writeVarint(out, geomType.toLong())
|
||||
// repeated uint32 geometry = 4 (packed)
|
||||
writeTag(out, 4, 2)
|
||||
writeBytes(out, geometry)
|
||||
return out.toByteArray()
|
||||
}
|
||||
|
||||
private fun encodeMvtLayer(name: String, extent: Int, features: List<ByteArray>): ByteArray {
|
||||
val out = ByteArrayOutputStream()
|
||||
// required uint32 version = 15
|
||||
writeTag(out, 15, 0)
|
||||
writeVarint(out, 2)
|
||||
// required string name = 1
|
||||
writeTag(out, 1, 2)
|
||||
writeBytes(out, name.toByteArray(Charsets.UTF_8))
|
||||
// repeated Feature features = 2
|
||||
for (feature in features) {
|
||||
writeTag(out, 2, 2)
|
||||
writeBytes(out, feature)
|
||||
}
|
||||
// optional uint32 extent = 5
|
||||
writeTag(out, 5, 0)
|
||||
writeVarint(out, extent.toLong())
|
||||
return out.toByteArray()
|
||||
}
|
||||
|
||||
private fun commandInteger(id: Int, count: Int): Int = (id and 0x7) or (count shl 3)
|
||||
|
||||
private fun zigzag(value: Int): Int = (value shl 1) xor (value shr 31)
|
||||
|
||||
private fun encodeVarint(value: Int): List<Byte> {
|
||||
val bytes = mutableListOf<Byte>()
|
||||
var v = value.toLong() and 0xFFFFFFFFL
|
||||
while (v > 0x7F) {
|
||||
bytes.add(((v and 0x7F) or 0x80).toByte())
|
||||
v = v ushr 7
|
||||
}
|
||||
bytes.add((v and 0x7F).toByte())
|
||||
return bytes
|
||||
}
|
||||
|
||||
private fun writeTag(out: ByteArrayOutputStream, fieldNumber: Int, wireType: Int) {
|
||||
writeVarint(out, ((fieldNumber shl 3) or wireType).toLong())
|
||||
}
|
||||
|
||||
private fun writeVarint(out: ByteArrayOutputStream, value: Long) {
|
||||
var v = value
|
||||
while (v > 0x7F) {
|
||||
out.write(((v and 0x7F) or 0x80).toInt())
|
||||
v = v ushr 7
|
||||
}
|
||||
out.write((v and 0x7F).toInt())
|
||||
}
|
||||
|
||||
private fun writeBytes(out: ByteArrayOutputStream, data: ByteArray) {
|
||||
writeVarint(out, data.size.toLong())
|
||||
out.write(data)
|
||||
}
|
||||
|
||||
private fun List<Byte>.toByteArray(): ByteArray = ByteArray(size) { this[it] }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<org.maplibre.android.maps.MapView
|
||||
android:id="@id/mapView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:maplibre_cameraTargetLat="48.85"
|
||||
app:maplibre_cameraTargetLng="2.35"
|
||||
app:maplibre_cameraZoom="4"/>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fabToggle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_margin="16dp"
|
||||
android:src="@android:drawable/ic_input_add"
|
||||
android:contentDescription="Toggle custom source"/>
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -83,6 +83,7 @@
|
||||
<string name="description_surfaceview_resize">Resize a map rendered on a SurfaceView</string>
|
||||
<string name="description_overlay">Blend an overlay on a map</string>
|
||||
<string name="description_grid_source">Example Custom Geometry Source</string>
|
||||
<string name="description_custom_vector_source">CustomVectorSource with synthetic MVT tiles</string>
|
||||
<string name="description_no_style">Load a map without providing a style URI or JSON</string>
|
||||
<string name="description_vector_tile">Load MVT data</string>
|
||||
<string name="description_pmtiles">Load PMTiles data</string>
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
<string name="activity_surfaceview_resize">SurfaceView resize</string>
|
||||
<string name="activity_overlay">Map overlay</string>
|
||||
<string name="activity_grid_source">Grid Source</string>
|
||||
<string name="activity_custom_vector_source">Custom Vector Source</string>
|
||||
<string name="activity_no_style">No Style URI/JSON</string>
|
||||
<string name="activity_vector_tile">Vector Tile source</string>
|
||||
<string name="activity_pmtiles">PMTiles</string>
|
||||
|
||||
@@ -219,6 +219,7 @@ OfflineRegionStatus OfflineDownload::getStatus() const {
|
||||
case SourceType::Video:
|
||||
case SourceType::Annotations:
|
||||
case SourceType::CustomVector:
|
||||
case SourceType::CustomMVTVector:
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -333,6 +334,7 @@ void OfflineDownload::activateDownload() {
|
||||
case SourceType::Video:
|
||||
case SourceType::Annotations:
|
||||
case SourceType::CustomVector:
|
||||
case SourceType::CustomMVTVector:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <mbgl/layermanager/layer_manager.hpp>
|
||||
#include <mbgl/renderer/render_source_observer.hpp>
|
||||
#include <mbgl/renderer/sources/render_custom_geometry_source.hpp>
|
||||
#include <mbgl/renderer/sources/render_custom_vector_source.hpp>
|
||||
#include <mbgl/renderer/sources/render_geojson_source.hpp>
|
||||
#include <mbgl/renderer/sources/render_image_source.hpp>
|
||||
#include <mbgl/renderer/sources/render_raster_dem_source.hpp>
|
||||
@@ -49,6 +50,9 @@ std::unique_ptr<RenderSource> RenderSource::create(const Immutable<Source::Impl>
|
||||
case SourceType::CustomVector:
|
||||
return std::make_unique<RenderCustomGeometrySource>(staticImmutableCast<CustomGeometrySource::Impl>(impl),
|
||||
threadPool);
|
||||
case SourceType::CustomMVTVector:
|
||||
return std::make_unique<RenderCustomVectorSource>(staticImmutableCast<CustomVectorSource::Impl>(impl),
|
||||
threadPool);
|
||||
}
|
||||
|
||||
// Not reachable, but placate GCC.
|
||||
|
||||
57
src/mbgl/renderer/sources/render_custom_vector_source.cpp
Normal file
57
src/mbgl/renderer/sources/render_custom_vector_source.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#include <mbgl/renderer/sources/render_custom_vector_source.hpp>
|
||||
#include <mbgl/renderer/render_tile.hpp>
|
||||
#include <mbgl/renderer/paint_parameters.hpp>
|
||||
#include <mbgl/tile/custom_vector_tile.hpp>
|
||||
#include <mbgl/util/constants.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
using namespace style;
|
||||
|
||||
RenderCustomVectorSource::RenderCustomVectorSource(Immutable<style::CustomVectorSource::Impl> impl_,
|
||||
const TaggedScheduler& threadPool_)
|
||||
: RenderTileSource(std::move(impl_), threadPool_) {
|
||||
tilePyramid.setObserver(this);
|
||||
}
|
||||
|
||||
const style::CustomVectorSource::Impl& RenderCustomVectorSource::impl() const {
|
||||
return static_cast<const style::CustomVectorSource::Impl&>(*baseImpl);
|
||||
}
|
||||
|
||||
void RenderCustomVectorSource::update(Immutable<style::Source::Impl> baseImpl_,
|
||||
const std::vector<Immutable<style::LayerProperties>>& layers,
|
||||
const bool needsRendering,
|
||||
const bool needsRelayout,
|
||||
const TileParameters& parameters) {
|
||||
if (baseImpl != baseImpl_) {
|
||||
std::swap(baseImpl, baseImpl_);
|
||||
|
||||
auto newImpl = staticImmutableCast<style::CustomVectorSource::Impl>(baseImpl);
|
||||
auto currentImpl = staticImmutableCast<style::CustomVectorSource::Impl>(baseImpl_);
|
||||
if (*newImpl != *currentImpl) {
|
||||
tilePyramid.clearAll();
|
||||
}
|
||||
}
|
||||
|
||||
enabled = needsRendering;
|
||||
|
||||
auto tileLoader = impl().getTileLoader();
|
||||
if (!tileLoader) {
|
||||
return;
|
||||
}
|
||||
|
||||
tilePyramid.update(layers,
|
||||
needsRendering,
|
||||
needsRelayout,
|
||||
parameters,
|
||||
*baseImpl,
|
||||
util::tileSize_I,
|
||||
impl().getZoomRange(),
|
||||
{},
|
||||
[&](const OverscaledTileID& tileID, TileObserver* observer_) {
|
||||
return std::make_unique<CustomVectorTile>(
|
||||
tileID, impl().id, parameters, *tileLoader, observer_);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace mbgl
|
||||
22
src/mbgl/renderer/sources/render_custom_vector_source.hpp
Normal file
22
src/mbgl/renderer/sources/render_custom_vector_source.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/renderer/sources/render_tile_source.hpp>
|
||||
#include <mbgl/style/sources/custom_vector_source_impl.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
class RenderCustomVectorSource final : public RenderTileSource {
|
||||
public:
|
||||
explicit RenderCustomVectorSource(Immutable<style::CustomVectorSource::Impl>, const TaggedScheduler&);
|
||||
|
||||
void update(Immutable<style::Source::Impl>,
|
||||
const std::vector<Immutable<style::LayerProperties>>&,
|
||||
bool needsRendering,
|
||||
bool needsRelayout,
|
||||
const TileParameters&) override;
|
||||
|
||||
private:
|
||||
const style::CustomVectorSource::Impl& impl() const;
|
||||
};
|
||||
|
||||
} // namespace mbgl
|
||||
114
src/mbgl/style/custom_vector_tile_loader.cpp
Normal file
114
src/mbgl/style/custom_vector_tile_loader.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
#include <mbgl/style/custom_vector_tile_loader.hpp>
|
||||
#include <mbgl/tile/custom_vector_tile.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
namespace style {
|
||||
|
||||
CustomVectorTileLoader::CustomVectorTileLoader(const TileFunction& fetchTileFn, const TileFunction& cancelTileFn)
|
||||
: fetchTileFunction(fetchTileFn),
|
||||
cancelTileFunction(cancelTileFn) {}
|
||||
|
||||
void CustomVectorTileLoader::fetchTile(const OverscaledTileID& tileID, const ActorRef<CustomVectorTile>& tileRef) {
|
||||
std::scoped_lock guard(dataMutex);
|
||||
auto cachedTileData = dataCache.find(tileID.canonical);
|
||||
if (cachedTileData != dataCache.end()) {
|
||||
tileRef.invoke(&CustomVectorTile::setTileData, cachedTileData->second.first, cachedTileData->second.second);
|
||||
}
|
||||
auto tileCallbacks = tileCallbackMap.find(tileID.canonical);
|
||||
if (tileCallbacks == tileCallbackMap.end()) {
|
||||
auto tuple = std::make_tuple(tileID.overscaledZ, tileID.wrap, tileRef);
|
||||
tileCallbackMap.insert({tileID.canonical, std::vector<OverscaledIDFunctionTuple>(1, tuple)});
|
||||
} else {
|
||||
for (auto& iter : tileCallbacks->second) {
|
||||
if (std::get<0>(iter) == tileID.overscaledZ && std::get<1>(iter) == tileID.wrap) {
|
||||
std::get<2>(iter) = tileRef;
|
||||
return;
|
||||
}
|
||||
}
|
||||
tileCallbacks->second.emplace_back(std::make_tuple(tileID.overscaledZ, tileID.wrap, tileRef));
|
||||
}
|
||||
if (cachedTileData == dataCache.end()) {
|
||||
invokeTileFetch(tileID.canonical);
|
||||
}
|
||||
}
|
||||
|
||||
void CustomVectorTileLoader::cancelTile(const OverscaledTileID& tileID) {
|
||||
std::scoped_lock guard(dataMutex);
|
||||
if (tileCallbackMap.contains(tileID.canonical)) {
|
||||
invokeTileCancel(tileID.canonical);
|
||||
// Erase so a subsequent fetchTile for the same tile re-issues the fetch.
|
||||
// Intentionally diverges from CustomTileLoader which omits this — that is a
|
||||
// latent bug there that needs a separate cleanup.
|
||||
tileCallbackMap.erase(tileID.canonical);
|
||||
}
|
||||
}
|
||||
|
||||
void CustomVectorTileLoader::removeTile(const OverscaledTileID& tileID) {
|
||||
std::scoped_lock guard(dataMutex);
|
||||
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.empty()) {
|
||||
tileCallbackMap.erase(tileCallbacks);
|
||||
dataCache.erase(tileID.canonical);
|
||||
}
|
||||
}
|
||||
|
||||
void CustomVectorTileLoader::setTileData(const CanonicalTileID& tileID,
|
||||
const std::shared_ptr<const std::string>& data,
|
||||
TileDataFormat format) {
|
||||
std::scoped_lock guard(dataMutex);
|
||||
auto iter = tileCallbackMap.find(tileID);
|
||||
if (iter == tileCallbackMap.end()) return;
|
||||
for (auto& tuple : iter->second) {
|
||||
auto actor = std::get<2>(tuple);
|
||||
actor.invoke(&CustomVectorTile::setTileData, data, format);
|
||||
}
|
||||
dataCache[tileID] = std::make_pair(data, format);
|
||||
}
|
||||
|
||||
void CustomVectorTileLoader::setTileError(const CanonicalTileID& tileID, std::exception_ptr error) {
|
||||
std::scoped_lock guard(dataMutex);
|
||||
auto iter = tileCallbackMap.find(tileID);
|
||||
if (iter == tileCallbackMap.end()) return;
|
||||
for (auto& tuple : iter->second) {
|
||||
auto actor = std::get<2>(tuple);
|
||||
actor.invoke(&CustomVectorTile::setTileError, error);
|
||||
}
|
||||
tileCallbackMap.erase(iter);
|
||||
dataCache.erase(tileID);
|
||||
}
|
||||
|
||||
void CustomVectorTileLoader::invalidateTile(const CanonicalTileID& tileID) {
|
||||
std::scoped_lock guard(dataMutex);
|
||||
auto tileCallbacks = tileCallbackMap.find(tileID);
|
||||
if (tileCallbacks == tileCallbackMap.end()) return;
|
||||
for (auto& iter : tileCallbacks->second) {
|
||||
auto actor = std::get<2>(iter);
|
||||
actor.invoke(&CustomVectorTile::invalidateTileData);
|
||||
invokeTileCancel(tileID);
|
||||
}
|
||||
tileCallbackMap.erase(tileCallbacks);
|
||||
dataCache.erase(tileID);
|
||||
}
|
||||
|
||||
void CustomVectorTileLoader::invokeTileFetch(const CanonicalTileID& tileID) {
|
||||
if (fetchTileFunction != nullptr) {
|
||||
fetchTileFunction(tileID);
|
||||
}
|
||||
}
|
||||
|
||||
void CustomVectorTileLoader::invokeTileCancel(const CanonicalTileID& tileID) {
|
||||
if (cancelTileFunction != nullptr) {
|
||||
cancelTileFunction(tileID);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace style
|
||||
} // namespace mbgl
|
||||
52
src/mbgl/style/custom_vector_tile_loader.hpp
Normal file
52
src/mbgl/style/custom_vector_tile_loader.hpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/actor/actor_ref.hpp>
|
||||
#include <mbgl/style/sources/custom_vector_source.hpp>
|
||||
#include <mbgl/tile/tile_id.hpp>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
class CustomVectorTile;
|
||||
|
||||
namespace style {
|
||||
|
||||
class CustomVectorTileLoader {
|
||||
public:
|
||||
CustomVectorTileLoader(const CustomVectorTileLoader&) = delete;
|
||||
CustomVectorTileLoader& operator=(const CustomVectorTileLoader&) = delete;
|
||||
|
||||
using OverscaledIDFunctionTuple = std::tuple<uint8_t, int16_t, ActorRef<CustomVectorTile>>;
|
||||
|
||||
CustomVectorTileLoader(const TileFunction& fetchTileFn, const TileFunction& cancelTileFn);
|
||||
|
||||
void fetchTile(const OverscaledTileID& tileID, const ActorRef<CustomVectorTile>& tileRef);
|
||||
void cancelTile(const OverscaledTileID& tileID);
|
||||
void removeTile(const OverscaledTileID& tileID);
|
||||
|
||||
void setTileData(const CanonicalTileID& tileID,
|
||||
const std::shared_ptr<const std::string>& data,
|
||||
TileDataFormat format);
|
||||
void setTileError(const CanonicalTileID& tileID, std::exception_ptr error);
|
||||
void invalidateTile(const CanonicalTileID&);
|
||||
|
||||
private:
|
||||
void invokeTileFetch(const CanonicalTileID& tileID);
|
||||
void invokeTileCancel(const CanonicalTileID& tileID);
|
||||
|
||||
TileFunction fetchTileFunction;
|
||||
TileFunction cancelTileFunction;
|
||||
std::unordered_map<CanonicalTileID, std::vector<OverscaledIDFunctionTuple>> tileCallbackMap;
|
||||
std::map<CanonicalTileID, std::pair<std::shared_ptr<const std::string>, TileDataFormat>> dataCache;
|
||||
std::mutex dataMutex;
|
||||
};
|
||||
|
||||
} // namespace style
|
||||
} // namespace mbgl
|
||||
56
src/mbgl/style/sources/custom_vector_source.cpp
Normal file
56
src/mbgl/style/sources/custom_vector_source.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#include <mbgl/actor/actor.hpp>
|
||||
#include <mbgl/actor/scheduler.hpp>
|
||||
#include <mbgl/style/custom_vector_tile_loader.hpp>
|
||||
#include <mbgl/style/layer.hpp>
|
||||
#include <mbgl/style/source_observer.hpp>
|
||||
#include <mbgl/style/sources/custom_vector_source.hpp>
|
||||
#include <mbgl/style/sources/custom_vector_source_impl.hpp>
|
||||
#include <mbgl/tile/tile.hpp>
|
||||
#include <mbgl/tile/tile_id.hpp>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace mbgl {
|
||||
namespace style {
|
||||
|
||||
CustomVectorSource::CustomVectorSource(std::string id, const CustomVectorSource::Options& options)
|
||||
: Source(makeMutable<CustomVectorSource::Impl>(std::move(id), options)),
|
||||
loader(std::make_unique<Actor<CustomVectorTileLoader>>(
|
||||
Scheduler::GetBackground(), options.fetchTileFunction, options.cancelTileFunction)) {}
|
||||
|
||||
CustomVectorSource::~CustomVectorSource() = default;
|
||||
|
||||
const CustomVectorSource::Impl& CustomVectorSource::impl() const {
|
||||
return static_cast<const CustomVectorSource::Impl&>(*baseImpl);
|
||||
}
|
||||
|
||||
void CustomVectorSource::loadDescription(FileSource&) {
|
||||
baseImpl = makeMutable<Impl>(impl(), loader->self());
|
||||
loaded = true;
|
||||
observer->onSourceLoaded(*this);
|
||||
}
|
||||
|
||||
bool CustomVectorSource::supportsLayerType(const mbgl::style::LayerTypeInfo* info) const {
|
||||
return mbgl::underlying_type(Tile::Kind::Geometry) == mbgl::underlying_type(info->tileKind);
|
||||
}
|
||||
|
||||
void CustomVectorSource::setTileData(const CanonicalTileID& tileID,
|
||||
const std::shared_ptr<const std::string>& data,
|
||||
TileDataFormat format) {
|
||||
loader->self().invoke(&CustomVectorTileLoader::setTileData, tileID, data, format);
|
||||
}
|
||||
|
||||
void CustomVectorSource::setTileError(const CanonicalTileID& tileID, std::exception_ptr error) {
|
||||
loader->self().invoke(&CustomVectorTileLoader::setTileError, tileID, error);
|
||||
}
|
||||
|
||||
void CustomVectorSource::invalidateTile(const CanonicalTileID& tileID) {
|
||||
loader->self().invoke(&CustomVectorTileLoader::invalidateTile, tileID);
|
||||
}
|
||||
|
||||
Mutable<Source::Impl> CustomVectorSource::createMutable() const noexcept {
|
||||
return staticMutableCast<Source::Impl>(makeMutable<Impl>(impl()));
|
||||
}
|
||||
|
||||
} // namespace style
|
||||
} // namespace mbgl
|
||||
33
src/mbgl/style/sources/custom_vector_source_impl.cpp
Normal file
33
src/mbgl/style/sources/custom_vector_source_impl.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#include <mbgl/style/sources/custom_vector_source_impl.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
namespace style {
|
||||
|
||||
CustomVectorSource::Impl::Impl(std::string id_, const CustomVectorSource::Options& options)
|
||||
: Source::Impl(SourceType::CustomMVTVector, std::move(id_)),
|
||||
zoomRange(options.zoomRange),
|
||||
loaderRef({}) {}
|
||||
|
||||
CustomVectorSource::Impl::Impl(const Impl& impl, const ActorRef<CustomVectorTileLoader>& loaderRef_)
|
||||
: Source::Impl(impl),
|
||||
zoomRange(impl.zoomRange),
|
||||
loaderRef(loaderRef_) {}
|
||||
|
||||
bool CustomVectorSource::Impl::operator!=(const Impl& other) const noexcept {
|
||||
return zoomRange != other.zoomRange || bool(loaderRef) != bool(other.loaderRef);
|
||||
}
|
||||
|
||||
std::optional<std::string> CustomVectorSource::Impl::getAttribution() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
Range<uint8_t> CustomVectorSource::Impl::getZoomRange() const {
|
||||
return zoomRange;
|
||||
}
|
||||
|
||||
std::optional<ActorRef<CustomVectorTileLoader>> CustomVectorSource::Impl::getTileLoader() const {
|
||||
return loaderRef;
|
||||
}
|
||||
|
||||
} // namespace style
|
||||
} // namespace mbgl
|
||||
28
src/mbgl/style/sources/custom_vector_source_impl.hpp
Normal file
28
src/mbgl/style/sources/custom_vector_source_impl.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/style/source_impl.hpp>
|
||||
#include <mbgl/style/sources/custom_vector_source.hpp>
|
||||
#include <mbgl/style/custom_vector_tile_loader.hpp>
|
||||
#include <mbgl/actor/actor_ref.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
namespace style {
|
||||
|
||||
class CustomVectorSource::Impl : public Source::Impl {
|
||||
public:
|
||||
Impl(std::string id, const CustomVectorSource::Options& options);
|
||||
Impl(const Impl&, const ActorRef<CustomVectorTileLoader>&);
|
||||
|
||||
std::optional<std::string> getAttribution() const final;
|
||||
|
||||
Range<uint8_t> getZoomRange() const;
|
||||
std::optional<ActorRef<CustomVectorTileLoader>> getTileLoader() const;
|
||||
bool operator!=(const Impl&) const noexcept;
|
||||
|
||||
private:
|
||||
Range<uint8_t> zoomRange;
|
||||
std::optional<ActorRef<CustomVectorTileLoader>> loaderRef;
|
||||
};
|
||||
|
||||
} // namespace style
|
||||
} // namespace mbgl
|
||||
63
src/mbgl/tile/custom_vector_tile.cpp
Normal file
63
src/mbgl/tile/custom_vector_tile.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
#include <mbgl/tile/custom_vector_tile.hpp>
|
||||
#include <mbgl/tile/vector_mvt_tile_data.hpp>
|
||||
#include <mbgl/renderer/tile_parameters.hpp>
|
||||
#include <mbgl/actor/scheduler.hpp>
|
||||
#include <mbgl/style/custom_vector_tile_loader.hpp>
|
||||
#include <mbgl/tile/tile_observer.hpp>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
CustomVectorTile::CustomVectorTile(const OverscaledTileID& overscaledTileID,
|
||||
std::string sourceID_,
|
||||
const TileParameters& parameters,
|
||||
ActorRef<style::CustomVectorTileLoader> loader_,
|
||||
TileObserver* observer_)
|
||||
: GeometryTile(overscaledTileID, std::move(sourceID_), parameters, observer_),
|
||||
necessity(TileNecessity::Optional),
|
||||
loader(std::move(loader_)),
|
||||
mailbox(std::make_shared<Mailbox>(*Scheduler::GetCurrent())),
|
||||
actorRef(*this, mailbox) {}
|
||||
|
||||
CustomVectorTile::~CustomVectorTile() {
|
||||
loader.invoke(&style::CustomVectorTileLoader::removeTile, id);
|
||||
}
|
||||
|
||||
void CustomVectorTile::setTileData(const std::shared_ptr<const std::string>& data, style::TileDataFormat format) {
|
||||
if (obsolete) return;
|
||||
|
||||
switch (format) {
|
||||
case style::TileDataFormat::MVT:
|
||||
GeometryTile::setData(data ? std::make_unique<VectorMVTTileData>(data) : nullptr);
|
||||
break;
|
||||
default:
|
||||
assert(false && "Unhandled TileDataFormat");
|
||||
GeometryTile::setData(nullptr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CustomVectorTile::setTileError(std::exception_ptr error) {
|
||||
if (obsolete) return;
|
||||
GeometryTile::setError(std::move(error));
|
||||
}
|
||||
|
||||
void CustomVectorTile::invalidateTileData() {
|
||||
stale = true;
|
||||
observer->onTileChanged(*this);
|
||||
}
|
||||
|
||||
void CustomVectorTile::setNecessity(TileNecessity newNecessity) {
|
||||
if (newNecessity != necessity || stale) {
|
||||
necessity = newNecessity;
|
||||
if (necessity == TileNecessity::Required) {
|
||||
loader.invoke(&style::CustomVectorTileLoader::fetchTile, id, actorRef);
|
||||
stale = false;
|
||||
} else if (!isRenderable()) {
|
||||
loader.invoke(&style::CustomVectorTileLoader::cancelTile, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mbgl
|
||||
37
src/mbgl/tile/custom_vector_tile.hpp
Normal file
37
src/mbgl/tile/custom_vector_tile.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/tile/geometry_tile.hpp>
|
||||
#include <mbgl/style/sources/custom_vector_source.hpp>
|
||||
#include <mbgl/actor/mailbox.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
class TileParameters;
|
||||
|
||||
namespace style {
|
||||
class CustomVectorTileLoader;
|
||||
} // namespace style
|
||||
|
||||
class CustomVectorTile : public GeometryTile {
|
||||
public:
|
||||
CustomVectorTile(const OverscaledTileID&,
|
||||
std::string sourceID,
|
||||
const TileParameters&,
|
||||
ActorRef<style::CustomVectorTileLoader> loader,
|
||||
TileObserver* observer = nullptr);
|
||||
~CustomVectorTile() override;
|
||||
|
||||
void setTileData(const std::shared_ptr<const std::string>& data, style::TileDataFormat format);
|
||||
void setTileError(std::exception_ptr error);
|
||||
void invalidateTileData();
|
||||
void setNecessity(TileNecessity) final;
|
||||
|
||||
private:
|
||||
bool stale = true;
|
||||
TileNecessity necessity;
|
||||
ActorRef<style::CustomVectorTileLoader> loader;
|
||||
std::shared_ptr<Mailbox> mailbox;
|
||||
ActorRef<CustomVectorTile> actorRef;
|
||||
};
|
||||
|
||||
} // namespace mbgl
|
||||
Reference in New Issue
Block a user