diff --git a/CMakeLists.txt b/CMakeLists.txt index 6525df59428d..f6f0c5a0eae1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -979,11 +979,18 @@ list(APPEND SRC_FILES ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_layer_properties.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_layer_impl.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_layer_factory.hpp + ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_style_filter.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_layer.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_layer_render.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_layer_properties.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_layer_impl.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_layer_factory.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_file_source.hpp + ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_style_filter.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/feature_collection.hpp + ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/feature_collection.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/feature_collection_bucket.hpp + ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/feature_collection_bucket.cpp ) diff --git a/bazel/core.bzl b/bazel/core.bzl index 6dae94b4514f..314247c2bccb 100644 --- a/bazel/core.bzl +++ b/bazel/core.bzl @@ -4,6 +4,10 @@ MLN_LAYER_PLUGIN_HEADERS = [ "src/mbgl/plugin/plugin_layer_impl.hpp", "src/mbgl/plugin/plugin_layer_render.hpp", "src/mbgl/plugin/plugin_layer_properties.hpp", + "src/mbgl/plugin/plugin_file_source.hpp", + "src/mbgl/plugin/plugin_style_filter.hpp", + "src/mbgl/plugin/feature_collection_bucket.hpp", + "src/mbgl/plugin/feature_collection.hpp", ] MLN_LAYER_PLUGIN_SOURCE = [ @@ -12,6 +16,9 @@ MLN_LAYER_PLUGIN_SOURCE = [ "src/mbgl/plugin/plugin_layer_impl.cpp", "src/mbgl/plugin/plugin_layer_render.cpp", "src/mbgl/plugin/plugin_layer_properties.cpp", + "src/mbgl/plugin/plugin_style_filter.cpp", + "src/mbgl/plugin/feature_collection_bucket.cpp", + "src/mbgl/plugin/feature_collection.cpp", ] MLN_PUBLIC_GENERATED_STYLE_HEADERS = [ diff --git a/include/mbgl/storage/file_source.hpp b/include/mbgl/storage/file_source.hpp index 6a70154df417..13626bf13103 100644 --- a/include/mbgl/storage/file_source.hpp +++ b/include/mbgl/storage/file_source.hpp @@ -26,9 +26,10 @@ enum FileSourceType : uint8_t { Network, Mbtiles, Pmtiles, - ResourceLoader ///< %Resource loader acts as a proxy and has logic + ResourceLoader, ///< %Resource loader acts as a proxy and has logic /// for request delegation to Asset, Cache, and other /// file sources. + Custom // These are the plugin file resource types }; // TODO: Rename to ResourceProvider to avoid confusion with diff --git a/include/mbgl/storage/file_source_manager.hpp b/include/mbgl/storage/file_source_manager.hpp index b6ef59c09b35..f6fe523e4260 100644 --- a/include/mbgl/storage/file_source_manager.hpp +++ b/include/mbgl/storage/file_source_manager.hpp @@ -43,6 +43,12 @@ class FileSourceManager { // a FileSourceType invocation has no effect. virtual FileSourceFactory unRegisterFileSourceFactory(FileSourceType) noexcept; + // Registers a custom file source + virtual void registerCustomFileSource(std::shared_ptr) noexcept; + + // Returns an array of custom file sources + virtual std::vector> getCustomFileSources() noexcept; + protected: FileSourceManager(); class Impl; diff --git a/include/mbgl/style/style.hpp b/include/mbgl/style/style.hpp index 0d2d45719158..f3e359c1b8b5 100644 --- a/include/mbgl/style/style.hpp +++ b/include/mbgl/style/style.hpp @@ -20,6 +20,7 @@ namespace style { class Light; class Source; class Layer; +class PluginStyleFilter; class Style { public: @@ -71,6 +72,9 @@ class Style { void addLayer(std::unique_ptr, const std::optional& beforeLayerID = std::nullopt); std::unique_ptr removeLayer(const std::string& layerID); + // Add style parsing filter + void addStyleFilter(std::shared_ptr); + // Private implementation class Impl; const std::unique_ptr impl; diff --git a/platform/BUILD.bazel b/platform/BUILD.bazel index 6cc1f1dbed36..d70ee46317de 100644 --- a/platform/BUILD.bazel +++ b/platform/BUILD.bazel @@ -269,6 +269,10 @@ objc_library( srcs = [ "//platform/darwin:app/PluginLayerExample.h", "//platform/darwin:app/PluginLayerExample.mm", + "//platform/darwin:app/PluginProtocolExample.h", + "//platform/darwin:app/PluginProtocolExample.mm", + "//platform/darwin:app/StyleFilterExample.h", + "//platform/darwin:app/StyleFilterExample.mm", "//platform/darwin:app/PluginLayerExampleMetalRendering.h", "//platform/darwin:app/PluginLayerExampleMetalRendering.mm", "//platform/darwin:app/CustomStyleLayerExample.h", diff --git a/platform/android/MapLibreAndroid/src/cpp/jni_native.cpp b/platform/android/MapLibreAndroid/src/cpp/jni_native.cpp index b0a29b677d3d..24ab77e1b224 100644 --- a/platform/android/MapLibreAndroid/src/cpp/jni_native.cpp +++ b/platform/android/MapLibreAndroid/src/cpp/jni_native.cpp @@ -37,6 +37,7 @@ #include "native_map_options.hpp" #include "rendering_stats.hpp" #include "util/tile_server_options.hpp" +#include "plugin/plugin_file_source.hpp" #ifndef MBGL_MODULE_OFFLINE_DISABLE #include "offline/offline_manager.hpp" #include "offline/offline_region.hpp" @@ -107,6 +108,11 @@ void registerNatives(JavaVM* vm) { Polygon::registerNative(env); Polyline::registerNative(env); + // Plugins + PluginProtocolHandlerResource::registerNative(env); + PluginProtocolHandlerResponse::registerNative(env); + PluginFileSource::registerNative(env); + // Map MapRenderer::registerNative(env); MapRendererRunnable::registerNative(env); diff --git a/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp b/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp index e7a48ac3c31f..910ef7d4fea4 100644 --- a/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp +++ b/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp @@ -29,6 +29,8 @@ #include #include #include +#include +#include // Java -> C++ conversion #include "style/android_conversion.hpp" @@ -1320,6 +1322,93 @@ void NativeMapView::enableRenderingStatsView(JNIEnv&, jni::jboolean value) { map->enableRenderingStatsView(value); } +// Plugins +void NativeMapView::addPluginFileSource(JNIEnv& jniEnv, const jni::Object& pluginFileSource) { + // TODO: Unclear if any of these options are needed for plugins + mbgl::ResourceOptions resourceOptions; + + // TODO: Unclear if any of the properties on clientOptions need to be set + mbgl::ClientOptions clientOptions; + + android::UniqueEnv _env = android::AttachEnv(); + + // Set when the source is added to a map. + jni::Global> pluginPeer = jni::NewGlobal(*_env, pluginFileSource); + std::shared_ptr fileSourceContainer = std::make_shared(); + fileSourceContainer->_fileSource = std::move(pluginPeer); + _pluginFileSources.push_back(fileSourceContainer); + + std::shared_ptr pluginSource = std::make_shared(resourceOptions, + clientOptions); + pluginSource->setOnRequestResourceFunction([fileSourceContainer](const mbgl::Resource& resource) -> mbgl::Response { + mbgl::Response tempResponse; + + android::UniqueEnv env = android::AttachEnv(); + static auto& javaClass = jni::Class::Singleton(*env); + static auto requestResource = + javaClass.GetMethod(jni::Object)>( + *env, "requestResource"); + auto javaResource = PluginFileSource::createJavaResource(*env, resource); + auto tempResult = fileSourceContainer->_fileSource.Call(*env, requestResource, javaResource); + PluginProtocolHandlerResponse::Update(*env, tempResult, tempResponse); + + return tempResponse; + }); + pluginSource->setOnCanRequestFunction([fileSourceContainer](const mbgl::Resource& resource) -> bool { + android::UniqueEnv env = android::AttachEnv(); + static auto& javaClass = jni::Class::Singleton(*env); + static auto canRequestResource = javaClass.GetMethod)>( + *env, "canRequestResource"); + auto javaResource = PluginFileSource::createJavaResource(*env, resource); + auto tempResult = fileSourceContainer->_fileSource.Call(*env, canRequestResource, javaResource); + return tempResult; + + /* + if (!renderingStats) { + renderingStats = jni::NewGlobal(*_env, RenderingStats::Create(*_env)); + } + + RenderingStats::Update(*_env, renderingStats, status.renderingStats); + + weakReference.Call(*_env, + onDidFinishRenderingFrame, + (jboolean)(status.mode != MapObserver::RenderMode::Partial), + renderingStats); + */ + /* + static auto resourceURLField = javaClass.GetField>(env, + "resource"); auto str = jni::Make(env, resource.url); // wrap the jstring javaObject.Set(env, + resourceURLField, str); + + fileSourceContainer->_fileSource.Set(env, ) + */ + }); + auto fileSourceManager = mbgl::FileSourceManager::get(); + fileSourceManager->registerCustomFileSource(pluginSource); + + /* + android::UniqueEnv _env = android::AttachEnv(); + static auto& javaClass = jni::Class::Singleton(*_env); + static auto onDidFinishRenderingFrame = javaClass.GetMethod)>( + *_env, "onDidFinishRenderingFrame"); + auto weakReference = javaPeer.get(*_env); + if (weakReference) { + if (!renderingStats) { + renderingStats = jni::NewGlobal(*_env, RenderingStats::Create(*_env)); + } + + RenderingStats::Update(*_env, renderingStats, status.renderingStats); + + weakReference.Call(*_env, + onDidFinishRenderingFrame, + (jboolean)(status.mode != MapObserver::RenderMode::Partial), + renderingStats); + } + + */ +} + // Static methods // void NativeMapView::registerNative(jni::JNIEnv& env) { @@ -1442,7 +1531,8 @@ void NativeMapView::registerNative(jni::JNIEnv& env) { METHOD(&NativeMapView::getTileLodZoomShift, "nativeGetTileLodZoomShift"), METHOD(&NativeMapView::triggerRepaint, "nativeTriggerRepaint"), METHOD(&NativeMapView::isRenderingStatsViewEnabled, "nativeIsRenderingStatsViewEnabled"), - METHOD(&NativeMapView::enableRenderingStatsView, "nativeEnableRenderingStatsView")); + METHOD(&NativeMapView::enableRenderingStatsView, "nativeEnableRenderingStatsView"), + METHOD(&NativeMapView::addPluginFileSource, "nativeAddPluginFileSource")); } void NativeMapView::onRegisterShaders(gfx::ShaderRegistry&) {}; diff --git a/platform/android/MapLibreAndroid/src/cpp/native_map_view.hpp b/platform/android/MapLibreAndroid/src/cpp/native_map_view.hpp index f9a0c7a3e136..ab58fcdd904b 100644 --- a/platform/android/MapLibreAndroid/src/cpp/native_map_view.hpp +++ b/platform/android/MapLibreAndroid/src/cpp/native_map_view.hpp @@ -25,7 +25,7 @@ #include "style/light.hpp" #include "native_map_options.hpp" #include "bitmap.hpp" - +#include "plugin/plugin_file_source.hpp" #include #include #include @@ -325,6 +325,9 @@ class NativeMapView : public MapObserver { jni::jboolean isRenderingStatsViewEnabled(JNIEnv&); void enableRenderingStatsView(JNIEnv&, jni::jboolean); + // Plugins + void addPluginFileSource(JNIEnv&, const jni::Object&); + // Shader compilation void onRegisterShaders(mbgl::gfx::ShaderRegistry&) override; void onPreCompileShader(mbgl::shaders::BuiltIn, mbgl::gfx::Backend::Type, const std::string&) override; @@ -364,6 +367,9 @@ class NativeMapView : public MapObserver { // Ensure these are initialised last std::unique_ptr map; + + // List of plugin file source + std::vector> _pluginFileSources; }; } // namespace android diff --git a/platform/android/MapLibreAndroid/src/cpp/plugin/plugin_file_source.cpp b/platform/android/MapLibreAndroid/src/cpp/plugin/plugin_file_source.cpp new file mode 100644 index 000000000000..b288678af17a --- /dev/null +++ b/platform/android/MapLibreAndroid/src/cpp/plugin/plugin_file_source.cpp @@ -0,0 +1,78 @@ + +#include "plugin_file_source.hpp" + +using namespace mbgl::android; + +// Putting these here (should probably move out to a more dedicated JNI place within +// the native repo at some point, but this means we don't have to update +// the jni.hpp dependency +namespace jni { + +struct ByteBufferTag { + static constexpr auto Name() { return "java/nio/ByteBuffer"; } +}; + +template <> +struct TagTraits { + using SuperType = Object; + using UntaggedType = jobject; +}; + +} // namespace jni + +void PluginFileSource::registerNative(jni::JNIEnv& env) { + jni::Class::Singleton(env); +} + +jni::Global> PluginFileSource::createJavaResource( + jni::JNIEnv& env, const mbgl::Resource& resource) { + jni::Global> tempResult = jni::NewGlobal( + env, PluginProtocolHandlerResource::Create(env)); + PluginProtocolHandlerResource::Update(env, tempResult, resource); + return tempResult; +} + +void PluginProtocolHandlerResource::registerNative(jni::JNIEnv& env) { + jni::Class::Singleton(env); +} + +jni::Local> PluginProtocolHandlerResource::Create(jni::JNIEnv& env) { + auto& javaClass = jni::Class::Singleton(env); + auto constructor = javaClass.GetConstructor(env); + return javaClass.New(env, constructor); +} + +void PluginProtocolHandlerResource::Update(jni::JNIEnv& env, + jni::Object& javaObject, + const mbgl::Resource& resource) { + static auto& javaClass = jni::Class::Singleton(env); + + static auto resourceKindField = javaClass.GetField(env, "kind"); + javaObject.Set(env, resourceKindField, static_cast(resource.kind)); + + static auto resourceURLField = javaClass.GetField(env, "resourceURL"); + auto str = jni::Make(env, resource.url); // wrap the jstring + javaObject.Set(env, resourceURLField, str); +} + +void PluginProtocolHandlerResponse::registerNative(jni::JNIEnv& env) { + jni::Class::Singleton(env); +} + +jni::Local> PluginProtocolHandlerResponse::Create(jni::JNIEnv& env) { + auto& javaClass = jni::Class::Singleton(env); + auto constructor = javaClass.GetConstructor(env); + return javaClass.New(env, constructor); +} + +void PluginProtocolHandlerResponse::Update(jni::JNIEnv& env, + [[maybe_unused]] jni::Object& javaObject, + [[maybe_unused]] mbgl::Response& response) { + static auto& javaClass = jni::Class::Singleton(env); + static auto dataField = javaClass.GetField>(env, "data"); + auto objectValue = javaObject.Get(env, dataField); + auto objectRef = jobject(objectValue.get()); + void* bufPtr = env.GetDirectBufferAddress(objectRef); + jsize length = env.GetDirectBufferCapacity(objectRef); + response.data = std::make_shared((const char*)bufPtr, length); +} diff --git a/platform/android/MapLibreAndroid/src/cpp/plugin/plugin_file_source.hpp b/platform/android/MapLibreAndroid/src/cpp/plugin/plugin_file_source.hpp new file mode 100644 index 000000000000..cea25e21e4de --- /dev/null +++ b/platform/android/MapLibreAndroid/src/cpp/plugin/plugin_file_source.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include +#include + +namespace mbgl { +namespace android { + +class PluginProtocolHandlerResource { +public: + static constexpr auto Name() { return "org/maplibre/android/plugin/PluginProtocolHandlerResource"; }; + static void registerNative(jni::JNIEnv &); + + static jni::Local> Create(jni::JNIEnv &); + static void Update(jni::JNIEnv &, jni::Object &, const mbgl::Resource &); +}; + +class PluginProtocolHandlerResponse { +public: + static constexpr auto Name() { return "org/maplibre/android/plugin/PluginProtocolHandlerResponse"; }; + static void registerNative(jni::JNIEnv &); + + static jni::Local> Create(jni::JNIEnv &); + static void Update(jni::JNIEnv &, jni::Object &, mbgl::Response &); +}; + +class PluginFileSource { +public: + static constexpr auto Name() { return "org/maplibre/android/plugin/PluginFileSource"; }; + + // static mbgl::PluginFileSource getFileSource(jni::JNIEnv&, const jni::Object&); + + static jni::Global> createJavaResource(jni::JNIEnv &env, + const mbgl::Resource &resource); + + static void registerNative(jni::JNIEnv &); +}; + +class PluginFileSourceContainer { +public: + jni::Global> _fileSource; +}; + +} // namespace android +} // namespace mbgl diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMap.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMap.java index cdbecdfd660a..82ad755da11e 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMap.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMap.java @@ -20,6 +20,7 @@ import org.maplibre.android.gestures.RotateGestureDetector; import org.maplibre.android.gestures.ShoveGestureDetector; import org.maplibre.android.gestures.StandardScaleGestureDetector; +import org.maplibre.android.plugin.PluginProtocolHandler; import org.maplibre.geojson.Feature; import org.maplibre.geojson.Geometry; import org.maplibre.android.MapStrictMode; @@ -2694,4 +2695,19 @@ private void notifyDeveloperAnimationListeners() { listener.onDeveloperAnimationStarted(); } } + + + + /** + * Adds a custom protocol handler to the map view + */ + ArrayList pluginProtocolHandlers = new ArrayList(); + public void addPluginProtocolHandler(PluginProtocolHandler protocolHandler) { + pluginProtocolHandlers.add(protocolHandler); + nativeMapView.addPluginProtocolHandler(protocolHandler); + + // nativeMapView.addPolygon() + } + + } diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapView.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapView.java index d0828b14272f..7673241ec043 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapView.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapView.java @@ -31,6 +31,7 @@ import org.maplibre.android.maps.renderer.MapRenderer; import org.maplibre.android.maps.widgets.CompassView; import org.maplibre.android.net.ConnectivityReceiver; +import org.maplibre.android.plugin.PluginProtocolHandler; import org.maplibre.android.storage.FileSource; import org.maplibre.android.utils.BitmapUtils; import org.maplibre.android.tile.TileOperation; @@ -1817,4 +1818,14 @@ private AttributionDialogManager getDialogManager() { public static void setMapStrictModeEnabled(boolean strictModeEnabled) { MapStrictMode.setStrictModeEnabled(strictModeEnabled); } + + /** + * Adds a custom protocol handler to the map view + */ + public void addPluginProtocolHandler(PluginProtocolHandler protocolHandler) { + if (maplibreMap != null) { + maplibreMap.addPluginProtocolHandler(protocolHandler); + } + } + } diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMap.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMap.java index 585f7ed332b6..50dd727e6971 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMap.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMap.java @@ -8,6 +8,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.maplibre.android.plugin.PluginProtocolHandler; import org.maplibre.geojson.Feature; import org.maplibre.geojson.Geometry; import org.maplibre.android.annotations.Marker; @@ -270,6 +271,8 @@ List queryRenderedFeatures(@NonNull RectF coordinates, void enableRenderingStatsView(boolean value); + void addPluginProtocolHandler(PluginProtocolHandler protocolHandler); + void setSwapBehaviorFlush(boolean flush); // diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMapView.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMapView.java index 571193bd45d5..1e89a31b07cd 100755 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMapView.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMapView.java @@ -13,6 +13,9 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.maplibre.android.plugin.PluginFileSource; +import org.maplibre.android.plugin.PluginProtocolHandler; +import org.maplibre.android.plugin.PluginProtocolHandlerResource; import org.maplibre.geojson.Feature; import org.maplibre.geojson.Geometry; import org.maplibre.android.LibraryLoader; @@ -38,6 +41,7 @@ import org.maplibre.android.style.sources.Source; import org.maplibre.android.utils.BitmapUtils; import org.maplibre.android.tile.TileOperation; +import org.maplibre.android.plugin.PluginFileSource; import java.util.ArrayList; import java.util.Arrays; @@ -1149,6 +1153,13 @@ public void enableRenderingStatsView(boolean value) { nativeEnableRenderingStatsView(value); } + @Override + public void addPluginProtocolHandler(PluginProtocolHandler protocolHandler) { + PluginFileSource pluginFileSource = new PluginFileSource(); + pluginFileSource.protocolHandler = protocolHandler; + nativeAddPluginFileSource(pluginFileSource); + } + @Override public void setSwapBehaviorFlush(boolean flush) { mapRenderer.setSwapBehaviorFlush(flush); @@ -1746,6 +1757,11 @@ public long getNativePtr() { @Keep private native void nativeEnableRenderingStatsView(boolean enabled); + @Keep + private native void nativeAddPluginFileSource(PluginFileSource fileSource); + + + // // Snapshot // diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/plugin/PluginFileSource.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/plugin/PluginFileSource.java new file mode 100644 index 000000000000..e99ca05a08fb --- /dev/null +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/plugin/PluginFileSource.java @@ -0,0 +1,18 @@ +package org.maplibre.android.plugin; + +public class PluginFileSource { + + public PluginProtocolHandler protocolHandler; + + public boolean canRequestResource(PluginProtocolHandlerResource resource) { + + return protocolHandler.canRequestResource(resource); + + } + + public PluginProtocolHandlerResponse requestResource(PluginProtocolHandlerResource resource) { + PluginProtocolHandlerResponse response = protocolHandler.requestResource(resource); + return response; + } + +} diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/plugin/PluginProtocolHandler.kt b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/plugin/PluginProtocolHandler.kt new file mode 100644 index 000000000000..2b84385a9d2a --- /dev/null +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/plugin/PluginProtocolHandler.kt @@ -0,0 +1,18 @@ +package org.maplibre.android.plugin + +import android.icu.text.Normalizer + +public open class PluginProtocolHandler { + + open fun canRequestResource(resource: PluginProtocolHandlerResource?): Boolean { + // Base class does nothing + return false; + } + + + open fun requestResource(resource: PluginProtocolHandlerResource?): PluginProtocolHandlerResponse? { + // Base class does nothing + return null; + } + +} diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/plugin/PluginProtocolHandlerResource.kt b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/plugin/PluginProtocolHandlerResource.kt new file mode 100644 index 000000000000..606311cdf690 --- /dev/null +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/plugin/PluginProtocolHandlerResource.kt @@ -0,0 +1,33 @@ +package org.maplibre.android.plugin + +class PluginProtocolHandlerResource { + + enum class Kind { + Unknown, + Style, + Source, + Tile, + Glyphs, + SpriteImage, + SpriteJSON, + Image + }; + + enum class LoadingMethod { + Unknown, + CacheOnly, + NetworkOnly, + All + }; + + var resourceURL: String = ""; + + var kind: Int = 0; + + var resourceKind: Kind = Kind.Unknown; + + var loadingMethod: LoadingMethod = LoadingMethod.Unknown; + + var tileData: TileData? = null; + +} diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/plugin/PluginProtocolHandlerResponse.kt b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/plugin/PluginProtocolHandlerResponse.kt new file mode 100644 index 000000000000..5df047bd7c51 --- /dev/null +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/plugin/PluginProtocolHandlerResponse.kt @@ -0,0 +1,18 @@ +package org.maplibre.android.plugin + +import java.nio.ByteBuffer +import java.nio.charset.StandardCharsets + +class PluginProtocolHandlerResponse { + + fun generateBuffer(stringBuffer: String) { + val byteArray = stringBuffer.toByteArray(StandardCharsets.UTF_8); + val buffer = ByteBuffer.allocateDirect(byteArray.size); + buffer.put(byteArray); + buffer.flip(); + data = buffer; + } + + var data: ByteBuffer? = null; + +} diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/plugin/TileData.kt b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/plugin/TileData.kt new file mode 100644 index 000000000000..2a9f27abe151 --- /dev/null +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/plugin/TileData.kt @@ -0,0 +1,15 @@ +package org.maplibre.android.plugin + +class TileData { + + var tileURLTemplate: String? = null; + + var tilePixelRatio: Int = 0; + + var tileX: Int = 0; + + var tileY: Int = 0; + + var tileZoom: Int = 0; + +} diff --git a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/maplayout/SimpleMapActivity.kt b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/maplayout/SimpleMapActivity.kt index 8afad9bf94be..1717ee451d08 100644 --- a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/maplayout/SimpleMapActivity.kt +++ b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/maplayout/SimpleMapActivity.kt @@ -6,6 +6,7 @@ import androidx.activity.OnBackPressedCallback import androidx.appcompat.app.AppCompatActivity import org.maplibre.android.maps.* import org.maplibre.android.testapp.R +import org.maplibre.android.testapp.activity.plugin.PluginProtocolExample import org.maplibre.android.testapp.utils.ApiKeyUtils import org.maplibre.android.testapp.utils.NavUtils @@ -27,6 +28,8 @@ class SimpleMapActivity : AppCompatActivity() { mapView = findViewById(R.id.mapView) mapView.onCreate(savedInstanceState) mapView.getMapAsync { + val protocolExample = PluginProtocolExample(); + mapView.addPluginProtocolHandler(protocolExample); val key = ApiKeyUtils.getApiKey(applicationContext) if (key == null || key == "YOUR_API_KEY_GOES_HERE") { it.setStyle( diff --git a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/plugin/PluginProtocolExample.kt b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/plugin/PluginProtocolExample.kt new file mode 100644 index 000000000000..f027dde05fc3 --- /dev/null +++ b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/plugin/PluginProtocolExample.kt @@ -0,0 +1,104 @@ +package org.maplibre.android.testapp.activity.plugin + +import org.maplibre.android.plugin.PluginProtocolHandler +import org.maplibre.android.plugin.PluginProtocolHandlerResource +import org.maplibre.android.plugin.PluginProtocolHandlerResponse +import java.net.URI + + +class PluginProtocolExample : PluginProtocolHandler() { + var styleLoaded: Boolean = false; + + override fun canRequestResource(resource: PluginProtocolHandlerResource?): Boolean { + if (!styleLoaded) { + styleLoaded = true; + return true; + } + return false; + + } + + + override fun requestResource(resource: PluginProtocolHandlerResource?): PluginProtocolHandlerResponse? { + // Return some data here + var tempResult = PluginProtocolHandlerResponse(); + + val tempStyle: String = + "{\n" + + " \"id\": \"43f36e14-e3f5-43c1-84c0-50a9c80dc5c7\",\n" + + " \"name\": \"MapLibre\",\n" + + " \"zoom\": 0.8619833357855968,\n" + + " \"pitch\": 0,\n" + + " \"center\": [\n" + + " 17.65431710431244,\n" + + " 32.954120326746775\n" + + " ],\n" + + " \"layers\": [\n" + + " {\n" + + " \"id\": \"background\",\n" + + " \"type\": \"background\",\n" + + " \"paint\": {\n" + + " \"background-color\": \"#000000\"\n" + + " },\n" + + " \"filter\": [\n" + + " \"all\"\n" + + " ],\n" + + " \"layout\": {\n" + + " \"visibility\": \"visible\"\n" + + " },\n" + + " \"maxzoom\": 24\n" + + " },\n"+ + " {\n" + + " \"id\": \"coastline\",\n" + + " \"type\": \"line\",\n" + + " \"paint\": {\n" + + " \"line-blur\": 0.5,\n" + + " \"line-color\": \"#198EC8\",\n" + + " \"line-width\": {\n" + + " \"stops\": [\n" + + " [\n" + + " 0,\n" + + " 2\n" + + " ],\n" + + " [\n" + + " 6,\n" + + " 6\n" + + " ],\n" + + " [\n" + + " 14,\n" + + " 9\n" + + " ],\n" + + " [\n" + + " 22,\n" + + " 18\n" + + " ]\n" + + " ]\n" + + " }\n" + + " },\n" + + " \"filter\": [\n" + + " \"all\"\n" + + " ],\n" + + " \"layout\": {\n" + + " \"line-cap\": \"round\",\n" + + " \"line-join\": \"round\",\n" + + " \"visibility\": \"visible\"\n" + + " },\n" + + " \"source\": \"maplibre\",\n" + + " \"maxzoom\": 24,\n" + + " \"minzoom\": 0,\n" + + " \"source-layer\": \"countries\"\n" + + " }"+ + " ],\n"+ + " \"sources\": {\n" + + " \"maplibre\": {\n" + + " \"url\": \"https://demotiles.maplibre.org/tiles/tiles.json\",\n" + + " \"type\": \"vector\"\n" + + " },"+ + " \"version\": 8\n"+ + " } }\n"; + + tempResult.generateBuffer(tempStyle); + return tempResult; + } + +} diff --git a/platform/android/android.cmake b/platform/android/android.cmake index cb9e28e5a5de..72fc11d7276f 100644 --- a/platform/android/android.cmake +++ b/platform/android/android.cmake @@ -45,6 +45,7 @@ target_sources( ${PROJECT_SOURCE_DIR}/platform/android/src/string_util.cpp ${PROJECT_SOURCE_DIR}/platform/android/src/thread.cpp ${PROJECT_SOURCE_DIR}/platform/android/src/timer.cpp + ${PROJECT_SOURCE_DIR}/platform/android/MapLibreAndroid/src/cpp/plugin/plugin_file_source.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/gfx/headless_backend.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/gfx/headless_frontend.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/map/map_snapshotter.cpp @@ -61,6 +62,7 @@ target_sources( ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/storage/offline_database.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/storage/offline_download.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/storage/online_file_source.cpp + ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/storage/plugin_file_source.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/storage/$,pmtiles_file_source.cpp,pmtiles_file_source_stub.cpp> ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/storage/sqlite3.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/text/bidi.cpp diff --git a/platform/darwin/BUILD.bazel b/platform/darwin/BUILD.bazel index dcc0a8bcbac4..44b1ca1d3832 100644 --- a/platform/darwin/BUILD.bazel +++ b/platform/darwin/BUILD.bazel @@ -301,8 +301,12 @@ exports_files( "app/PluginLayerTestStyle.json", "app/PluginLayerExample.h", "app/PluginLayerExample.mm", + "app/StyleFilterExample.h", + "app/StyleFilterExample.mm", "app/PluginLayerExampleMetalRendering.h", "app/PluginLayerExampleMetalRendering.mm", + "app/PluginProtocolExample.h", + "app/PluginProtocolExample.mm", "test/amsterdam.geojson", "test/MLNSDKTestHelpers.swift", "app/CustomStyleLayerExample.h", diff --git a/platform/darwin/app/PluginLayerExample.mm b/platform/darwin/app/PluginLayerExample.mm index 0cb01054d155..1339f51289dd 100644 --- a/platform/darwin/app/PluginLayerExample.mm +++ b/platform/darwin/app/PluginLayerExample.mm @@ -1,5 +1,14 @@ #import "PluginLayerExample.h" +@interface PluginLayerExample () { + +} + +@property BOOL logFeatures; + +@end + + @implementation PluginLayerExample @@ -7,15 +16,24 @@ @implementation PluginLayerExample +(MLNPluginLayerCapabilities *)layerCapabilities { MLNPluginLayerCapabilities *tempResult = [[MLNPluginLayerCapabilities alloc] init]; - tempResult.layerID = @"plugin-layer-test"; + tempResult.layerID = @"maplibre::filter_features"; tempResult.requiresPass3D = YES; + tempResult.supportsReadingTileFeatures = YES; return tempResult; } +-(id)init { + if (self = [super init]) { + self.logFeatures = NO; + } + return self; +} + // The overrides --(void)onRenderLayer { - NSLog(@"PluginLayerExample: On Render Layer"); +-(void)onRenderLayer:(MLNMapView *)mapView + renderEncoder:(id)renderEncoder { + //NSLog(@"PluginLayerExample: On Render Layer"); } @@ -24,7 +42,48 @@ -(void)onUpdateLayer { } -(void)onUpdateLayerProperties:(NSDictionary *)layerProperties { - // NSLog(@"Layer Properties: %@", layerProperties); + // NSLog(@"Layer Properties: %@", layerProperties); +} + +-(void)featureLoaded:(MLNPluginLayerTileFeature *)tileFeature { + + // Writing a single string since the tile loading is multithreaded and the output can get interwoven + NSMutableString *outputStr = [NSMutableString string]; + [outputStr appendFormat:@"Tile Feature (id:%@) Properties: %@\n", tileFeature.featureID, tileFeature.featureProperties]; + + for (NSValue *v in tileFeature.featureCoordinates) { + + CLLocationCoordinate2D coord; + [v getValue:&coord]; + + [outputStr appendFormat:@" -> (%f, %f) \n", coord.latitude, coord.longitude]; + + } + + NSLog(@"Feature: %@", outputStr); +} + +-(void)featureUnloaded:(MLNPluginLayerTileFeature *)tileFeature { + // NSLog(@"Tile Features Unloaded: %@", tileFeature.featureProperties); + +} + + +/// Called when a set of features are loaded from the tile +- (void)onFeatureCollectionLoaded:(MLNPluginLayerTileFeatureCollection *)tileFeatureCollection { + //NSLog(@"Feature Collection Loaded for tile: %@", tileFeatureCollection.tileID); + for (MLNPluginLayerTileFeature *feature in tileFeatureCollection.features) { + [self featureLoaded:feature]; + } + +} + +/// Called when a set of features are unloaded because the tile goes out of scene/etc +- (void)onFeatureCollectionUnloaded:(MLNPluginLayerTileFeatureCollection *)tileFeatureCollection { + //NSLog(@"Feature Collection Unloaded for tile: %@", tileFeatureCollection.tileID); + for (MLNPluginLayerTileFeature *feature in tileFeatureCollection.features) { + [self featureUnloaded:feature]; + } } @end diff --git a/platform/darwin/app/PluginLayerTestStyle.json b/platform/darwin/app/PluginLayerTestStyle.json index 25ce408bd77f..b650177ef0f7 100644 --- a/platform/darwin/app/PluginLayerTestStyle.json +++ b/platform/darwin/app/PluginLayerTestStyle.json @@ -583,6 +583,13 @@ ] } }, + { "id": "centroid-features", + "type": "maplibre::filter_features", + "source": "maplibre", + "source-layer": "countries", + "maxzoom": 24, + "minzoom": 1 + }, { "id": "crimea-fill", "type": "fill", diff --git a/platform/darwin/app/PluginProtocolExample.h b/platform/darwin/app/PluginProtocolExample.h new file mode 100644 index 000000000000..63cf0fee0d99 --- /dev/null +++ b/platform/darwin/app/PluginProtocolExample.h @@ -0,0 +1,9 @@ +#import "MLNPluginProtocolHandler.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface PluginProtocolExample : MLNPluginProtocolHandler + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/app/PluginProtocolExample.mm b/platform/darwin/app/PluginProtocolExample.mm new file mode 100644 index 000000000000..8915f9b514a6 --- /dev/null +++ b/platform/darwin/app/PluginProtocolExample.mm @@ -0,0 +1,23 @@ +#import "PluginProtocolExample.h" + +@implementation PluginProtocolExample + +-(BOOL)canRequestResource:(MLNPluginProtocolHandlerResource *)resource { + if ([resource.resourceURL containsString:@"pluginProtocol"]) { + return YES; + } + return NO; +} + +-(MLNPluginProtocolHandlerResponse *)requestResource:(MLNPluginProtocolHandlerResource *)resource { + + NSData *data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"PluginLayerTestStyle.json" ofType:nil]]; + + MLNPluginProtocolHandlerResponse *response = [[MLNPluginProtocolHandlerResponse alloc] init]; + response.data = data; + return response; + +} + + +@end diff --git a/platform/darwin/app/StyleFilterExample.h b/platform/darwin/app/StyleFilterExample.h new file mode 100644 index 000000000000..ea694e6d4c3e --- /dev/null +++ b/platform/darwin/app/StyleFilterExample.h @@ -0,0 +1,9 @@ +#import "MLNStyleFilter.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface StyleFilterExample : MLNStyleFilter + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/app/StyleFilterExample.mm b/platform/darwin/app/StyleFilterExample.mm new file mode 100644 index 000000000000..730e36b75f3d --- /dev/null +++ b/platform/darwin/app/StyleFilterExample.mm @@ -0,0 +1,60 @@ +#import "StyleFilterExample.h" + +@implementation StyleFilterExample + +// This will filter the data passed in +-(NSData *)filterData:(NSData *)data { + // Don't call super + + // This example will remove any layer that has "metal-rendering-layer" in the id + + // Parse the JSON: Make the containers mutable + NSError *error = nil; + NSMutableDictionary *styleDictionary = [NSJSONSerialization JSONObjectWithData:data + options:NSJSONReadingMutableContainers + error:&error]; + + NSData *tempResult = data; + if (styleDictionary) { + + // Get the layer array + NSMutableArray *layerArray = [styleDictionary objectForKey:@"layers"]; + + // Create an array to hold which objects to remove since we can't remove them in the loop + NSMutableArray *removedLayers = [NSMutableArray array]; + + // Loop the layers and look for any layers that have the search string in them + for (NSMutableDictionary *layer in layerArray) { + NSString *layerID = [layer objectForKey:@"id"]; + + // If we find the layers we're looking for, add them to the list of layers to remove + if ([layerID containsString:@"metal-rendering-layer"]) { + [removedLayers addObject:layer]; + } + } + + // Go through and remove any layers that were found + for (NSMutableDictionary *l in removedLayers) { + [layerArray removeObject:l]; + } + + // Re-create the JSON, this time the layers we filtered out won't be there + NSData *filteredStyleJSON = [NSJSONSerialization dataWithJSONObject:styleDictionary + options:0 + error:&error]; + + // If the JSON write is successful, then set the output to the new json style + if (filteredStyleJSON) { + tempResult = filteredStyleJSON; + } + + } + + // Return the data + return tempResult; + +} + + + +@end diff --git a/platform/darwin/bazel/files.bzl b/platform/darwin/bazel/files.bzl index 560189cd69c0..f89b40d1ed4f 100644 --- a/platform/darwin/bazel/files.bzl +++ b/platform/darwin/bazel/files.bzl @@ -108,6 +108,8 @@ MLN_DARWIN_OBJC_HEADERS = [ "src/NSValue+MLNAdditions.h", "src/MLNPluginLayer.h", "src/MLNPluginStyleLayer.h", + "src/MLNPluginProtocolHandler.h", + "src/MLNStyleFilter.h", ] MLN_DARWIN_OBJCPP_HEADERS = [ @@ -165,8 +167,8 @@ MLN_DARWIN_PRIVATE_HEADERS = [ "src/MLNVectorTileSource_Private.h", "src/NSExpression+MLNPrivateAdditions.h", "src/NSPredicate+MLNPrivateAdditions.h", - "src/MLNPluginStyleLayer_Private.h" - + "src/MLNPluginStyleLayer_Private.h", + "src/MLNStyleFilter_Private.h" ] MLN_DARWIN_PUBLIC_OBJCPP_SOURCE = [ @@ -222,8 +224,9 @@ MLN_DARWIN_PUBLIC_OBJCPP_SOURCE = [ "src/NSPredicate+MLNAdditions.mm", "src/NSValue+MLNStyleAttributeAdditions.mm", "src/MLNPluginLayer.mm", - "src/MLNPluginStyleLayer.mm" - + "src/MLNPluginStyleLayer.mm", + "src/MLNPluginProtocolHandler.mm", + "src/MLNStyleFilter.mm", ] MLN_DARWIN_PUBLIC_OBJCPP_CUSTOM_DRAWABLE_SOURCE = [ "src/MLNCustomDrawableStyleLayer_Private.h", diff --git a/platform/darwin/core/http_file_source.mm b/platform/darwin/core/http_file_source.mm index 62ced8e7d1f3..d88f10e4b56e 100644 --- a/platform/darwin/core/http_file_source.mm +++ b/platform/darwin/core/http_file_source.mm @@ -292,11 +292,24 @@ BOOL isValidMapboxEndpoint(NSURL *url) { assert(session); + if ([networkManager.delegate respondsToSelector:@selector(willSendRequest:)]) { + req = [networkManager.delegate willSendRequest:req]; + } + request->task = [session dataTaskWithRequest:req completionHandler:^(NSData* data, NSURLResponse* res, NSError* error) { session = nil; + if ([networkManager.delegate respondsToSelector:@selector(didReceiveResponse:)]) { + NetworkResponse *networkResponse = [NetworkResponse responseWithData:data urlResponse:res error:error]; + networkResponse = [networkManager.delegate didReceiveResponse:networkResponse]; + data = networkResponse.data; + res = networkResponse.response; + error = networkResponse.error; + } + + if (error && [error code] == NSURLErrorCancelled) { [MLNNativeNetworkManager.sharedManager cancelDownloadEventForResponse:res]; return; diff --git a/platform/darwin/core/native_apple_interface.m b/platform/darwin/core/native_apple_interface.m index edd3b981d76b..5d176ea4e255 100644 --- a/platform/darwin/core/native_apple_interface.m +++ b/platform/darwin/core/native_apple_interface.m @@ -1,6 +1,22 @@ #import #import +@implementation NetworkResponse + ++(NetworkResponse *)responseWithData:(NSData *)data + urlResponse:(NSURLResponse *)response + error:(NSError *)error { + + NetworkResponse *tempResult = [[NetworkResponse alloc] init]; + tempResult.data = data; + tempResult.response = response; + tempResult.error = error; + return tempResult; +} + +@end + + @implementation MLNNativeNetworkManager static MLNNativeNetworkManager *instance = nil; diff --git a/platform/darwin/darwin.cmake b/platform/darwin/darwin.cmake index 4217aa752bd5..dffc48543c5e 100644 --- a/platform/darwin/darwin.cmake +++ b/platform/darwin/darwin.cmake @@ -63,6 +63,7 @@ target_sources( ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/storage/offline_database.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/storage/offline_download.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/storage/online_file_source.cpp + ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/storage/plugin_file_source.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/storage/$,pmtiles_file_source.cpp,pmtiles_file_source_stub.cpp> ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/storage/sqlite3.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/text/bidi.cpp @@ -164,6 +165,8 @@ add_library( "${CMAKE_CURRENT_LIST_DIR}/app/CustomStyleLayerExample.m" "${CMAKE_CURRENT_LIST_DIR}/app/PluginLayerExample.mm" "${CMAKE_CURRENT_LIST_DIR}/app/PluginLayerExampleMetalRendering.mm" + "${CMAKE_CURRENT_LIST_DIR}/app/PluginProtocolExample.mm" + "${CMAKE_CURRENT_LIST_DIR}/app/StyleFilterExample.mm" ) target_link_libraries( diff --git a/platform/darwin/include/mbgl/interface/native_apple_interface.h b/platform/darwin/include/mbgl/interface/native_apple_interface.h index 7633819bb302..34513c5850e0 100644 --- a/platform/darwin/include/mbgl/interface/native_apple_interface.h +++ b/platform/darwin/include/mbgl/interface/native_apple_interface.h @@ -4,12 +4,29 @@ NS_ASSUME_NONNULL_BEGIN @class MLNNativeNetworkManager; +@interface NetworkResponse : NSObject + +@property (nullable) NSError *error; +@property (nullable) NSData *data; +@property NSURLResponse *response; + ++(NetworkResponse *)responseWithData:(NSData *)data + urlResponse:(NSURLResponse *)response + error:(NSError *)error; + +@end + + @protocol MLNNativeNetworkDelegate @optional - (NSURLSession *)sessionForNetworkManager:(MLNNativeNetworkManager *)networkManager; +- (NSMutableURLRequest *)willSendRequest:(NSMutableURLRequest *)request; + +- (NetworkResponse *)didReceiveResponse:(NetworkResponse *)response; + @required - (NSURLSessionConfiguration *)sessionConfiguration; diff --git a/platform/darwin/src/MLNPluginLayer.h b/platform/darwin/src/MLNPluginLayer.h index 2e977b8d09c7..7f3242a13573 100644 --- a/platform/darwin/src/MLNPluginLayer.h +++ b/platform/darwin/src/MLNPluginLayer.h @@ -42,6 +42,22 @@ MLN_EXPORT @end +@interface MLNPluginLayerTileFeature : NSObject + +// This is the unique feature ID in the tile source if applicable. Can be empty +@property NSString *featureID; +@property NSDictionary *featureProperties; +@property NSArray *featureCoordinates; + +@end + +@interface MLNPluginLayerTileFeatureCollection : NSObject + +@property NSArray *features; +@property NSString *tileID; // z,x,y + +@end + typedef enum { MLNPluginLayerTileKindGeometry, MLNPluginLayerTileKindRaster, @@ -55,6 +71,9 @@ MLN_EXPORT @property (copy) NSString *layerID; @property BOOL requiresPass3D; +//! Set this to true if this layer can support reading features from the tiles +@property BOOL supportsReadingTileFeatures; + //! This is a list of layer properties that this layer supports. @property (copy) NSArray *layerProperties; @@ -104,6 +123,12 @@ MLN_EXPORT /// dynamic properties are updated - (void)onUpdateLayerProperties:(NSDictionary *)layerProperties; +/// Called when a set of features are loaded from the tile +- (void)onFeatureCollectionLoaded:(MLNPluginLayerTileFeatureCollection *)tileFeatureCollection; + +/// Called when a set of features are unloaded because the tile goes out of scene/etc +- (void)onFeatureCollectionUnloaded:(MLNPluginLayerTileFeatureCollection *)tileFeatureCollection; + /// Added to a map view - (void)didMoveToMapView:(MLNMapView *)mapView; diff --git a/platform/darwin/src/MLNPluginLayer.mm b/platform/darwin/src/MLNPluginLayer.mm index cff0044d05ac..0e57affd1e7f 100644 --- a/platform/darwin/src/MLNPluginLayer.mm +++ b/platform/darwin/src/MLNPluginLayer.mm @@ -1,5 +1,13 @@ #import "MLNPluginLayer.h" +@implementation MLNPluginLayerTileFeature + +@end + +@implementation MLNPluginLayerTileFeatureCollection + +@end + @implementation MLNPluginLayerProperty +(MLNPluginLayerProperty *)propertyWithName:(NSString *)propertyName @@ -66,12 +74,25 @@ -(void)onUpdateLayerProperties:(NSDictionary *)layerProperties { // Base class does nothing } +// If the layer properties indicate that this layer has a the ability to intercept +// features, then this method will be called when a feature is loaded +- (void)onFeatureCollectionLoaded:(MLNPluginLayerTileFeatureCollection *)tileFeatureCollection { + // Base class does nothing +} + +/// Called when a set of features are unloaded because the tile goes out of scene/etc +- (void)onFeatureCollectionUnloaded:(MLNPluginLayerTileFeatureCollection *)tileFeatureCollection { + // Base class does nothing +} + + /// Added to a map view - (void)didMoveToMapView:(MLNMapView *)mapView { // Base class does nothing } + @end diff --git a/platform/darwin/src/MLNPluginProtocolHandler.h b/platform/darwin/src/MLNPluginProtocolHandler.h new file mode 100644 index 000000000000..ef88a9889b35 --- /dev/null +++ b/platform/darwin/src/MLNPluginProtocolHandler.h @@ -0,0 +1,62 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef enum { + MLNPluginProtocolHandlerResourceKindUnknown, + MLNPluginProtocolHandlerResourceKindStyle, + MLNPluginProtocolHandlerResourceKindSource, + MLNPluginProtocolHandlerResourceKindTile, + MLNPluginProtocolHandlerResourceKindGlyphs, + MLNPluginProtocolHandlerResourceKindSpriteImage, + MLNPluginProtocolHandlerResourceKindSpriteJSON, + MLNPluginProtocolHandlerResourceKindImage +} MLNPluginProtocolHandlerResourceKind; + +typedef enum { + MLNPluginProtocolHandlerResourceLoadingMethodUnknown, + MLNPluginProtocolHandlerResourceLoadingMethodCacheOnly, + MLNPluginProtocolHandlerResourceLoadingMethodNetworkOnly, + MLNPluginProtocolHandlerResourceLoadingMethodAll +} MLNPluginProtocolHandlerResourceLoadingMethod; + +// TODO: Might make sense to add this to it's own file +@interface MLNTileData : NSObject + +// Optional Tile Data +@property NSString *tileURLTemplate; +@property int tilePixelRatio; +@property int tileX; +@property int tileY; +@property int tileZoom; + +@end + +@interface MLNPluginProtocolHandlerResource : NSObject + +@property MLNPluginProtocolHandlerResourceKind resourceKind; + +@property MLNPluginProtocolHandlerResourceLoadingMethod loadingMethod; + +@property NSString *resourceURL; + +// This is optional +@property MLNTileData *__nullable tileData; + +@end + +@interface MLNPluginProtocolHandlerResponse : NSObject + +@property NSData *data; + +@end + +@interface MLNPluginProtocolHandler : NSObject + +- (BOOL)canRequestResource:(MLNPluginProtocolHandlerResource *)resource; + +- (MLNPluginProtocolHandlerResponse *)requestResource:(MLNPluginProtocolHandlerResource *)resource; + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MLNPluginProtocolHandler.mm b/platform/darwin/src/MLNPluginProtocolHandler.mm new file mode 100644 index 000000000000..a68409d104dc --- /dev/null +++ b/platform/darwin/src/MLNPluginProtocolHandler.mm @@ -0,0 +1,26 @@ +#import "MLNPluginProtocolHandler.h" + +@implementation MLNPluginProtocolHandler + +-(BOOL)canRequestResource:(MLNPluginProtocolHandlerResource *)resource { + // Base class returns false + return NO; +} + +-(MLNPluginProtocolHandlerResponse *)requestResource:(MLNPluginProtocolHandlerResource *)resource { + // Base class does nothing + return nil; +} + + +@end + +@implementation MLNPluginProtocolHandlerResource +@end + + +@implementation MLNPluginProtocolHandlerResponse +@end + +@implementation MLNTileData +@end diff --git a/platform/darwin/src/MLNStyleFilter.h b/platform/darwin/src/MLNStyleFilter.h new file mode 100644 index 000000000000..d9a242ed6394 --- /dev/null +++ b/platform/darwin/src/MLNStyleFilter.h @@ -0,0 +1,12 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MLNStyleFilter : NSObject + +// This will filter the data passed in +- (NSData *)filterData:(NSData *)data; + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MLNStyleFilter.mm b/platform/darwin/src/MLNStyleFilter.mm new file mode 100644 index 000000000000..31c6b0f6e937 --- /dev/null +++ b/platform/darwin/src/MLNStyleFilter.mm @@ -0,0 +1,23 @@ +#import "MLNStyleFilter.h" +#include + +@interface MLNStyleFilter () { + std::shared_ptr _coreFilter; +} + +@end + +@implementation MLNStyleFilter + +-(NSData *)filterData:(NSData *)data { + // Base class does nothing but return the same data passed in + return data; +} + +// Private +-(void)setFilter:(std::shared_ptr)filter { + _coreFilter = filter; +} + + +@end diff --git a/platform/darwin/src/MLNStyleFilter_Private.h b/platform/darwin/src/MLNStyleFilter_Private.h new file mode 100644 index 000000000000..749cefcc7fd9 --- /dev/null +++ b/platform/darwin/src/MLNStyleFilter_Private.h @@ -0,0 +1,14 @@ + +#ifndef MLNStyleFilter_Private_h +#define MLNStyleFilter_Private_h + +#include +#import "MLNStyleFilter.h" + +@interface MLNStyleFilter (Private) + +- (void)setFilter:(std::shared_ptr)filter; + +@end + +#endif /* MLNStyleFilter_Private_h */ diff --git a/platform/default/BUILD.bazel b/platform/default/BUILD.bazel index e22c12db9526..7dbb1bb097ef 100644 --- a/platform/default/BUILD.bazel +++ b/platform/default/BUILD.bazel @@ -58,6 +58,7 @@ cc_library( "src/mbgl/storage/offline_database.cpp", "src/mbgl/storage/offline_download.cpp", "src/mbgl/storage/online_file_source.cpp", + "src/mbgl/storage/plugin_file_source.cpp", "src/mbgl/storage/pmtiles_file_source.cpp", "src/mbgl/storage/sqlite3.cpp", "src/mbgl/text/bidi.cpp", diff --git a/platform/default/src/mbgl/storage/main_resource_loader.cpp b/platform/default/src/mbgl/storage/main_resource_loader.cpp index 2f523f1e8d2e..208c109e5fe8 100644 --- a/platform/default/src/mbgl/storage/main_resource_loader.cpp +++ b/platform/default/src/mbgl/storage/main_resource_loader.cpp @@ -65,54 +65,67 @@ class MainResourceLoaderThread { // the sources were able to request a resource. const std::size_t tasksSize = tasks.size(); - // Waterfall resource request processing and return early once resource was requested. - if (assetFileSource && assetFileSource->canRequest(resource)) { - // Asset request - tasks[req] = assetFileSource->request(resource, callback); - } else if (mbtilesFileSource && mbtilesFileSource->canRequest(resource)) { - // Local file request - tasks[req] = mbtilesFileSource->request(resource, callback); - } else if (pmtilesFileSource && pmtilesFileSource->canRequest(resource)) { - // Local file request - tasks[req] = pmtilesFileSource->request(resource, callback); - } else if (localFileSource && localFileSource->canRequest(resource)) { - // Local file request - tasks[req] = localFileSource->request(resource, callback); - } else if (databaseFileSource && databaseFileSource->canRequest(resource)) { - // Try cache only request if needed. - if (resource.loadingMethod == Resource::LoadingMethod::CacheOnly) { - tasks[req] = databaseFileSource->request(resource, callback); - } else { - // Cache request with fallback to network with cache control - tasks[req] = databaseFileSource->request(resource, [=, this](const Response& response) { - Resource res = resource; - - // Resource is in the cache - if (!response.noContent) { - if (response.isUsable()) { - callback(response); - // Set the priority of existing resource to low if it's expired but usable. - res.setPriority(Resource::Priority::Low); - } else { - // Set prior data only if it was not returned to - // the requester. Once we get 304 response from - // the network, we will forward response to the - // requester. - res.priorData = response.data; - } + // Go through custom handlers + bool requestHandledByCustomHandler = false; + auto fm = FileSourceManager::get(); + for (auto customFileSource : fm->getCustomFileSources()) { + if (customFileSource->canRequest(resource)) { + tasks[req] = customFileSource->request(resource, callback); + requestHandledByCustomHandler = true; + break; + } + } - // Copy response fields for cache control request - res.priorModified = response.modified; - res.priorExpires = response.expires; - res.priorEtag = response.etag; - } + if (!requestHandledByCustomHandler) { + // Waterfall resource request processing and return early once resource was requested. + if (assetFileSource && assetFileSource->canRequest(resource)) { + // Asset request + tasks[req] = assetFileSource->request(resource, callback); + } else if (mbtilesFileSource && mbtilesFileSource->canRequest(resource)) { + // Local file request + tasks[req] = mbtilesFileSource->request(resource, callback); + } else if (pmtilesFileSource && pmtilesFileSource->canRequest(resource)) { + // Local file request + tasks[req] = pmtilesFileSource->request(resource, callback); + } else if (localFileSource && localFileSource->canRequest(resource)) { + // Local file request + tasks[req] = localFileSource->request(resource, callback); + } else if (databaseFileSource && databaseFileSource->canRequest(resource)) { + // Try cache only request if needed. + if (resource.loadingMethod == Resource::LoadingMethod::CacheOnly) { + tasks[req] = databaseFileSource->request(resource, callback); + } else { + // Cache request with fallback to network with cache control + tasks[req] = databaseFileSource->request(resource, [=, this](const Response& response) { + Resource res = resource; + + // Resource is in the cache + if (!response.noContent) { + if (response.isUsable()) { + callback(response); + // Set the priority of existing resource to low if it's expired but usable. + res.setPriority(Resource::Priority::Low); + } else { + // Set prior data only if it was not returned to + // the requester. Once we get 304 response from + // the network, we will forward response to the + // requester. + res.priorData = response.data; + } + + // Copy response fields for cache control request + res.priorModified = response.modified; + res.priorExpires = response.expires; + res.priorEtag = response.etag; + } - tasks[req] = requestFromNetwork(res, std::move(tasks[req])); - }); + tasks[req] = requestFromNetwork(res, std::move(tasks[req])); + }); + } + } else if (auto networkReq = requestFromNetwork(resource, nullptr)) { + // Get from the online file source + tasks[req] = std::move(networkReq); } - } else if (auto networkReq = requestFromNetwork(resource, nullptr)) { - // Get from the online file source - tasks[req] = std::move(networkReq); } // If no new tasks were added, notify client that request cannot be processed. @@ -180,6 +193,14 @@ class MainResourceLoader::Impl { } bool canRequest(const Resource& resource) const { + // Check the custom file sources + auto fm = FileSourceManager::get(); + for (auto customFileSource : fm->getCustomFileSources()) { + if (customFileSource->canRequest(resource)) { + return true; + } + } + return (assetFileSource && assetFileSource->canRequest(resource)) || (localFileSource && localFileSource->canRequest(resource)) || (databaseFileSource && databaseFileSource->canRequest(resource)) || diff --git a/platform/default/src/mbgl/storage/plugin_file_source.cpp b/platform/default/src/mbgl/storage/plugin_file_source.cpp new file mode 100644 index 000000000000..163542eeef30 --- /dev/null +++ b/platform/default/src/mbgl/storage/plugin_file_source.cpp @@ -0,0 +1,123 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mbgl { + +void PluginFileSource::setProtocolPrefix([[maybe_unused]] const std::string& protocolPrefix) {} + +class PluginFileSource::Impl { +public: + explicit Impl(const ActorRef&, const ResourceOptions& resourceOptions_, const ClientOptions& clientOptions_) + : resourceOptions(resourceOptions_.clone()), + clientOptions(clientOptions_.clone()) {} + + OnRequestResource _requestFunction; + void setOnRequestResourceFunction(OnRequestResource requestFunction) { _requestFunction = requestFunction; } + + void request(const Resource& resource, const ActorRef& req) { + Response response; + if (_requestFunction) { + response = _requestFunction(resource); + } else { + response.error = std::make_unique( + Response::Error::Reason::Other, std::string("Custom Protocol Handler Not Configured Correctly")); + } + req.invoke(&FileSourceRequest::setResponse, response); + } + + void setResourceOptions(ResourceOptions options) { + std::lock_guard lock(resourceOptionsMutex); + resourceOptions = options; + } + + ResourceOptions getResourceOptions() { + std::lock_guard lock(resourceOptionsMutex); + return resourceOptions.clone(); + } + + void setClientOptions(ClientOptions options) { + std::lock_guard lock(clientOptionsMutex); + clientOptions = options; + } + + ClientOptions getClientOptions() { + std::lock_guard lock(clientOptionsMutex); + return clientOptions.clone(); + } + +private: + mutable std::mutex resourceOptionsMutex; + mutable std::mutex clientOptionsMutex; + ResourceOptions resourceOptions; + ClientOptions clientOptions; +}; + +void PluginFileSource::setOnCanRequestFunction(OnCanRequestResource requestFunction) { + _onCanRequestResourceFunction = requestFunction; +} + +void PluginFileSource::setOnRequestResourceFunction(OnRequestResource requestFunction) { + impl.get()->actor().invoke(&Impl::setOnRequestResourceFunction, requestFunction); +} + +PluginFileSource::PluginFileSource(const ResourceOptions& resourceOptions, const ClientOptions& clientOptions) + : impl(std::make_unique>( + util::makeThreadPrioritySetter(platform::EXPERIMENTAL_THREAD_PRIORITY_FILE), + "PluginFileSource", + resourceOptions.clone(), + clientOptions.clone())) {} + +PluginFileSource::~PluginFileSource() = default; + +std::unique_ptr PluginFileSource::request(const Resource& resource, Callback callback) { + auto req = std::make_unique(std::move(callback)); + + impl->actor().invoke(&Impl::request, resource, req->actor()); + + return req; +} + +bool PluginFileSource::canRequest(const Resource& resource) const { + if (_onCanRequestResourceFunction) { + return _onCanRequestResourceFunction(resource); + } + return false; +} + +void PluginFileSource::pause() { + impl->pause(); +} + +void PluginFileSource::resume() { + impl->resume(); +} + +void PluginFileSource::setResourceOptions(ResourceOptions options) { + impl->actor().invoke(&Impl::setResourceOptions, options.clone()); +} + +ResourceOptions PluginFileSource::getResourceOptions() { + return impl->actor().ask(&Impl::getResourceOptions).get(); +} + +void PluginFileSource::setClientOptions(ClientOptions options) { + impl->actor().invoke(&Impl::setClientOptions, options.clone()); +} + +ClientOptions PluginFileSource::getClientOptions() { + return impl->actor().ask(&Impl::getClientOptions).get(); +} + +} // namespace mbgl diff --git a/platform/ios/app/MBXViewController.mm b/platform/ios/app/MBXViewController.mm index 4c13a767ea51..f344302088ca 100644 --- a/platform/ios/app/MBXViewController.mm +++ b/platform/ios/app/MBXViewController.mm @@ -27,6 +27,8 @@ #import "PluginLayerExample.h" #import "PluginLayerExampleMetalRendering.h" #import "MLNPluginStyleLayer.h" +#import "PluginProtocolExample.h" +#import "StyleFilterExample.h" static const CLLocationCoordinate2D WorldTourDestinations[] = { { .latitude = 38.8999418, .longitude = -77.033996 }, @@ -281,6 +283,9 @@ -(void)addPluginLayers { [self.mapView addPluginLayerType:[PluginLayerExample class]]; [self.mapView addPluginLayerType:[PluginLayerExampleMetalRendering class]]; + [self.mapView addPluginProtocolHandler:[PluginProtocolExample class]]; + + [self.mapView addStyleFilter:[[StyleFilterExample alloc] init]]; } @@ -2330,19 +2335,22 @@ - (void)setStyles self.styleNames = [NSMutableArray array]; self.styleURLs = [NSMutableArray array]; - - - /// Style that does not require an `apiKey` nor any further configuration [self.styleNames addObject:@"MapLibre Basic"]; [self.styleURLs addObject:[NSURL URLWithString:@"https://demotiles.maplibre.org/style.json"]]; - /// This is hte same style as above but copied locally and the three instances of the metal plug-in layer added to the style + /// This is the same style as above but copied locally and the three instances of the metal plug-in layer added to the style /// Look for "type": "plugin-layer-metal-rendering" in the PluginLayerTestStyle.json for an example of how the layer is defined [self.styleNames addObject:@"MapLibre Basic - Local With Plugin"]; NSURL *url = [[NSBundle mainBundle] URLForResource:@"PluginLayerTestStyle.json" withExtension:nil]; [self.styleURLs addObject:url]; + /// This is the same style as above, but using the plugin protocol to actually load the style + [self.styleNames addObject:@"MapLibre Basic - Local With Plugin Loader"]; + NSURL *pluginstyleurl = [NSURL URLWithString:@"pluginProtocol://PluginLayerTestStyle.json"]; + [self.styleURLs addObject:pluginstyleurl]; + + /// Add MapLibre Styles if an `apiKey` exists NSString* apiKey = [MLNSettings apiKey]; if (apiKey.length) diff --git a/platform/ios/src/MLNMapView.h b/platform/ios/src/MLNMapView.h index 1e800fafe188..3758303e8d4c 100644 --- a/platform/ios/src/MLNMapView.h +++ b/platform/ios/src/MLNMapView.h @@ -20,6 +20,7 @@ NS_ASSUME_NONNULL_BEGIN @class MLNScaleBar; @class MLNShape; @class MLNPluginLayer; +@class MLNStyleFilter; @protocol MLNMapViewDelegate; @protocol MLNAnnotation; @@ -2276,6 +2277,16 @@ vertically on the map. */ - (void)addPluginLayerType:(Class)pluginLayerClass; +/** + Adds a plug-in protocol handler that is external to this library + */ +- (void)addPluginProtocolHandler:(Class)pluginProtocolHandlerClass; + +/** + Adds a style filter to the map view + */ +- (void)addStyleFilter:(MLNStyleFilter *)styleFilter; + @end NS_ASSUME_NONNULL_END diff --git a/platform/ios/src/MLNMapView.mm b/platform/ios/src/MLNMapView.mm index 23313088fa14..8e62beaeb131 100644 --- a/platform/ios/src/MLNMapView.mm +++ b/platform/ios/src/MLNMapView.mm @@ -30,9 +30,13 @@ #include #include #include +#include +#include +#include #include #include #include +#include #import "Mapbox.h" #import "MLNShape_Private.h" @@ -80,8 +84,11 @@ #import "MLNActionJournalOptions_Private.h" #import "MLNMapProjection.h" #import "MLNPluginLayer.h" +#import "MLNPluginProtocolHandler.h" #import "MLNStyleLayerManager.h" #include "MLNPluginStyleLayer_Private.h" +#include "MLNStyleFilter.h" +#include "MLNStyleFilter_Private.h" #include #include @@ -452,8 +459,12 @@ @interface MLNMapView () triggerRepaint(); } +-(MLNPluginLayerTileFeature *)featureFromCore:(std::shared_ptr)feature { + + MLNPluginLayerTileFeature *tempResult = [[MLNPluginLayerTileFeature alloc] init]; + + NSMutableDictionary *tileProperties = [NSMutableDictionary dictionary]; + for (auto p: feature->_featureProperties) { + NSString *key = [NSString stringWithUTF8String:p.first.c_str()]; + NSString *value = [NSString stringWithUTF8String:p.second.c_str()]; + [tileProperties setObject:value forKey:key]; + } + + tempResult.featureProperties = [NSDictionary dictionaryWithDictionary:tileProperties]; + + NSMutableArray *featureCoordinates = [NSMutableArray array]; + for (auto & coordinateCollection: feature->_featureCoordinates) { + + for (auto & coordinate: coordinateCollection._coordinates) { + CLLocationCoordinate2D c = CLLocationCoordinate2DMake(coordinate._lat, coordinate._lon); + NSValue *value = [NSValue valueWithBytes:&c objCType:@encode(CLLocationCoordinate2D)]; + [featureCoordinates addObject:value]; + } + + } + // TODO: Need to figure out how we're going to handle multiple coordinate groups/etc + if ([featureCoordinates count] > 0) { + tempResult.featureCoordinates = [NSArray arrayWithArray:featureCoordinates]; + } + + tempResult.featureID = [NSString stringWithUTF8String:feature->_featureID.c_str()]; + + return tempResult; +} + +-(NSString *)tileIDToString:(mbgl::OverscaledTileID &)tileID { + NSString *tempResult = [NSString stringWithFormat:@"%i,%i,%i", tileID.canonical.z, tileID.canonical.x, tileID.canonical.y]; + return tempResult; +} + /** Adds a plug-in layer that is external to this library */ @@ -7688,6 +7737,13 @@ -(void)addPluginLayerType:(Class)pluginLayerClass { pass3D = mbgl::style::LayerTypeInfo::Pass3D::Required; } + + // If we read tile features, then we need to set these things + if (capabilities.supportsReadingTileFeatures) { + tileKind = mbgl::style::LayerTypeInfo::TileKind::Geometry; + source = mbgl::style::LayerTypeInfo::Source::Required; + } + auto factory = std::make_unique(layerType, source, pass3D, @@ -7699,6 +7755,7 @@ -(void)addPluginLayerType:(Class)pluginLayerClass { __weak MLNMapView *weakMapView = self; Class layerClass = pluginLayerClass; + factory->supportsFeatureCollectionBuckets = capabilities.supportsReadingTileFeatures; factory->setOnLayerCreatedEvent([layerClass, weakMapView, pluginLayerClass](mbgl::style::PluginLayer *pluginLayer) { //NSLog(@"Creating Plugin Layer: %@", layerClass); @@ -7792,6 +7849,58 @@ -(void)addPluginLayerType:(Class)pluginLayerClass { } }); + // If this layer can read tile features, then setup that lambda + if (capabilities.supportsReadingTileFeatures) { + + pluginLayerImpl->setFeatureCollectionLoadedFunction([weakPlugInLayer, weakMapView](const std::shared_ptr featureCollection) { + + @autoreleasepool { + + MLNPluginLayerTileFeatureCollection *collection = [[MLNPluginLayerTileFeatureCollection alloc] init]; + + // Add the features + NSMutableArray *featureList = [NSMutableArray arrayWithCapacity:featureCollection->_features.size()]; + for (auto f: featureCollection->_features) { + [featureList addObject:[weakMapView featureFromCore:f]]; + } + collection.features = [NSArray arrayWithArray:featureList]; + collection.tileID = [weakMapView tileIDToString:featureCollection->_featureCollectionTileID]; + + + [weakPlugInLayer onFeatureCollectionLoaded:collection]; + + } + + }); + + pluginLayerImpl->setFeatureCollectionUnloadedFunction([weakPlugInLayer, weakMapView](const std::shared_ptr featureCollection) { + + @autoreleasepool { + + // TODO: Map these collections to local vars and maybe don't keep recreating it + + MLNPluginLayerTileFeatureCollection *collection = [[MLNPluginLayerTileFeatureCollection alloc] init]; + + // Add the features + NSMutableArray *featureList = [NSMutableArray arrayWithCapacity:featureCollection->_features.size()]; + for (auto f: featureCollection->_features) { + [featureList addObject:[weakMapView featureFromCore:f]]; + } + collection.features = [NSArray arrayWithArray:featureList]; + collection.tileID = [weakMapView tileIDToString:featureCollection->_featureCollectionTileID]; + + [weakPlugInLayer onFeatureCollectionUnloaded:collection]; + + } + + }); + + + + + + } + }); // TODO: Same question as above. Do we ever want to have a core only layer type? @@ -7801,6 +7910,169 @@ -(void)addPluginLayerType:(Class)pluginLayerClass { } +- (MLNPluginProtocolHandlerResource *)resourceFromCoreResource:(const mbgl::Resource &)resource { + + MLNPluginProtocolHandlerResource *tempResult = [[MLNPluginProtocolHandlerResource alloc] init]; + + // The URL of the request + tempResult.resourceURL = [NSString stringWithUTF8String:resource.url.c_str()]; + + // The kind of request + switch (resource.kind) { + case mbgl::Resource::Kind::Style: + tempResult.resourceKind = MLNPluginProtocolHandlerResourceKindStyle; + break; + case mbgl::Resource::Kind::Source: + tempResult.resourceKind = MLNPluginProtocolHandlerResourceKindSource; + break; + case mbgl::Resource::Kind::Tile: + tempResult.resourceKind = MLNPluginProtocolHandlerResourceKindTile; + break; + case mbgl::Resource::Kind::Glyphs: + tempResult.resourceKind = MLNPluginProtocolHandlerResourceKindGlyphs; + break; + case mbgl::Resource::Kind::SpriteImage: + tempResult.resourceKind = MLNPluginProtocolHandlerResourceKindSpriteImage; + break; + case mbgl::Resource::Kind::Image: + tempResult.resourceKind = MLNPluginProtocolHandlerResourceKindImage; + break; + case mbgl::Resource::Kind::SpriteJSON: + tempResult.resourceKind = MLNPluginProtocolHandlerResourceKindSpriteJSON; + break; + default: + tempResult.resourceKind = MLNPluginProtocolHandlerResourceKindUnknown; + break; + } + + // The loading method + if (resource.loadingMethod == mbgl::Resource::LoadingMethod::CacheOnly) { + tempResult.loadingMethod = MLNPluginProtocolHandlerResourceLoadingMethodCacheOnly; + } else if (resource.loadingMethod == mbgl::Resource::LoadingMethod::NetworkOnly) { + tempResult.loadingMethod = MLNPluginProtocolHandlerResourceLoadingMethodNetworkOnly; + } else if (resource.loadingMethod == mbgl::Resource::LoadingMethod::All) { + tempResult.loadingMethod = MLNPluginProtocolHandlerResourceLoadingMethodAll; + } + + if (resource.tileData) { + auto td = *resource.tileData; + MLNTileData *tileData = [[MLNTileData alloc] init]; + tileData.tileURLTemplate = [NSString stringWithUTF8String:td.urlTemplate.c_str()]; + tileData.tilePixelRatio = td.pixelRatio; + tileData.tileX = td.x; + tileData.tileY = td.y; + tileData.tileZoom = td.z; + tempResult.tileData = tileData; + } + + // TODO: Figure out which other properties from resource should be passed along here +/* + Usage usage{Usage::Online}; + Priority priority{Priority::Regular}; + std::optional> dataRange = std::nullopt; + std::optional priorModified = std::nullopt; + std::optional priorExpires = std::nullopt; + std::optional priorEtag = std::nullopt; + std::shared_ptr priorData; + Duration minimumUpdateInterval{Duration::zero()}; + StoragePolicy storagePolicy{StoragePolicy::Permanent}; + */ + + return tempResult; + +} + +- (void)addPluginProtocolHandler:(Class)pluginProtocolHandlerClass { + + + MLNPluginProtocolHandler *handler = [[pluginProtocolHandlerClass alloc] init]; + if (!self.pluginProtocols) { + self.pluginProtocols = [NSMutableArray array]; + } + [self.pluginProtocols addObject:handler]; + + // TODO: Unclear if any of these options are needed for plugins + mbgl::ResourceOptions resourceOptions; + + // TODO: Unclear if any of the properties on clientOptions need to be set + mbgl::ClientOptions clientOptions; + + // Use weak here so there isn't a retain cycle + __weak MLNPluginProtocolHandler *weakHandler = handler; + __weak MLNMapView *weakSelf = self; + + std::shared_ptr pluginSource = std::make_shared(resourceOptions, clientOptions); + pluginSource->setOnRequestResourceFunction([weakHandler, weakSelf](const mbgl::Resource &resource) -> mbgl::Response { + mbgl::Response tempResult; + + __strong MLNPluginProtocolHandler *strongHandler = weakHandler; + if (strongHandler) { + + MLNPluginProtocolHandlerResource *res = [weakSelf resourceFromCoreResource:resource]; + + // TODO: Figure out what other fields in response need to be passed back from requestResource + MLNPluginProtocolHandlerResponse *response = [strongHandler requestResource:res]; + if (response.data) { + tempResult.data = std::make_shared((const char*)[response.data bytes], + [response.data length]); + } + } + + return tempResult; + + }); + + pluginSource->setOnCanRequestFunction([weakHandler, weakSelf](const mbgl::Resource &resource) -> bool{ + @autoreleasepool { + __strong MLNPluginProtocolHandler *strongHandler = weakHandler; + if (!strongHandler) { + return false; + } + + MLNPluginProtocolHandlerResource *res = [weakSelf resourceFromCoreResource:resource]; + BOOL tempResult = [strongHandler canRequestResource:res]; + + return tempResult; + + } + }); + + auto fileSourceManager = mbgl::FileSourceManager::get(); + fileSourceManager->registerCustomFileSource(pluginSource); + +} + +-(void)addStyleFilter:(MLNStyleFilter *)styleFilter { + + if (!self.styleFilters) { + self.styleFilters = [NSMutableArray array]; + } + [self.styleFilters addObject:styleFilter]; + + auto coreStyleFilter = std::make_shared(); + coreStyleFilter->_filterStyleFunction = [styleFilter](const std::string &filterData) -> const std::string { + + std::string tempResult; + + @autoreleasepool { + NSData *sourceData = [NSData dataWithBytesNoCopy:(void *)filterData.data() + length:filterData.size() + freeWhenDone:NO]; + NSData *filteredData = [styleFilter filterData:sourceData]; + tempResult = std::string((const char*)[filteredData bytes], [filteredData length]); + + } + return tempResult; + }; + + // Set the ivar + [styleFilter setFilter:coreStyleFilter]; + + _mbglMap->getStyle().addStyleFilter(coreStyleFilter); + +} + + - (NSArray*)getActionJournalLogFiles { const auto& actionJournal = _mbglMap->getActionJournal(); diff --git a/src/mbgl/plugin/feature_collection.cpp b/src/mbgl/plugin/feature_collection.cpp new file mode 100644 index 000000000000..46d2cdf7e994 --- /dev/null +++ b/src/mbgl/plugin/feature_collection.cpp @@ -0,0 +1 @@ +#include "feature_collection.hpp" diff --git a/src/mbgl/plugin/feature_collection.hpp b/src/mbgl/plugin/feature_collection.hpp new file mode 100644 index 000000000000..6520105f3294 --- /dev/null +++ b/src/mbgl/plugin/feature_collection.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include + +#include +#include +#include +#include + +namespace mbgl { + +namespace plugin { + +class FeatureCoordinate { +public: + FeatureCoordinate(double lat, double lon) + : _lat(lat), + _lon(lon) {} + double _lat = 0; + double _lon = 0; + double _tileX = 0; // Tile coord + double _tileY = 0; // Tile coord +}; + +// This is a list of coordinates. Broken out into its own class because +// a raw bucket feature can have an array of these +class FeatureCoordinateCollection { +public: + std::vector _coordinates; +}; + +class Feature { +public: + Feature() {}; + enum class FeatureType { + FeatureTypeUnknown, + FeatureTypePoint, + FeatureTypeLine, + FeatureTypePolygon + }; + FeatureType _featureType = FeatureType::FeatureTypeUnknown; + std::map _featureProperties; + std::vector _featureCoordinates; + std::string _featureID; // Unique id from the data source for this +}; + +class FeatureCollection { +public: + FeatureCollection(OverscaledTileID tileID) + : _featureCollectionTileID(tileID) {}; + std::vector> _features; + OverscaledTileID _featureCollectionTileID; +}; + +} // namespace plugin + +} // namespace mbgl diff --git a/src/mbgl/plugin/feature_collection_bucket.cpp b/src/mbgl/plugin/feature_collection_bucket.cpp new file mode 100644 index 000000000000..09d24dbbd09b --- /dev/null +++ b/src/mbgl/plugin/feature_collection_bucket.cpp @@ -0,0 +1,132 @@ +#include +#include +#include +#include + +using namespace mbgl; + +FeatureCollectionBucket::~FeatureCollectionBucket() {} + +FeatureCollectionBucket::FeatureCollectionBucket(const BucketParameters& bucketParameters, + const std::vector>& layers) + : _tileID(bucketParameters.tileID) { + _layers = layers; + _featureCollection = std::make_shared(bucketParameters.tileID); +} + +void geometryToLatLon(const GeometryCoordinate& coord, + int tileX, + int tileY, + int zoom, + double& lat, + double& lon, + int extent = 8192, + int tileSize = 512) { + double px = coord.x / double(extent); + double py = coord.y / double(extent); + + double worldPixelX = (tileX + px) * tileSize; + double worldPixelY = (tileY + py) * tileSize; + + double worldSize = tileSize * (1 << zoom); + + double mercX = worldPixelX / worldSize * 2.0 - 1.0; + double mercY = 1.0 - worldPixelY / worldSize * 2.0; + + lon = mercX * 180.0; + lat = 180.0 / M_PI * (2.0 * atan(exp(mercY * M_PI)) - M_PI / 2.0); +} + +std::string toString(FeatureIdentifier& v) { + auto ti = v.which(); + if (ti == 1) { + return std::to_string(v.get()); + } else if (ti == 2) { + return std::to_string(v.get()); + } else if (ti == 3) { + return v.get(); + } + return ""; +} + +void FeatureCollectionBucket::addFeature(const GeometryTileFeature& tileFeature, + const GeometryCollection& geometeryCollection, + [[maybe_unused]] const mbgl::ImagePositions& imagePositions, + [[maybe_unused]] const PatternLayerMap& patternLayerMap, + [[maybe_unused]] std::size_t size, + const CanonicalTileID& tileID) { + std::shared_ptr tempFeature = std::make_shared(); + + auto featureID = tileFeature.getID(); + auto featureIDString = toString(featureID); + tempFeature->_featureID = featureIDString; + + switch (tileFeature.getType()) { + case FeatureType::Point: + tempFeature->_featureType = plugin::Feature::FeatureType::FeatureTypePoint; + break; + case FeatureType::Unknown: + break; + case FeatureType::LineString: + tempFeature->_featureType = plugin::Feature::FeatureType::FeatureTypeLine; + break; + case FeatureType::Polygon: + tempFeature->_featureType = plugin::Feature::FeatureType::FeatureTypePolygon; + break; + } + + auto pm = tileFeature.getProperties(); + for (auto p : pm) { + auto name = p.first; + mapbox::feature::value value = p.second; + + if (auto iVal = value.getInt()) { + tempFeature->_featureProperties[name] = std::to_string(*iVal); + } else if (auto uIVal = value.getUint()) { + tempFeature->_featureProperties[name] = std::to_string(*uIVal); + } else if (auto s = value.getString()) { + tempFeature->_featureProperties[name] = *s; + } else if (auto d = value.getDouble()) { + tempFeature->_featureProperties[name] = std::to_string(*d); + } else if (auto b = value.getBool()) { + tempFeature->_featureProperties[name] = std::to_string(*b); + // TODO: Add array type + // } else if (auto a = value.getArray()) { + // tempFeature->_featureProperties[name] = std::to_string(*b); + } + } + + LatLngBounds b(tileID); + + for (const auto& g : geometeryCollection) { + // g is GeometryCoordinates + plugin::FeatureCoordinateCollection c; + for (std::size_t i = 0, len = g.size(); i < len; i++) { + const GeometryCoordinate& p1 = g[i]; + double lat = 0; + double lon = 0; + geometryToLatLon(p1, tileID.x, tileID.y, tileID.z, lat, lon); + c._coordinates.push_back(plugin::FeatureCoordinate(lat, lon)); + } + tempFeature->_featureCoordinates.push_back(c); + } + + _featureCollection->_features.push_back(tempFeature); +} + +bool FeatureCollectionBucket::hasData() const { + return _featureCollection->_features.size() > 0; +} + +void FeatureCollectionBucket::upload(gfx::UploadPass&) { + uploaded = true; +} + +float FeatureCollectionBucket::getQueryRadius(const RenderLayer&) const { + return 0; +} + +void FeatureCollectionBucket::update(const FeatureStates&, + const GeometryTileLayer&, + const std::string&, + const ImagePositions&) {} diff --git a/src/mbgl/plugin/feature_collection_bucket.hpp b/src/mbgl/plugin/feature_collection_bucket.hpp new file mode 100644 index 000000000000..90ae88bb738f --- /dev/null +++ b/src/mbgl/plugin/feature_collection_bucket.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace mbgl { + +class BucketParameters; +class RenderFillLayer; + +class FeatureCollectionBucket final : public Bucket { +public: + ~FeatureCollectionBucket() override; + + FeatureCollectionBucket(const BucketParameters&, const std::vector>&); + + void addFeature(const GeometryTileFeature&, + const GeometryCollection&, + const mbgl::ImagePositions&, + const PatternLayerMap&, + std::size_t, + const CanonicalTileID&) override; + + bool hasData() const override; + + void upload(gfx::UploadPass&) override; + + float getQueryRadius(const RenderLayer&) const override; + + void update(const FeatureStates&, const GeometryTileLayer&, const std::string&, const ImagePositions&) override; + + // The tile ID + OverscaledTileID _tileID; + + // Feature collection is an list of features + std::shared_ptr _featureCollection = nullptr; + std::vector> _layers; +}; + +} // namespace mbgl diff --git a/src/mbgl/plugin/plugin_file_source.hpp b/src/mbgl/plugin/plugin_file_source.hpp new file mode 100644 index 000000000000..7f6acc686156 --- /dev/null +++ b/src/mbgl/plugin/plugin_file_source.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include + +namespace mbgl { + +namespace util { +template +class Thread; +} // namespace util + +class PluginFileSourceRequest {}; + +class PluginFileSourceResponse {}; + +class PluginFileSource : public FileSource { +public: + PluginFileSource(const ResourceOptions &resourceOptions, const ClientOptions &clientOptions); + ~PluginFileSource() override; + + using OnCanRequestResource = std::function; + using OnRequestResource = std::function; + + void setOnCanRequestFunction(OnCanRequestResource requestFunction); + void setOnRequestResourceFunction(OnRequestResource requestFunction); + + std::unique_ptr request(const Resource &, Callback) override; + bool canRequest(const Resource &) const override; + void pause() override; + void resume() override; + + void setProtocolPrefix(const std::string &); + + void setResourceOptions(ResourceOptions) override; + ResourceOptions getResourceOptions() override; + + void setClientOptions(ClientOptions) override; + ClientOptions getClientOptions() override; + +private: + OnCanRequestResource _onCanRequestResourceFunction; + class Impl; + std::unique_ptr> impl; +}; + +} // namespace mbgl diff --git a/src/mbgl/plugin/plugin_layer.hpp b/src/mbgl/plugin/plugin_layer.hpp index f8f95a4c4c70..07fd90228cad 100644 --- a/src/mbgl/plugin/plugin_layer.hpp +++ b/src/mbgl/plugin/plugin_layer.hpp @@ -3,9 +3,12 @@ #include #include #include +#include namespace mbgl { +class RawBucketFeature; + namespace style { class PluginLayer final : public Layer { @@ -28,6 +31,10 @@ class PluginLayer final : public Layer { using OnRenderLayer = std::function; using OnUpdateLayer = std::function; using OnUpdateLayerProperties = std::function; + using OnFeatureCollectionLoaded = + std::function featureCollection)>; + using OnFeatureCollectionUnloaded = + std::function featureCollection)>; void* _platformReference = nullptr; diff --git a/src/mbgl/plugin/plugin_layer_factory.cpp b/src/mbgl/plugin/plugin_layer_factory.cpp index f33fb701f29f..8fc2d8b1027f 100644 --- a/src/mbgl/plugin/plugin_layer_factory.cpp +++ b/src/mbgl/plugin/plugin_layer_factory.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -66,7 +67,7 @@ PluginLayerFactory::PluginLayerFactory(std::string& layerType, mbgl::style::LayerTypeInfo::TileKind tileKind) : _layerTypeInfo(getDefaultInfo()), _layerType(layerType) { - _layerTypeInfo.type = layerType.c_str(); + _layerTypeInfo.type = _layerType.c_str(); plugins::NonConstLayerTypeInfo* lti = (plugins::NonConstLayerTypeInfo*)&_layerTypeInfo; lti->source = (plugins::NonConstLayerTypeInfo::Source)((int)source); lti->pass3d = (plugins::NonConstLayerTypeInfo::Pass3D)((int)pass3D); @@ -149,12 +150,18 @@ std::unique_ptr PluginLayerFactory::createLayer(const std::string& } } - std::string source = "source"; + std::string sourceStr = "pluginLayerNoSource"; + if (supportsFeatureCollectionBuckets) { + auto source = getSource(value); + if (source.has_value()) { + sourceStr = source.value(); + } + } - auto tempResult = std::unique_ptr(new (std::nothrow) - style::PluginLayer(id, source, _layerTypeInfo, layerProperties - //,*customProperties - )); + auto tempResult = std::unique_ptr(new (std::nothrow) style::PluginLayer( + id, sourceStr, _layerTypeInfo, layerProperties + //,*customProperties + )); if (_onLayerCreated != nullptr) { auto layerRaw = tempResult.get(); @@ -172,7 +179,9 @@ std::unique_ptr PluginLayerFactory::createLayer(const std::string& std::unique_ptr PluginLayerFactory::createBucket( [[maybe_unused]] const BucketParameters& parameters, [[maybe_unused]] const std::vector>& layers) noexcept { - // Returning null for now. Not using buckets in plug-ins yet. + if (supportsFeatureCollectionBuckets) { + return std::make_unique(parameters, layers); + } return nullptr; } diff --git a/src/mbgl/plugin/plugin_layer_factory.hpp b/src/mbgl/plugin/plugin_layer_factory.hpp index 0a976b37a9f1..a0acc5936afd 100644 --- a/src/mbgl/plugin/plugin_layer_factory.hpp +++ b/src/mbgl/plugin/plugin_layer_factory.hpp @@ -23,6 +23,9 @@ class PluginLayerFactory : public LayerFactory { mbgl::style::LayerTypeInfo::CrossTileIndex crossTileIndex, mbgl::style::LayerTypeInfo::TileKind tileKind); + // Set to false, but if the caller wants to support on features loaded, then set this to true + bool supportsFeatureCollectionBuckets = false; + using OnLayerCreatedEvent = std::function; void setOnLayerCreatedEvent(OnLayerCreatedEvent onLayerCreated) { _onLayerCreated = onLayerCreated; } diff --git a/src/mbgl/plugin/plugin_layer_impl.hpp b/src/mbgl/plugin/plugin_layer_impl.hpp index 512cec427a33..e79e75320d77 100644 --- a/src/mbgl/plugin/plugin_layer_impl.hpp +++ b/src/mbgl/plugin/plugin_layer_impl.hpp @@ -99,6 +99,14 @@ class PluginLayer::Impl : public Layer::Impl { _updateLayerPropertiesFunction = updateLayerPropertiesFunction; } + void setFeatureCollectionLoadedFunction(OnFeatureCollectionLoaded featureCollectionLoadedFunction) { + _featureCollectionLoadedFunction = featureCollectionLoadedFunction; + } + + void setFeatureCollectionUnloadedFunction(OnFeatureCollectionUnloaded featureCollectionUnloadedFunction) { + _featureCollectionUnloadedFunction = featureCollectionUnloadedFunction; + } + //! The property manager handles all of the custom properties for this layer type / instance PluginLayerPropertyManager _propertyManager; @@ -113,6 +121,12 @@ class PluginLayer::Impl : public Layer::Impl { //! Optional: Called when the layer properties change. The properties are passed as JSON for now OnUpdateLayerProperties _updateLayerPropertiesFunction; + //! Optional: Called when a feature collection is loaded + OnFeatureCollectionLoaded _featureCollectionLoadedFunction; + + //! Optional: Called when a feature colleciton is unloaded from the scene (tile goes out of scene/etc) + OnFeatureCollectionUnloaded _featureCollectionUnloadedFunction; + private: LayerTypeInfo _layerTypeInfo; std::string _layerProperties; diff --git a/src/mbgl/plugin/plugin_layer_render.cpp b/src/mbgl/plugin/plugin_layer_render.cpp index 5021fa33f82f..1368d5694ecb 100644 --- a/src/mbgl/plugin/plugin_layer_render.cpp +++ b/src/mbgl/plugin/plugin_layer_render.cpp @@ -13,8 +13,8 @@ #include #include #include - -#include +#include +#include using namespace mbgl; @@ -85,37 +85,100 @@ void RenderPluginLayer::update([[maybe_unused]] gfx::ShaderRegistry& shaderRegis [[maybe_unused]] const std::shared_ptr& updateParameters, [[maybe_unused]] const RenderTree& renderTree, [[maybe_unused]] UniqueChangeRequestVec& changes) { + auto pluginLayer = static_cast(*baseImpl); + + std::vector removedTiles; + bool removeAllTiles = ((renderTiles == nullptr) || (renderTiles->empty())); + + // Get list of tiles to remove and then remove them + for (auto currentCollection : _featureCollectionByTile) { + if (removeAllTiles || !hasRenderTile(currentCollection.first)) { + removedTiles.push_back(currentCollection.first); + } + } + if (removedTiles.size() > 0) { + for (auto tileID : removedTiles) { + auto featureCollection = _featureCollectionByTile[tileID]; + if (pluginLayer._featureCollectionUnloadedFunction) { + pluginLayer._featureCollectionUnloadedFunction(featureCollection); + } + _featureCollectionByTile.erase(tileID); + } + } + + if (renderTiles) { + if (!renderTiles->empty()) { + auto drawPass = RenderPass::Pass3D; + + // If we're reading feature collections, go through + // and notify the plugin of any new feature collections + for (const RenderTile& tile : *renderTiles) { + const auto& tileID = tile.getOverscaledTileID(); + + const auto* optRenderData = getRenderDataForPass(tile, drawPass); + if (!optRenderData || !optRenderData->bucket || !optRenderData->bucket->hasData()) { + removeTile(drawPass, tileID); + continue; + } + const auto& renderData = *optRenderData; + auto& bucket = static_cast(*renderData.bucket); + auto featureCollection = bucket._featureCollection; + if (featureCollection == nullptr) { + continue; + } + + // See if we already have this tile's feature collection + if (_featureCollectionByTile.contains(tileID)) { + continue; + } + + _featureCollectionByTile[tileID] = featureCollection; + + // static_cast(*baseImpl); + // auto layer = static_cast(*renderData.layerProperties->baseImpl); + if (pluginLayer._featureCollectionLoadedFunction) { + if (featureCollection != nullptr) { + pluginLayer._featureCollectionLoadedFunction(featureCollection); + } + } + } + } + } + // create layer group if (!layerGroup) { if (auto layerGroup_ = context.createLayerGroup(layerIndex, /*initialCapacity=*/1, getID())) { setLayerGroup(std::move(layerGroup_), changes); } } - + auto* localLayerGroup = static_cast(layerGroup.get()); - + // TODO: Implement this bool hostChanged = false; - + // create drawable - if (localLayerGroup->getDrawableCount() == 0 || hostChanged) { - localLayerGroup->clearDrawables(); - - // create tweaker - auto tweaker = std::make_shared(this); - - // create empty drawable using a builder - std::unique_ptr builder = context.createDrawableBuilder(getID()); - auto& drawable = builder->getCurrentDrawable(true); - drawable->setIsCustom(true); - drawable->setRenderPass(RenderPass::Translucent); - - // assign tweaker to drawable - drawable->addTweaker(tweaker); - - // add drawable to layer group - localLayerGroup->addDrawable(std::move(drawable)); - ++stats.drawablesAdded; + if (pluginLayer.getTypeInfo()->source != mbgl::style::LayerTypeInfo::Source::Required) { + if (localLayerGroup->getDrawableCount() == 0 || hostChanged) { + localLayerGroup->clearDrawables(); + + // create tweaker + auto tweaker = std::make_shared(this); + + // create empty drawable using a builder + std::unique_ptr builder = context.createDrawableBuilder(getID()); + auto& drawable = builder->getCurrentDrawable(true); + drawable->setIsCustom(true); + drawable->setRenderPass(RenderPass::Translucent); + + // assign tweaker to drawable + drawable->addTweaker(tweaker); + + // add drawable to layer group + localLayerGroup->addDrawable(std::move(drawable)); + ++stats.drawablesAdded; + } } } @@ -128,6 +191,13 @@ void RenderPluginLayer::render(PaintParameters& paintParameters) { } void RenderPluginLayer::prepare(const LayerPrepareParameters& layerParameters) { + // This check is here because base prepare will assert on these and crash + if (layerParameters.source != nullptr) { + if (layerParameters.source->isEnabled()) { + RenderLayer::prepare(layerParameters); + } + } + if (_updateFunction) { _updateFunction(layerParameters); } @@ -161,8 +231,13 @@ void RenderPluginLayer::evaluate(const PropertyEvaluationParameters& parameters) } std::string jsonProperties = pm.propertiesAsJSON(); - i->_updateLayerPropertiesFunction(jsonProperties); + + auto properties = makeMutable( + staticImmutableCast(baseImpl)); + passes = RenderPass::Pass3D; + properties->renderPasses = mbgl::underlying_type(passes); + evaluatedProperties = std::move(properties); } bool RenderPluginLayer::hasTransition() const { @@ -188,7 +263,8 @@ void RenderPluginLayer::layerChanged([[maybe_unused]] const TransitionParameters /// Remove all drawables for the tile from the layer group /// @return The number of drawables actually removed. -std::size_t RenderPluginLayer::removeTile([[maybe_unused]] RenderPass, [[maybe_unused]] const OverscaledTileID&) { +std::size_t RenderPluginLayer::removeTile([[maybe_unused]] RenderPass renderPass, + [[maybe_unused]] const OverscaledTileID& tileID) { return 0; } diff --git a/src/mbgl/plugin/plugin_layer_render.hpp b/src/mbgl/plugin/plugin_layer_render.hpp index cc2ea2db7a6e..f7ccdc70d8c8 100644 --- a/src/mbgl/plugin/plugin_layer_render.hpp +++ b/src/mbgl/plugin/plugin_layer_render.hpp @@ -5,6 +5,7 @@ #include #include +#include #include namespace mbgl { @@ -67,6 +68,8 @@ class RenderPluginLayer final : public RenderLayer { style::PluginLayer::OnUpdateLayer _updateFunction = nullptr; style::PluginLayer::OnUpdateLayerProperties _updateLayerPropertiesFunction = nullptr; + + std::map> _featureCollectionByTile; }; } // namespace mbgl diff --git a/src/mbgl/plugin/plugin_style_filter.cpp b/src/mbgl/plugin/plugin_style_filter.cpp new file mode 100644 index 000000000000..546de4f2c7aa --- /dev/null +++ b/src/mbgl/plugin/plugin_style_filter.cpp @@ -0,0 +1,12 @@ +#include + +using namespace mbgl::style; + +// This method will call the lambda if it exists +const std::string PluginStyleFilter::filterResponse(const std::string& res) { + if (_filterStyleFunction) { + auto tempResult = _filterStyleFunction(res); + return tempResult; + } + return res; +} diff --git a/src/mbgl/plugin/plugin_style_filter.hpp b/src/mbgl/plugin/plugin_style_filter.hpp new file mode 100644 index 000000000000..985c0ea59fe9 --- /dev/null +++ b/src/mbgl/plugin/plugin_style_filter.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +namespace mbgl { + +namespace style { + +class PluginStyleFilter { +public: + using OnFilterStyle = std::function; + + OnFilterStyle _filterStyleFunction; + + // This method + const std::string filterResponse(const std::string& styleData); +}; + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/storage/file_source_manager.cpp b/src/mbgl/storage/file_source_manager.cpp index 2e21443b82a6..f9876b156753 100644 --- a/src/mbgl/storage/file_source_manager.cpp +++ b/src/mbgl/storage/file_source_manager.cpp @@ -27,6 +27,7 @@ class FileSourceManager::Impl { std::list fileSources; std::map fileSourceFactories; std::recursive_mutex mutex; + std::vector> customFileSources; }; FileSourceManager::FileSourceManager() @@ -86,4 +87,14 @@ FileSourceManager::FileSourceFactory FileSourceManager::unRegisterFileSourceFact return factory; } +// Registers a custom file source factory +void FileSourceManager::registerCustomFileSource(std::shared_ptr fileSource) noexcept { + impl->customFileSources.push_back(fileSource); +} + +// Returns an array of custom file sources +std::vector> FileSourceManager::getCustomFileSources() noexcept { + return impl->customFileSources; +} + } // namespace mbgl diff --git a/src/mbgl/style/style.cpp b/src/mbgl/style/style.cpp index 4f8723ead10a..0caa94860549 100644 --- a/src/mbgl/style/style.cpp +++ b/src/mbgl/style/style.cpp @@ -177,5 +177,11 @@ std::unique_ptr Style::removeLayer(const std::string& id) { return impl->removeLayer(id); } +// Add style parsing filter +void Style::addStyleFilter(std::shared_ptr filter) { + impl->mutated = true; + return impl->addStyleFilter(filter); +} + } // namespace style } // namespace mbgl diff --git a/src/mbgl/style/style_impl.cpp b/src/mbgl/style/style_impl.cpp index 2b4bf88b850a..11669068c641 100644 --- a/src/mbgl/style/style_impl.cpp +++ b/src/mbgl/style/style_impl.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include namespace mbgl { @@ -44,7 +45,7 @@ void Style::Impl::loadJSON(const std::string& json_) { observer->onStyleLoading(); url.clear(); - parse(json_); + filterThenParse(json_); } void Style::Impl::loadURL(const std::string& url_) { @@ -74,11 +75,25 @@ void Style::Impl::loadURL(const std::string& url_) { } else if (res.notModified || res.noContent) { return; } else { - parse(*res.data); + filterThenParse(*res.data); } }); } +void Style::Impl::filterThenParse(const std::string& styleData) { + if (_styleFilters.size() == 0) { + parse(styleData); + return; + } + + // Otherwise, go through the chain of filters + std::string filteredStyle = styleData; + for (auto filter : _styleFilters) { + filteredStyle = filter->filterResponse(filteredStyle); + } + parse(filteredStyle); +} + void Style::Impl::parse(const std::string& json_) { Parser parser; @@ -233,6 +248,11 @@ std::unique_ptr Style::Impl::removeLayer(const std::string& id) { return layer; } +// Add style parsing filter +void Style::Impl::addStyleFilter(std::shared_ptr filter) { + _styleFilters.push_back(filter); +} + void Style::Impl::setLight(std::unique_ptr light_) { light = std::move(light_); light->setObserver(this); diff --git a/src/mbgl/style/style_impl.hpp b/src/mbgl/style/style_impl.hpp index 8ef03e98ffe7..cf05f986d7c3 100644 --- a/src/mbgl/style/style_impl.hpp +++ b/src/mbgl/style/style_impl.hpp @@ -68,6 +68,9 @@ class Style::Impl : public SpriteLoaderObserver, Layer* addLayer(std::unique_ptr, const std::optional& beforeLayerID = std::nullopt); std::unique_ptr removeLayer(const std::string& layerID); + // Add style parsing filter + void addStyleFilter(std::shared_ptr); + std::string getName() const; CameraOptions getDefaultCamera() const; @@ -96,8 +99,11 @@ class Style::Impl : public SpriteLoaderObserver, bool loaded = false; private: + void filterThenParse(const std::string& styleData); void parse(const std::string&); + std::vector> _styleFilters; + std::shared_ptr fileSource; std::string url; diff --git a/src/mbgl/tile/geometry_tile.cpp b/src/mbgl/tile/geometry_tile.cpp index 0fe0cf2535ed..4b98ba00bac9 100644 --- a/src/mbgl/tile/geometry_tile.cpp +++ b/src/mbgl/tile/geometry_tile.cpp @@ -35,7 +35,7 @@ LayerRenderData* GeometryTile::LayoutResult::getLayerRenderData(const style::Lay return nullptr; } LayerRenderData& result = it->second; - if (result.layerProperties->baseImpl->getTypeInfo() != layerImpl.getTypeInfo()) { + if (!layerTypeInfoEquals(result.layerProperties->baseImpl->getTypeInfo(), layerImpl.getTypeInfo())) { // Layer data might be outdated, see issue #12432. return nullptr; }