From 5171f6b966f3c373a7e0fcfc0085201452265d06 Mon Sep 17 00:00:00 2001 From: AtlasProgramming Date: Thu, 26 Jun 2025 00:27:32 -0400 Subject: [PATCH] Add run-time plug-in layer support to iOS (#3430) --- BUILD.bazel | 7 +- CMakeLists.txt | 13 + bazel/core.bzl | 16 + include/mbgl/layermanager/layer_manager.hpp | 9 + include/mbgl/style/layer.hpp | 4 + platform/BUILD.bazel | 4 + platform/darwin/BUILD.bazel | 6 + platform/darwin/app/PluginLayerExample.h | 9 + platform/darwin/app/PluginLayerExample.mm | 30 + .../app/PluginLayerExampleMetalRendering.h | 17 + .../app/PluginLayerExampleMetalRendering.mm | 233 ++ platform/darwin/app/PluginLayerTestStyle.json | 2124 +++++++++++++++++ platform/darwin/bazel/files.bzl | 7 + platform/darwin/darwin.cmake | 2 + platform/darwin/src/MLNMapOptions.h | 5 + platform/darwin/src/MLNMapOptions.mm | 1 + platform/darwin/src/MLNPluginLayer.h | 109 + platform/darwin/src/MLNPluginLayer.mm | 80 + platform/darwin/src/MLNPluginStyleLayer.h | 13 + platform/darwin/src/MLNPluginStyleLayer.mm | 35 + .../darwin/src/MLNPluginStyleLayer_Private.h | 25 + platform/darwin/src/MLNStyleLayerManager.h | 17 +- platform/darwin/src/MLNStyleLayerManager.mm | 5 +- .../src/mbgl/layermanager/layer_manager.cpp | 15 +- platform/ios/BUILD.bazel | 1 + platform/ios/MapLibre.docc/MapLibre.md | 1 + platform/ios/MapLibre.docc/PluginLayers.md | 148 ++ platform/ios/app/MBXViewController.mm | 25 + platform/ios/bazel/files.bzl | 2 +- platform/ios/src/MLNMapView.h | 6 + platform/ios/src/MLNMapView.mm | 159 ++ src/mbgl/layermanager/layer_manager.cpp | 2 + src/mbgl/plugin/plugin_layer.cpp | 84 + src/mbgl/plugin/plugin_layer.hpp | 45 + src/mbgl/plugin/plugin_layer_factory.cpp | 188 ++ src/mbgl/plugin/plugin_layer_factory.hpp | 45 + src/mbgl/plugin/plugin_layer_impl.cpp | 99 + src/mbgl/plugin/plugin_layer_impl.hpp | 122 + src/mbgl/plugin/plugin_layer_properties.cpp | 22 + src/mbgl/plugin/plugin_layer_properties.hpp | 21 + src/mbgl/plugin/plugin_layer_render.cpp | 199 ++ src/mbgl/plugin/plugin_layer_render.hpp | 72 + src/mbgl/style/layer.cpp | 8 + test/CMakeLists.txt | 1 + test/plugin/plugin.test.cpp | 236 ++ 45 files changed, 4259 insertions(+), 13 deletions(-) create mode 100644 platform/darwin/app/PluginLayerExample.h create mode 100644 platform/darwin/app/PluginLayerExample.mm create mode 100644 platform/darwin/app/PluginLayerExampleMetalRendering.h create mode 100644 platform/darwin/app/PluginLayerExampleMetalRendering.mm create mode 100644 platform/darwin/app/PluginLayerTestStyle.json create mode 100644 platform/darwin/src/MLNPluginLayer.h create mode 100644 platform/darwin/src/MLNPluginLayer.mm create mode 100644 platform/darwin/src/MLNPluginStyleLayer.h create mode 100644 platform/darwin/src/MLNPluginStyleLayer.mm create mode 100644 platform/darwin/src/MLNPluginStyleLayer_Private.h create mode 100644 platform/ios/MapLibre.docc/PluginLayers.md create mode 100644 src/mbgl/plugin/plugin_layer.cpp create mode 100644 src/mbgl/plugin/plugin_layer.hpp create mode 100644 src/mbgl/plugin/plugin_layer_factory.cpp create mode 100644 src/mbgl/plugin/plugin_layer_factory.hpp create mode 100644 src/mbgl/plugin/plugin_layer_impl.cpp create mode 100644 src/mbgl/plugin/plugin_layer_impl.hpp create mode 100644 src/mbgl/plugin/plugin_layer_properties.cpp create mode 100644 src/mbgl/plugin/plugin_layer_properties.hpp create mode 100644 src/mbgl/plugin/plugin_layer_render.cpp create mode 100644 src/mbgl/plugin/plugin_layer_render.hpp create mode 100644 test/plugin/plugin.test.cpp diff --git a/BUILD.bazel b/BUILD.bazel index 8ec90d818..ab6d783cf 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -4,6 +4,8 @@ load("@npm//:defs.bzl", "npm_link_all_packages") load("@rules_cc//cc:defs.bzl", "cc_library") load( "//bazel:core.bzl", + "MLN_LAYER_PLUGIN_HEADERS", + "MLN_LAYER_PLUGIN_SOURCE", "MLN_CORE_HEADERS", "MLN_CORE_SOURCE", "MLN_DRAWABLES_GL_HEADERS", @@ -76,14 +78,15 @@ cc_library( # the prefix from the path. cc_library( name = "mbgl-core", - srcs = MLN_CORE_SOURCE + + srcs = MLN_LAYER_PLUGIN_SOURCE + + MLN_CORE_SOURCE + MLN_GENERATED_SHADER_SOURCE + MLN_GENERATED_STYLE_SOURCE + select({ ":drawable_renderer": MLN_OPENGL_SOURCE + MLN_DRAWABLES_SOURCE + MLN_DRAWABLES_GL_SOURCE, ":metal_renderer": MLN_DRAWABLES_SOURCE + MLN_DRAWABLES_MTL_SOURCE, "//conditions:default": [], }), - hdrs = MLN_CORE_HEADERS + select({ + hdrs = MLN_LAYER_PLUGIN_HEADERS + MLN_CORE_HEADERS + select({ ":drawable_renderer": MLN_OPENGL_HEADERS + MLN_DRAWABLES_HEADERS + MLN_DRAWABLES_GL_HEADERS, ":metal_renderer": MLN_DRAWABLES_HEADERS + MLN_DRAWABLES_MTL_HEADERS, "//conditions:default": [], diff --git a/CMakeLists.txt b/CMakeLists.txt index adfbadd6c..eafae983a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -966,6 +966,19 @@ list(APPEND SRC_FILES ${PROJECT_SOURCE_DIR}/src/mbgl/util/version.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/util/work_request.cpp ) +list(APPEND SRC_FILES + ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_layer.hpp + ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_layer_render.hpp + ${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_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 +) + if(MLN_WITH_OPENGL) message(STATUS "Configuring GL-Native with OpenGL renderer backend") diff --git a/bazel/core.bzl b/bazel/core.bzl index 479b94841..b27bbd8d9 100644 --- a/bazel/core.bzl +++ b/bazel/core.bzl @@ -1,3 +1,19 @@ +MLN_LAYER_PLUGIN_HEADERS = [ + "src/mbgl/plugin/plugin_layer.hpp", + "src/mbgl/plugin/plugin_layer_factory.hpp", + "src/mbgl/plugin/plugin_layer_impl.hpp", + "src/mbgl/plugin/plugin_layer_render.hpp", + "src/mbgl/plugin/plugin_layer_properties.hpp", +] + +MLN_LAYER_PLUGIN_SOURCE = [ + "src/mbgl/plugin/plugin_layer.cpp", + "src/mbgl/plugin/plugin_layer_factory.cpp", + "src/mbgl/plugin/plugin_layer_impl.cpp", + "src/mbgl/plugin/plugin_layer_render.cpp", + "src/mbgl/plugin/plugin_layer_properties.cpp", +] + MLN_PUBLIC_GENERATED_STYLE_HEADERS = [ "include/mbgl/style/layers/circle_layer.hpp", "include/mbgl/style/layers/fill_layer.hpp", diff --git a/include/mbgl/layermanager/layer_manager.hpp b/include/mbgl/layermanager/layer_manager.hpp index fb7b98031..f3bd94e6a 100644 --- a/include/mbgl/layermanager/layer_manager.hpp +++ b/include/mbgl/layermanager/layer_manager.hpp @@ -67,6 +67,15 @@ public: */ static const bool annotationsEnabled; + /** + * Enables a layer type for JSON style only. + * + * We might not want to expose runtime API for some layer types + * in order to save binary size (the corresponding SDK layer wrappers + * should be excluded from the project build). + */ + virtual void addLayerTypeCoreOnly(std::unique_ptr); + protected: virtual ~LayerManager() = default; virtual LayerFactory* getFactory(const std::string& type) noexcept = 0; diff --git a/include/mbgl/style/layer.hpp b/include/mbgl/style/layer.hpp index af75b755a..36e8db174 100644 --- a/include/mbgl/style/layer.hpp +++ b/include/mbgl/style/layer.hpp @@ -90,6 +90,10 @@ struct LayerTypeInfo { } tileKind; }; +// Added this to support plugins and that their LayerTypeInfo isn't the same point +// across the board +bool layerTypeInfoEquals(const mbgl::style::LayerTypeInfo* one, const mbgl::style::LayerTypeInfo* other); + /** * The runtime representation of a [layer](https://maplibre.org/maplibre-style-spec/#layers) * from the MapLibre Style Spec. diff --git a/platform/BUILD.bazel b/platform/BUILD.bazel index 3841ca31e..f39d31d1e 100644 --- a/platform/BUILD.bazel +++ b/platform/BUILD.bazel @@ -266,6 +266,10 @@ objc_library( objc_library( name = "iosapp", srcs = [ + "//platform/darwin:app/PluginLayerExample.h", + "//platform/darwin:app/PluginLayerExample.mm", + "//platform/darwin:app/PluginLayerExampleMetalRendering.h", + "//platform/darwin:app/PluginLayerExampleMetalRendering.mm", "//platform/darwin:app/CustomStyleLayerExample.h", "//platform/darwin:app/CustomStyleLayerExample.m", "//platform/ios:ios_app_srcs", diff --git a/platform/darwin/BUILD.bazel b/platform/darwin/BUILD.bazel index 6948b827b..dcc0a8bcb 100644 --- a/platform/darwin/BUILD.bazel +++ b/platform/darwin/BUILD.bazel @@ -174,6 +174,7 @@ swift_library( data = [ "test/amsterdam.geojson", "test/one-liner.json", + "app/PluginLayerTestStyle.json", ], visibility = [ "//platform/ios/test:__pkg__", @@ -297,6 +298,11 @@ objc_library( exports_files( [ + "app/PluginLayerTestStyle.json", + "app/PluginLayerExample.h", + "app/PluginLayerExample.mm", + "app/PluginLayerExampleMetalRendering.h", + "app/PluginLayerExampleMetalRendering.mm", "test/amsterdam.geojson", "test/MLNSDKTestHelpers.swift", "app/CustomStyleLayerExample.h", diff --git a/platform/darwin/app/PluginLayerExample.h b/platform/darwin/app/PluginLayerExample.h new file mode 100644 index 000000000..858ba15a9 --- /dev/null +++ b/platform/darwin/app/PluginLayerExample.h @@ -0,0 +1,9 @@ +#import +#import "MLNPluginLayer.h" +NS_ASSUME_NONNULL_BEGIN + +@interface PluginLayerExample : MLNPluginLayer + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/app/PluginLayerExample.mm b/platform/darwin/app/PluginLayerExample.mm new file mode 100644 index 000000000..0cb01054d --- /dev/null +++ b/platform/darwin/app/PluginLayerExample.mm @@ -0,0 +1,30 @@ +#import "PluginLayerExample.h" + +@implementation PluginLayerExample + + +// This is the layer type in the style that is used ++(MLNPluginLayerCapabilities *)layerCapabilities { + + MLNPluginLayerCapabilities *tempResult = [[MLNPluginLayerCapabilities alloc] init]; + tempResult.layerID = @"plugin-layer-test"; + tempResult.requiresPass3D = YES; + return tempResult; + +} + +// The overrides +-(void)onRenderLayer { + NSLog(@"PluginLayerExample: On Render Layer"); + +} + +-(void)onUpdateLayer { + +} + +-(void)onUpdateLayerProperties:(NSDictionary *)layerProperties { + // NSLog(@"Layer Properties: %@", layerProperties); +} + +@end diff --git a/platform/darwin/app/PluginLayerExampleMetalRendering.h b/platform/darwin/app/PluginLayerExampleMetalRendering.h new file mode 100644 index 000000000..9ff1455a7 --- /dev/null +++ b/platform/darwin/app/PluginLayerExampleMetalRendering.h @@ -0,0 +1,17 @@ +/* + PluginLayerExampleMetalRendering + + This is a port lf the CustomStyleLayerExample into the plug-in architecture. It assumes + that the rendering is being done in metal since it's added to the core via a darwin method + + */ + +#import "MLNPluginLayer.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface PluginLayerExampleMetalRendering : MLNPluginLayer + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/app/PluginLayerExampleMetalRendering.mm b/platform/darwin/app/PluginLayerExampleMetalRendering.mm new file mode 100644 index 000000000..1fe951e14 --- /dev/null +++ b/platform/darwin/app/PluginLayerExampleMetalRendering.mm @@ -0,0 +1,233 @@ +/* + PluginLayerExampleMetalRendering + + This is a port lf the CustomStyleLayerExample into the plug-in architecture. It assumes + that the rendering is being done in metal since it's added to the core via a darwin method + + */ + +#import "PluginLayerExampleMetalRendering.h" +#import +#import "Mapbox.h" + +typedef struct +{ + vector_float2 position; + vector_float4 color; +} Vertex; + +@interface PluginLayerExampleMetalRendering () { + // The render pipeline state + id _pipelineState; + id _depthStencilStateWithoutStencil; + + // Properties + float _offsetX; + float _offsetY; + float _scale; + float _r, _g, _b, _a; + + +} + +@end + + +@implementation PluginLayerExampleMetalRendering + + +// This is a static class method that is used by the MLNMapView to create the +// required marshalling classes to interface with the MapLibre core. ++(MLNPluginLayerCapabilities *)layerCapabilities { + + MLNPluginLayerCapabilities *tempResult = [[MLNPluginLayerCapabilities alloc] init]; + tempResult.layerID = @"plugin-layer-metal-rendering"; + tempResult.requiresPass3D = YES; + + // Define the paint properties that this layer implements and + // what types they are + tempResult.layerProperties = @[ + // The scale property + [MLNPluginLayerProperty propertyWithName:@"scale" + propertyType:MLNPluginLayerPropertyTypeSingleFloat + defaultValue:@(1.0)], + + // The fill color property + [MLNPluginLayerProperty propertyWithName:@"fill-color" + propertyType:MLNPluginLayerPropertyTypeColor + defaultValue:[UIColor blueColor]] + + ]; + + return tempResult; + +} + +- (void)createShaders:(MLNMapView *)mapView { + MLNBackendResource* resource = [mapView backendResource]; + + NSString *shaderSource = @ +" #include \n" +" using namespace metal;\n" +" typedef struct\n" +" {\n" +" vector_float2 position;\n" +" vector_float4 color;\n" +" } Vertex;\n" +" struct RasterizerData\n" +" {\n" +" float4 position [[position]];\n" +" float4 color;\n" +" };\n" +" vertex RasterizerData\n" +" vertexShader(uint vertexID [[vertex_id]],\n" +" constant Vertex *vertices [[buffer(0)]],\n" +" constant vector_uint2 *viewportSizePointer [[buffer(1)]])\n" +" {\n" +" RasterizerData out;\n" +" float2 pixelSpacePosition = vertices[vertexID].position.xy;\n" +" vector_float2 viewportSize = vector_float2(*viewportSizePointer);\n" +" out.position = vector_float4(0.0, 0.0, 0.0, 1.0);\n" +" out.position.xy = pixelSpacePosition / (viewportSize / 2.0);\n" +" out.color = vertices[vertexID].color;\n" +" return out;\n" +" }\n" +" fragment float4 fragmentShader(RasterizerData in [[stage_in]])\n" +" {\n" +" return in.color;\n" +" }\n"; + + + NSError *error = nil; + id _device = resource.device; + id library = [_device newLibraryWithSource:shaderSource options:nil error:&error]; + NSAssert(library, @"Error compiling shaders: %@", error); + id vertexFunction = [library newFunctionWithName:@"vertexShader"]; + id fragmentFunction = [library newFunctionWithName:@"fragmentShader"]; + + // Configure a pipeline descriptor that is used to create a pipeline state. + MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; + pipelineStateDescriptor.label = @"Simple Pipeline"; + pipelineStateDescriptor.vertexFunction = vertexFunction; + pipelineStateDescriptor.fragmentFunction = fragmentFunction; + pipelineStateDescriptor.colorAttachments[0].pixelFormat = resource.mtkView.colorPixelFormat; + pipelineStateDescriptor.depthAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8; + pipelineStateDescriptor.stencilAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8; + + _pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor + error:&error]; + NSAssert(_pipelineState, @"Failed to create pipeline state: %@", error); + + // Notice that we don't configure the stencilTest property, leaving stencil testing disabled + MTLDepthStencilDescriptor *depthStencilDescriptor = [[MTLDepthStencilDescriptor alloc] init]; + depthStencilDescriptor.depthCompareFunction = MTLCompareFunctionAlways; // Or another value as needed + depthStencilDescriptor.depthWriteEnabled = NO; + + _depthStencilStateWithoutStencil = [_device newDepthStencilStateWithDescriptor:depthStencilDescriptor]; + +} + + +// Setup the rendering environment +-(void)setupRendering:(MLNMapView *)mapView { + [self createShaders:mapView]; + if (_scale == 0) { + _scale = 1; + } + // Set the initial + _a = 1; + +} + +// The overrides +-(void)onRenderLayer:(MLNMapView *)mapView + renderEncoder:(id)renderEncoder { + + MLNBackendResource* resource = [mapView backendResource]; + + if (_pipelineState == nil) { + [self setupRendering:mapView]; + } + + if (renderEncoder == nil) { + return; + } + + + vector_uint2 _viewportSize; + _viewportSize.x = resource.mtkView.drawableSize.width; + _viewportSize.y = resource.mtkView.drawableSize.height; + + Vertex triangleVerticesWithColor[] = { + // 2D positions, RGBA colors + { { (250 + _offsetX) * _scale, (-250 + _offsetY) * _scale }, { 1, 1, 1, _a } }, + { { (-250 + _offsetX) * _scale, (-250 + _offsetY) * _scale }, { 1, 1, 1, _a} }, + { { (0 + _offsetX) * _scale, (250 + _offsetY) * _scale }, { _r, _g, _b, _a } }, + }; + + [renderEncoder setRenderPipelineState:_pipelineState]; + [renderEncoder setDepthStencilState:_depthStencilStateWithoutStencil]; + + // Pass in the parameter data. + [renderEncoder setVertexBytes:triangleVerticesWithColor + length:sizeof(triangleVerticesWithColor) + atIndex:0]; + + [renderEncoder setVertexBytes:&_viewportSize + length:sizeof(_viewportSize) + atIndex:1]; + + // Draw the triangle. + [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle + vertexStart:0 + vertexCount:3]; +} + +-(void)onUpdateLayer:(MLNPluginLayerDrawingContext)drawingContext { + // This is called before the render call and is a place where + // any animations/etc can be updated +} + +-(void)onUpdateLayerProperties:(NSDictionary *)layerProperties { + //NSLog(@"Metal Layer Rendering Properties: %@", layerProperties); + + // These two properties are in the "properties" section of the style sheet and + // are passed in at layer creation. These are not expression based properties + NSNumber *offsetX = [layerProperties objectForKey:@"offset-x"]; + if (offsetX) { + _offsetX = [[layerProperties objectForKey:@"offset-x"] floatValue]; + } + + NSNumber *offsetY = [layerProperties objectForKey:@"offset-y"]; + if (offsetY) { + _offsetY = [[layerProperties objectForKey:@"offset-y"] floatValue]; + } + + // These are the paint properties and can be updated each frame or zoom level + // change/etc depending on if they are static or expression based/etc. + NSNumber *scale = [layerProperties objectForKey:@"scale"]; + if (scale) { + if ([scale isKindOfClass:[NSNumber class]]) { + _scale = [scale floatValue]; + } + } + + NSString *fillColor = [layerProperties objectForKey:@"fill-color"]; + if (fillColor) { + //NSLog(@"Fill Color: %@", fillColor); + fillColor = [fillColor stringByReplacingOccurrencesOfString:@"rgba(" withString:@""]; + fillColor = [fillColor stringByReplacingOccurrencesOfString:@")" withString:@""]; + NSArray *components = [fillColor componentsSeparatedByString:@","]; + if ([components count] == 4) { + // Convert to float since they come in as 0..255 + _r = [[components objectAtIndex:0] floatValue] / 255.0; + _g = [[components objectAtIndex:1] floatValue] / 255.0; + _b = [[components objectAtIndex:2] floatValue] / 255.0; + _a = [[components objectAtIndex:3] floatValue]; + } + + } + +} + +@end diff --git a/platform/darwin/app/PluginLayerTestStyle.json b/platform/darwin/app/PluginLayerTestStyle.json new file mode 100644 index 000000000..25ce408bd --- /dev/null +++ b/platform/darwin/app/PluginLayerTestStyle.json @@ -0,0 +1,2124 @@ +{ + "comment":"this is based on the URL in the app, but copied here in its entirety to add the plugin layer metadata", + + "id": "43f36e14-e3f5-43c1-84c0-50a9c80dc5c7", + "name": "MapLibre", + "zoom": 0.8619833357855968, + "pitch": 0, + "center": [ + 17.65431710431244, + 32.954120326746775 + ], + "glyphs": "https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "#D8F2FF" + }, + "filter": [ + "all" + ], + "layout": { + "visibility": "visible" + }, + "maxzoom": 24 + }, + { + "id": "coastline", + "type": "line", + "paint": { + "line-blur": 0.5, + "line-color": "#198EC8", + "line-width": { + "stops": [ + [ + 0, + 2 + ], + [ + 6, + 6 + ], + [ + 14, + 9 + ], + [ + 22, + 18 + ] + ] + } + }, + "filter": [ + "all" + ], + "layout": { + "line-cap": "round", + "line-join": "round", + "visibility": "visible" + }, + "source": "maplibre", + "maxzoom": 24, + "minzoom": 0, + "source-layer": "countries" + }, + { + "id": "countries-fill", + "type": "fill", + "paint": { + "fill-color": [ + "match", + [ + "get", + "ADM0_A3" + ], + [ + "ARM", + "ATG", + "AUS", + "BTN", + "CAN", + "COG", + "CZE", + "GHA", + "GIN", + "HTI", + "ISL", + "JOR", + "KHM", + "KOR", + "LVA", + "MLT", + "MNE", + "MOZ", + "PER", + "SAH", + "SGP", + "SLV", + "SOM", + "TJK", + "TUV", + "UKR", + "WSM" + ], + "#D6C7FF", + [ + "AZE", + "BGD", + "CHL", + "CMR", + "CSI", + "DEU", + "DJI", + "GUY", + "HUN", + "IOA", + "JAM", + "LBN", + "LBY", + "LSO", + "MDG", + "MKD", + "MNG", + "MRT", + "NIU", + "NZL", + "PCN", + "PYF", + "SAU", + "SHN", + "STP", + "TTO", + "UGA", + "UZB", + "ZMB" + ], + "#EBCA8A", + [ + "AGO", + "ASM", + "ATF", + "BDI", + "BFA", + "BGR", + "BLZ", + "BRA", + "CHN", + "CRI", + "ESP", + "HKG", + "HRV", + "IDN", + "IRN", + "ISR", + "KNA", + "LBR", + "LCA", + "MAC", + "MUS", + "NOR", + "PLW", + "POL", + "PRI", + "SDN", + "TUN", + "UMI", + "USA", + "USG", + "VIR", + "VUT" + ], + "#C1E599", + [ + "ARE", + "ARG", + "BHS", + "CIV", + "CLP", + "DMA", + "ETH", + "GAB", + "GRD", + "HMD", + "IND", + "IOT", + "IRL", + "IRQ", + "ITA", + "KOS", + "LUX", + "MEX", + "NAM", + "NER", + "PHL", + "PRT", + "RUS", + "SEN", + "SUR", + "TZA", + "VAT" + ], + "#E7E58F", + [ + "AUT", + "BEL", + "BHR", + "BMU", + "BRB", + "CYN", + "DZA", + "EST", + "FLK", + "GMB", + "GUM", + "HND", + "JEY", + "KGZ", + "LIE", + "MAF", + "MDA", + "NGA", + "NRU", + "SLB", + "SOL", + "SRB", + "SWZ", + "THA", + "TUR", + "VEN", + "VGB" + ], + "#98DDA1", + [ + "AIA", + "BIH", + "BLM", + "BRN", + "CAF", + "CHE", + "COM", + "CPV", + "CUB", + "ECU", + "ESB", + "FSM", + "GAZ", + "GBR", + "GEO", + "KEN", + "LTU", + "MAR", + "MCO", + "MDV", + "NFK", + "NPL", + "PNG", + "PRY", + "QAT", + "SLE", + "SPM", + "SYC", + "TCA", + "TKM", + "TLS", + "VNM", + "WEB", + "WSB", + "YEM", + "ZWE" + ], + "#83D5F4", + [ + "ABW", + "ALB", + "AND", + "ATC", + "BOL", + "COD", + "CUW", + "CYM", + "CYP", + "EGY", + "FJI", + "GGY", + "IMN", + "KAB", + "KAZ", + "KWT", + "LAO", + "MLI", + "MNP", + "MSR", + "MYS", + "NIC", + "NLD", + "PAK", + "PAN", + "PRK", + "ROU", + "SGS", + "SVN", + "SWE", + "TGO", + "TWN", + "VCT", + "ZAF" + ], + "#B1BBF9", + [ + "ATA", + "GRL" + ], + "#FFFFFF", + "#EAB38F" + ] + }, + "filter": [ + "all" + ], + "layout": { + "visibility": "visible" + }, + "source": "maplibre", + "maxzoom": 24, + "source-layer": "countries" + }, + { + "id": "countries-boundary", + "type": "line", + "paint": { + "line-color": "rgba(255, 255, 255, 1)", + "line-width": { + "stops": [ + [ + 1, + 1 + ], + [ + 6, + 2 + ], + [ + 14, + 6 + ], + [ + 22, + 12 + ] + ] + }, + "line-opacity": { + "stops": [ + [ + 3, + 0.5 + ], + [ + 6, + 1 + ] + ] + } + }, + "layout": { + "line-cap": "round", + "line-join": "round", + "visibility": "visible" + }, + "source": "maplibre", + "maxzoom": 24, + "source-layer": "countries" + }, + { + "id": "geolines", + "type": "line", + "paint": { + "line-color": "#1077B0", + "line-opacity": 1, + "line-dasharray": [ + 3, + 3 + ] + }, + "filter": [ + "all", + [ + "!=", + "name", + "International Date Line" + ] + ], + "layout": { + "visibility": "visible" + }, + "source": "maplibre", + "maxzoom": 24, + "source-layer": "geolines" + }, + { + "id": "geolines-label", + "type": "symbol", + "paint": { + "text-color": "#1077B0", + "text-halo-blur": 1, + "text-halo-color": "rgba(255, 255, 255, 1)", + "text-halo-width": 1 + }, + "filter": [ + "all", + [ + "!=", + "name", + "International Date Line" + ] + ], + "layout": { + "text-font": [ + "Open Sans Semibold" + ], + "text-size": { + "stops": [ + [ + 2, + 12 + ], + [ + 6, + 16 + ] + ] + }, + "text-field": "{name}", + "visibility": "visible", + "symbol-placement": "line" + }, + "source": "maplibre", + "maxzoom": 24, + "minzoom": 1, + "source-layer": "geolines" + }, + { + "id": "countries-label", + "type": "symbol", + "paint": { + "text-color": "rgba(8, 37, 77, 1)", + "text-halo-blur": { + "stops": [ + [ + 2, + 0.2 + ], + [ + 6, + 0 + ] + ] + }, + "text-halo-color": "rgba(255, 255, 255, 1)", + "text-halo-width": { + "stops": [ + [ + 2, + 1 + ], + [ + 6, + 1.6 + ] + ] + } + }, + "filter": [ + "all" + ], + "layout": { + "text-font": [ + "Open Sans Semibold" + ], + "text-size": { + "stops": [ + [ + 2, + 10 + ], + [ + 4, + 12 + ], + [ + 6, + 16 + ] + ] + }, + "text-field": { + "stops": [ + [ + 2, + "{ABBREV}" + ], + [ + 4, + "{NAME}" + ] + ] + }, + "visibility": "visible", + "text-max-width": 10, + "text-transform": { + "stops": [ + [ + 0, + "uppercase" + ], + [ + 2, + "none" + ] + ] + } + }, + "source": "maplibre", + "maxzoom": 24, + "minzoom": 2, + "source-layer": "centroids" + }, + { "id": "metal-rendering-layer-1", + "type": "plugin-layer-metal-rendering", + "properties": { + "color1":"#FFAADD", + "offset-x": -300, + "offset-y": -300 + }, + "paint": { + "scale": 2.5 + } + }, + { "id": "metal-rendering-layer-2", + "type": "plugin-layer-metal-rendering", + "properties": { + "color1":"#FFAADD", + "offset-x": -300 + }, + "paint": { + "scale": [ + "interpolate", + ["linear"], + ["zoom"], + 5, + 0.5, + 15, + 3.0 + ] + } + }, + { "id": "metal-rendering-layer-3", + "type": "plugin-layer-metal-rendering", + "properties": { + "color1":"#DDAAFF", + "offset-x": 300 + }, + "paint": { + "scale": [ + "interpolate", + ["linear"], + ["zoom"], + 5, + 3.0, + 15, + 0.5 + ], + "fill-color": [ + "interpolate", + ["linear"], + ["zoom"], + 1, + "#ff0000", + 22, + "#00ff00" + ] + } + }, + { + "id": "crimea-fill", + "type": "fill", + "source": "crimea", + "paint": { + "fill-color": "#D6C7FF" + } + } + ], + "bearing": 0, + "sources": { + "maplibre": { + "url": "https://demotiles.maplibre.org/tiles/tiles.json", + "type": "vector" + }, + "crimea": { + "type": "geojson", + "data": { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 34.00905273547181, + 46.55925987559425 + ], + [ + 33.64325260204026, + 46.34533545368038 + ], + [ + 33.628682598560204, + 46.12569762665683 + ], + [ + 33.64292861730951, + 46.10476396128129 + ], + [ + 33.648473474905984, + 46.09033047763651 + ], + [ + 33.63876482059936, + 46.077976784785335 + ], + [ + 33.62782672238245, + 46.06747935719011 + ], + [ + 33.62911357645072, + 46.05708111413949 + ], + [ + 33.642686868727424, + 46.02192963417187 + ], + [ + 33.6429723910654, + 46.01521185644708 + ], + [ + 33.636224138774026, + 46.006705833212465 + ], + [ + 33.63052626465907, + 45.99692992186792 + ], + [ + 33.63193836679693, + 45.988472992911284 + ], + [ + 33.64276684834442, + 45.984575360297384 + ], + [ + 33.646928693041986, + 45.97981936210982 + ], + [ + 33.638745893564305, + 45.96829769147004 + ], + [ + 33.61958133326394, + 45.951176418494185 + ], + [ + 33.63181380398527, + 45.9445404758078 + ], + [ + 33.638921676216, + 45.94737012930554 + ], + [ + 33.64561542516918, + 45.95403251372139 + ], + [ + 33.65666403976448, + 45.95687114427736 + ], + [ + 33.6825817382811, + 45.95878100879199 + ], + [ + 33.738791807037614, + 45.94836945227263 + ], + [ + 33.758180142697, + 45.94072970008301 + ], + [ + 33.77735917288169, + 45.92923970233858 + ], + [ + 33.75927796793485, + 45.92241179584471 + ], + [ + 33.72529865009221, + 45.91587363154565 + ], + [ + 33.70875012326826, + 45.91008760988058 + ], + [ + 33.69378857293381, + 45.91480850795665 + ], + [ + 33.69092650243843, + 45.89657370898402 + ], + [ + 33.693592356906805, + 45.87271465766318 + ], + [ + 33.69226765972388, + 45.86041392418218 + ], + [ + 33.6704813511748, + 45.8584273836251 + ], + [ + 33.65936345808916, + 45.85944682601249 + ], + [ + 33.653870582376726, + 45.86425922279372 + ], + [ + 33.65107345584843, + 45.87089907254003 + ], + [ + 33.63067378180233, + 45.88040685247182 + ], + [ + 33.61945300059696, + 45.88147266102649 + ], + [ + 33.60987421595539, + 45.88048951126686 + ], + [ + 33.59906957603934, + 45.877610457390375 + ], + [ + 33.57828877687868, + 45.86810261756233 + ], + [ + 33.55357394560386, + 45.84700625141778 + ], + [ + 33.530220674480375, + 45.84221983655459 + ], + [ + 33.5192297395441, + 45.84121682367507 + ], + [ + 33.50832088442496, + 45.84313067048083 + ], + [ + 33.48901101848409, + 45.85268298292175 + ], + [ + 33.482152996405716, + 45.854578171799005 + ], + [ + 33.46719955896293, + 45.849912739405056 + ], + [ + 33.42447496599681, + 45.83075886348303 + ], + [ + 33.40940172404095, + 45.82691953557702 + ], + [ + 33.37918350072067, + 45.802867525073566 + ], + [ + 33.37362145339398, + 45.79619281922518 + ], + [ + 33.33805543634864, + 45.78577808972071 + ], + [ + 33.26498872665803, + 45.75410774187094 + ], + [ + 33.22887541283427, + 45.75131270772724 + ], + [ + 33.19548267281132, + 45.7644887297206 + ], + [ + 33.1789202379222, + 45.78010311364778 + ], + [ + 33.1688456078636, + 45.78470227904205 + ], + [ + 33.161012432811674, + 45.77921593899549 + ], + [ + 33.15951585299757, + 45.76864464913777 + ], + [ + 33.165962301438725, + 45.762685940125465 + ], + [ + 33.1750888126426, + 45.759218220695715 + ], + [ + 33.181464829753, + 45.75490447884948 + ], + [ + 33.17613930782352, + 45.7437961960276 + ], + [ + 33.16369168844906, + 45.735912015025065 + ], + [ + 32.93692665480876, + 45.662114646778264 + ], + [ + 32.86839112407645, + 45.63044340698664 + ], + [ + 32.83803944575723, + 45.60834075026611 + ], + [ + 32.82702772424804, + 45.59576101516498 + ], + [ + 32.82433467080986, + 45.58705137380335 + ], + [ + 32.82563941622885, + 45.579605763895614 + ], + [ + 32.82993674258438, + 45.56978311819469 + ], + [ + 32.82851940940563, + 45.56227808675749 + ], + [ + 32.813310142795274, + 45.55930933158257 + ], + [ + 32.80213583657516, + 45.560145780074464 + ], + [ + 32.78258622159436, + 45.565158335073846 + ], + [ + 32.77333922465823, + 45.56689313356526 + ], + [ + 32.758306734735356, + 45.565030173463356 + ], + [ + 32.750177256846115, + 45.55943726334968 + ], + [ + 32.74340732630185, + 45.55261895849793 + ], + [ + 32.73524549539499, + 45.54598788110354 + ], + [ + 32.72031700779701, + 45.53735927760957 + ], + [ + 32.70536040418847, + 45.53169142131733 + ], + [ + 32.68589438933773, + 45.52663379187257 + ], + [ + 32.66370583186284, + 45.52563107058867 + ], + [ + 32.64312077736798, + 45.52188979044979 + ], + [ + 32.525284074162556, + 45.45838108691365 + ], + [ + 32.49490411219156, + 45.43524910229854 + ], + [ + 32.48107654411925, + 45.408986638827514 + ], + [ + 32.48514589713025, + 45.39458067125969 + ], + [ + 32.51256939517424, + 45.34060655033625 + ], + [ + 32.535915460470335, + 45.33777248012882 + ], + [ + 32.57027153843481, + 45.32510892683359 + ], + [ + 32.590830644991826, + 45.32038723212662 + ], + [ + 32.66380378113439, + 45.320421746458976 + ], + [ + 32.67760722618917, + 45.32609231279554 + ], + [ + 32.71316246802607, + 45.353283572618125 + ], + [ + 32.72817188836078, + 45.36074681043402 + ], + [ + 32.750518060251466, + 45.36371725645313 + ], + [ + 32.89973931692998, + 45.35412322462227 + ], + [ + 32.941197846443885, + 45.34245505845169 + ], + [ + 32.97701667405008, + 45.32596743563991 + ], + [ + 33.04296090827762, + 45.2853982930032 + ], + [ + 33.05274355585479, + 45.28154273654923 + ], + [ + 33.06850284417635, + 45.27703461892352 + ], + [ + 33.07825272648239, + 45.272210805127315 + ], + [ + 33.089426322403455, + 45.25656353201492 + ], + [ + 33.09897492343546, + 45.247820101667884 + ], + [ + 33.12384611720435, + 45.238235755071685 + ], + [ + 33.15767197859745, + 45.20755227709648 + ], + [ + 33.172959979330074, + 45.19681657531794 + ], + [ + 33.21837666514142, + 45.187878368659824 + ], + [ + 33.24017433636709, + 45.180191106261134 + ], + [ + 33.248571989896675, + 45.16588271012458 + ], + [ + 33.259649216030766, + 45.155918961282026 + ], + [ + 33.28309785485047, + 45.16064860772312 + ], + [ + 33.31767999550894, + 45.17535522412791 + ], + [ + 33.35458473323109, + 45.18598673360148 + ], + [ + 33.39725661527919, + 45.18973663076909 + ], + [ + 33.41344561756824, + 45.18490731877088 + ], + [ + 33.468468576977216, + 45.149132412229676 + ], + [ + 33.537128652906205, + 45.11719769268973 + ], + [ + 33.56161328289443, + 45.094099022711475 + ], + [ + 33.57837628774928, + 45.053145935448015 + ], + [ + 33.58247744978442, + 45.027377243150454 + ], + [ + 33.5851414316958, + 45.01816461606674 + ], + [ + 33.6031021265521, + 44.993103583251695 + ], + [ + 33.605922209331794, + 44.986905272229734 + ], + [ + 33.60843524291815, + 44.97039962759274 + ], + [ + 33.61943161357851, + 44.93184946652454 + ], + [ + 33.619484500808824, + 44.90819321920554 + ], + [ + 33.61549738593425, + 44.88894092276257 + ], + [ + 33.608561183117274, + 44.871288478948514 + ], + [ + 33.59889474705494, + 44.859790298912856 + ], + [ + 33.55904244709464, + 44.850057575124595 + ], + [ + 33.54667558363471, + 44.83724531175508 + ], + [ + 33.53701832136994, + 44.81871953508235 + ], + [ + 33.5303157846202, + 44.798338017069625 + ], + [ + 33.5249116915937, + 44.78918633101301 + ], + [ + 33.51669091675143, + 44.784809980590666 + ], + [ + 33.524785531609865, + 44.77183212449111 + ], + [ + 33.5302902535075, + 44.75724515985675 + ], + [ + 33.53710734694323, + 44.73034290771247 + ], + [ + 33.54650992495621, + 44.70989226909535 + ], + [ + 33.5481286806762, + 44.699106546699085 + ], + [ + 33.543995566510915, + 44.68230506537358 + ], + [ + 33.53580273994743, + 44.6726082589706 + ], + [ + 33.52337411931097, + 44.661863083605255 + ], + [ + 33.515320778874354, + 44.6491266698327 + ], + [ + 33.516377841582795, + 44.63464990118433 + ], + [ + 33.52466971637648, + 44.62863961572572 + ], + [ + 33.557474298027785, + 44.62473000923737 + ], + [ + 33.5710648827386, + 44.620853511273225 + ], + [ + 33.55105839203679, + 44.61506440493406 + ], + [ + 33.499905706797676, + 44.61452599304897 + ], + [ + 33.48451102966331, + 44.60992438254493 + ], + [ + 33.47658499621011, + 44.60714391514574 + ], + [ + 33.46705078205747, + 44.60616254193252 + ], + [ + 33.44476599234898, + 44.607062134677875 + ], + [ + 33.4353466482458, + 44.60509936890821 + ], + [ + 33.413591053005575, + 44.593500212748125 + ], + [ + 33.40543527945235, + 44.59055535193136 + ], + [ + 33.37510958624222, + 44.58564691897425 + ], + [ + 33.37074452434078, + 44.58851022190515 + ], + [ + 33.372237834990756, + 44.576810695127364 + ], + [ + 33.37913003799301, + 44.56412673079859 + ], + [ + 33.48759131590526, + 44.51024086451031 + ], + [ + 33.50011215135888, + 44.50041002882833 + ], + [ + 33.517917009115365, + 44.49074142372788 + ], + [ + 33.53836387802215, + 44.49164280212756 + ], + [ + 33.56041892763031, + 44.4966411022441 + ], + [ + 33.57822378538677, + 44.497542389459795 + ], + [ + 33.59062975079095, + 44.48975808594983 + ], + [ + 33.619577003408466, + 44.46229988129974 + ], + [ + 33.62635433636015, + 44.45336293328907 + ], + [ + 33.63175322871038, + 44.434828756313124 + ], + [ + 33.645537634715026, + 44.42498521035591 + ], + [ + 33.721007257593925, + 44.39946630464436 + ], + [ + 33.74168386660085, + 44.39560878121904 + ], + [ + 33.80727466517129, + 44.39454176175843 + ], + [ + 33.81841706002561, + 44.39552670349164 + ], + [ + 33.83909366903248, + 44.40143600575672 + ], + [ + 33.85149963444792, + 44.40143600575945 + ], + [ + 33.91467816197718, + 44.38387049706651 + ], + [ + 33.938111652185, + 44.38083293528811 + ], + [ + 33.957065210440874, + 44.38272116790142 + ], + [ + 34.06614966692763, + 44.42019923628979 + ], + [ + 34.088893936836286, + 44.42200415824283 + ], + [ + 34.10279321289039, + 44.42487551014821 + ], + [ + 34.135933345669, + 44.44163597968952 + ], + [ + 34.14696087047267, + 44.44959070749778 + ], + [ + 34.16058918507403, + 44.466287285335795 + ], + [ + 34.170123399227776, + 44.48186111741296 + ], + [ + 34.182759104731986, + 44.49267838558103 + ], + [ + 34.22923417224524, + 44.49949719774551 + ], + [ + 34.24301857824986, + 44.50744404277697 + ], + [ + 34.263903954150294, + 44.53186886058606 + ], + [ + 34.26631622520165, + 44.53555362837611 + ], + [ + 34.26631622520165, + 44.54153064468656 + ], + [ + 34.27033667695244, + 44.545378535987936 + ], + [ + 34.2757355693048, + 44.54644280144541 + ], + [ + 34.285384653508004, + 44.54562413743594 + ], + [ + 34.299973149863405, + 44.54554227040174 + ], + [ + 34.32260254971496, + 44.543577427039224 + ], + [ + 34.3308731933177, + 44.54546040325087 + ], + [ + 34.340292537420794, + 44.55798473830754 + ], + [ + 34.38042135640006, + 44.631830317636684 + ], + [ + 34.41495238900856, + 44.673669777529994 + ], + [ + 34.424193090575585, + 44.68239452736094 + ], + [ + 34.42959198292681, + 44.68884644523774 + ], + [ + 34.469399167794535, + 44.730194532749294 + ], + [ + 34.47376422969597, + 44.73011292571252 + ], + [ + 34.47376422969597, + 44.72635887754967 + ], + [ + 34.475142670296464, + 44.723502373339585 + ], + [ + 34.499724861011515, + 44.74292382044041 + ], + [ + 34.532800295801195, + 44.752620844929055 + ], + [ + 34.61217550038418, + 44.76534519537847 + ], + [ + 34.65065696715081, + 44.777088262503725 + ], + [ + 34.72084256772871, + 44.811080759265764 + ], + [ + 34.756796893391225, + 44.82094054159748 + ], + [ + 34.82646979041766, + 44.81208604604609 + ], + [ + 34.84289620758207, + 44.816893835303176 + ], + [ + 34.856910353686715, + 44.82373813182468 + ], + [ + 34.889648317948144, + 44.817871641692506 + ], + [ + 34.90733830566026, + 44.820886440346584 + ], + [ + 34.922960632465504, + 44.83050015059965 + ], + [ + 34.92950822531711, + 44.83652826953224 + ], + [ + 34.94179932067178, + 44.84019370922482 + ], + [ + 34.95282684547897, + 44.841415470643284 + ], + [ + 34.98567967978991, + 44.840275160795755 + ], + [ + 35.0053224583441, + 44.83538786296728 + ], + [ + 35.017958163849414, + 44.82219008824552 + ], + [ + 35.02703289780189, + 44.80890779582285 + ], + [ + 35.037933245998005, + 44.79869792240089 + ], + [ + 35.08073333784134, + 44.793525442788905 + ], + [ + 35.1080207326404, + 44.824553365795765 + ], + [ + 35.130368105574235, + 44.86879838545747 + ], + [ + 35.15485200090768, + 44.90071251697748 + ], + [ + 35.17111229780758, + 44.90746386008772 + ], + [ + 35.21522068940149, + 44.91421441031795 + ], + [ + 35.233163085981715, + 44.925728224907715 + ], + [ + 35.25636688416236, + 44.95896657181197 + ], + [ + 35.27300098099195, + 44.96690119386028 + ], + [ + 35.29748487632534, + 44.95605693543271 + ], + [ + 35.30496087491386, + 44.96121482614441 + ], + [ + 35.315240372954605, + 44.965711070514175 + ], + [ + 35.31935217217088, + 44.96941359539801 + ], + [ + 35.36757236298112, + 44.94362319076086 + ], + [ + 35.36103086422793, + 44.97364475976596 + ], + [ + 35.362152264014156, + 44.98593980935419 + ], + [ + 35.374674561627444, + 44.997835734117416 + ], + [ + 35.389439658813274, + 45.00180049366759 + ], + [ + 35.42270785247763, + 45.00087540764923 + ], + [ + 35.43504325012745, + 45.00470780964241 + ], + [ + 35.43504325012745, + 45.011446929213974 + ], + [ + 35.40631957913584, + 45.02015821022701 + ], + [ + 35.40089948016896, + 45.025046135473445 + ], + [ + 35.39790908073891, + 45.03482073400548 + ], + [ + 35.40052568024015, + 45.042216617888045 + ], + [ + 35.40631957913584, + 45.051328088783805 + ], + [ + 35.40744097892215, + 45.06294640963205 + ], + [ + 35.41734667704213, + 45.0708666385693 + ], + [ + 35.469304867139925, + 45.10068964922732 + ], + [ + 35.5070260597534, + 45.113341616151644 + ], + [ + 35.54758335202416, + 45.12019982412133 + ], + [ + 35.59019654390909, + 45.11993606213795 + ], + [ + 35.63411803553862, + 45.11439677872579 + ], + [ + 35.70669729572677, + 45.09480210570922 + ], + [ + 35.771782422456766, + 45.06572995732262 + ], + [ + 35.78430472007, + 45.057941041321754 + ], + [ + 35.81250040352472, + 45.031852200991295 + ], + [ + 35.81941570220667, + 45.021152336906454 + ], + [ + 35.82763930064016, + 44.99895365027004 + ], + [ + 35.848198296721705, + 44.99208088455586 + ], + [ + 35.916977483614176, + 45.00172895661731 + ], + [ + 35.99360646900681, + 44.997896355361604 + ], + [ + 36.00893226608571, + 45.00926125333629 + ], + [ + 36.02539976723364, + 45.03288661039673 + ], + [ + 36.047827762958946, + 45.048074065419456 + ], + [ + 36.078666257082034, + 45.03883000769565 + ], + [ + 36.079137312377895, + 45.046610970582435 + ], + [ + 36.135020401727616, + 45.02125162210126 + ], + [ + 36.2241716847341, + 45.00751061631556 + ], + [ + 36.24398308095806, + 45.011474706353084 + ], + [ + 36.24828178013877, + 45.01649549321965 + ], + [ + 36.25332807917695, + 45.03247980324494 + ], + [ + 36.25743987839326, + 45.03842324279259 + ], + [ + 36.267158676549116, + 45.043573724415154 + ], + [ + 36.2783726744118, + 45.04555455542638 + ], + [ + 36.36740852558336, + 45.04833265291825 + ], + [ + 36.44029951169139, + 45.06787222615526 + ], + [ + 36.45375630913995, + 45.07631970334319 + ], + [ + 36.455251508854985, + 45.09202341204062 + ], + [ + 36.44142091149291, + 45.10709638287736 + ], + [ + 36.41432041665814, + 45.12872568311289 + ], + [ + 36.40852651776157, + 45.149160473330085 + ], + [ + 36.409997342308856, + 45.171615955386955 + ], + [ + 36.418312796420764, + 45.23001671705953 + ], + [ + 36.42672329481775, + 45.25186253492981 + ], + [ + 36.43756477765089, + 45.27227491599612 + ], + [ + 36.4497132753354, + 45.28542626329343 + ], + [ + 36.45905827355429, + 45.28753019598713 + ], + [ + 36.4814862692796, + 45.28845064200263 + ], + [ + 36.4909554290368, + 45.29213135137758 + ], + [ + 36.49637552800283, + 45.300940007322055 + ], + [ + 36.49394582846682, + 45.305015191082816 + ], + [ + 36.48871262946426, + 45.30935296803605 + ], + [ + 36.48460083024801, + 45.315924724862185 + ], + [ + 36.489647129296515, + 45.336413860372005 + ], + [ + 36.502169426909745, + 45.34731734941451 + ], + [ + 36.52104632331191, + 45.35033842661815 + ], + [ + 36.544281237819945, + 45.34731734942025 + ], + [ + 36.57455903204905, + 45.33601971904315 + ], + [ + 36.585399229982954, + 45.333917585593355 + ], + [ + 36.59810088537549, + 45.334837278577254 + ], + [ + 36.630808379142394, + 45.34048649352954 + ], + [ + 36.637536777859964, + 45.3511265071989 + ], + [ + 36.63099527910589, + 45.3741073632589 + ], + [ + 36.61359545390113, + 45.40895280985421 + ], + [ + 36.59845655678569, + 45.421547717459106 + ], + [ + 36.58331765967199, + 45.42731944465129 + ], + [ + 36.566309762912795, + 45.42548305000767 + ], + [ + 36.54836736633254, + 45.41210180010589 + ], + [ + 36.53285466928139, + 45.4090840212946 + ], + [ + 36.51565987255873, + 45.41957994832251 + ], + [ + 36.49117597722616, + 45.44279525429408 + ], + [ + 36.47043008117939, + 45.4458112314303 + ], + [ + 36.411182792482634, + 45.43610707766504 + ], + [ + 36.391371396258705, + 45.43991025572652 + ], + [ + 36.35959840231365, + 45.45407156049933 + ], + [ + 36.33960010612526, + 45.45695583486963 + ], + [ + 36.33025510790637, + 45.454464879327446 + ], + [ + 36.32053630976225, + 45.44856480887407 + ], + [ + 36.31156511147125, + 45.4438443081136 + ], + [ + 36.29885591389362, + 45.442795254299995 + ], + [ + 36.3072664122906, + 45.46115087970253 + ], + [ + 36.30016421364425, + 45.47320989503609 + ], + [ + 36.283717016779036, + 45.476355300848866 + ], + [ + 36.267082919949445, + 45.46704963343626 + ], + [ + 36.25213092279836, + 45.46115087970253 + ], + [ + 36.13681364478941, + 45.46219959214511 + ], + [ + 36.11700224855986, + 45.45721803432335 + ], + [ + 36.097003952371466, + 45.441483909606006 + ], + [ + 36.06952965760803, + 45.43046741078453 + ], + [ + 36.0655449627526, + 45.42553028973455 + ], + [ + 36.05134056545904, + 45.39535242162091 + ], + [ + 36.022557970944945, + 45.368441166003805 + ], + [ + 35.986486277818386, + 45.362926059418186 + ], + [ + 35.94723728529826, + 45.372380198658874 + ], + [ + 35.87220216002379, + 45.404075760536614 + ], + [ + 35.85388596351393, + 45.413916621802144 + ], + [ + 35.84715756479628, + 45.426379251448395 + ], + [ + 35.8524047739447, + 45.44386497541683 + ], + [ + 35.85950697259193, + 45.45933624762881 + ], + [ + 35.857824872912545, + 45.469953901705 + ], + [ + 35.83278027768503, + 45.47087138287168 + ], + [ + 35.8167068807486, + 45.46392436820739 + ], + [ + 35.80362388324218, + 45.44963442058864 + ], + [ + 35.79469305616038, + 45.42980210462429 + ], + [ + 35.791889556694684, + 45.41209230278156 + ], + [ + 35.772265060435046, + 45.39214572935421 + ], + [ + 35.767405661361295, + 45.38873311015669 + ], + [ + 35.75189296431793, + 45.386632934388984 + ], + [ + 35.7481549650407, + 45.379938103368545 + ], + [ + 35.746846665290036, + 45.369960021421576 + ], + [ + 35.74423006578874, + 45.36076812520648 + ], + [ + 35.71619507113218, + 45.34040932557082 + ], + [ + 35.69451467527287, + 45.32989869277279 + ], + [ + 35.51720627467216, + 45.29506847418358 + ], + [ + 35.48038698168983, + 45.2979608697527 + ], + [ + 35.33194061536096, + 45.371562726652314 + ], + [ + 35.04491375777232, + 45.669545248704424 + ], + [ + 35.00230056589345, + 45.7290693869553 + ], + [ + 34.70631294999043, + 46.024929846739866 + ], + [ + 34.35868883309806, + 46.106725558140795 + ], + [ + 34.00905273547181, + 46.55925987559425 + ] + ] + ] + } + } + } + }, + "version": 8, + "metadata": { + "maptiler:copyright": "This style was generated on MapTiler Cloud. Usage is governed by the license terms in https://github.com/maplibre/demotiles/blob/gh-pages/LICENSE", + "openmaptiles:version": "3.x" + } +} diff --git a/platform/darwin/bazel/files.bzl b/platform/darwin/bazel/files.bzl index 4db2748fb..560189cd6 100644 --- a/platform/darwin/bazel/files.bzl +++ b/platform/darwin/bazel/files.bzl @@ -106,6 +106,8 @@ MLN_DARWIN_OBJC_HEADERS = [ "src/NSExpression+MLNAdditions.h", "src/NSPredicate+MLNAdditions.h", "src/NSValue+MLNAdditions.h", + "src/MLNPluginLayer.h", + "src/MLNPluginStyleLayer.h", ] MLN_DARWIN_OBJCPP_HEADERS = [ @@ -163,6 +165,8 @@ MLN_DARWIN_PRIVATE_HEADERS = [ "src/MLNVectorTileSource_Private.h", "src/NSExpression+MLNPrivateAdditions.h", "src/NSPredicate+MLNPrivateAdditions.h", + "src/MLNPluginStyleLayer_Private.h" + ] MLN_DARWIN_PUBLIC_OBJCPP_SOURCE = [ @@ -217,6 +221,9 @@ MLN_DARWIN_PUBLIC_OBJCPP_SOURCE = [ "src/NSExpression+MLNAdditions.mm", "src/NSPredicate+MLNAdditions.mm", "src/NSValue+MLNStyleAttributeAdditions.mm", + "src/MLNPluginLayer.mm", + "src/MLNPluginStyleLayer.mm" + ] MLN_DARWIN_PUBLIC_OBJCPP_CUSTOM_DRAWABLE_SOURCE = [ "src/MLNCustomDrawableStyleLayer_Private.h", diff --git a/platform/darwin/darwin.cmake b/platform/darwin/darwin.cmake index de3bab2ce..4217aa752 100644 --- a/platform/darwin/darwin.cmake +++ b/platform/darwin/darwin.cmake @@ -162,6 +162,8 @@ add_library( EXCLUDE_FROM_ALL "${CMAKE_CURRENT_LIST_DIR}/app/ExampleCustomDrawableStyleLayer.mm" "${CMAKE_CURRENT_LIST_DIR}/app/CustomStyleLayerExample.m" + "${CMAKE_CURRENT_LIST_DIR}/app/PluginLayerExample.mm" + "${CMAKE_CURRENT_LIST_DIR}/app/PluginLayerExampleMetalRendering.mm" ) target_link_libraries( diff --git a/platform/darwin/src/MLNMapOptions.h b/platform/darwin/src/MLNMapOptions.h index 48267467c..89ce52eb0 100644 --- a/platform/darwin/src/MLNMapOptions.h +++ b/platform/darwin/src/MLNMapOptions.h @@ -32,6 +32,11 @@ MLN_EXPORT */ @property (nonatomic, nonnull) MLNActionJournalOptions *actionJournalOptions; +/** + List of plugin classes + */ +@property NSArray *pluginLayers; + @end NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MLNMapOptions.mm b/platform/darwin/src/MLNMapOptions.mm index d9ddabbc3..6c24047cd 100644 --- a/platform/darwin/src/MLNMapOptions.mm +++ b/platform/darwin/src/MLNMapOptions.mm @@ -13,6 +13,7 @@ { _styleURL = nil; _styleJSON = nil; + _pluginLayers = nil; _actionJournalOptions = [[MLNActionJournalOptions alloc] init]; } diff --git a/platform/darwin/src/MLNPluginLayer.h b/platform/darwin/src/MLNPluginLayer.h new file mode 100644 index 000000000..b188e4fb2 --- /dev/null +++ b/platform/darwin/src/MLNPluginLayer.h @@ -0,0 +1,109 @@ +#import +#import +#if TARGET_OS_IPHONE +#import +#else +#import +#endif + +#import "MLNFoundation.h" +#import "MLNGeometry.h" + +NS_ASSUME_NONNULL_BEGIN + +typedef enum { + MLNPluginLayerPropertyTypeUnknown, + MLNPluginLayerPropertyTypeSingleFloat, + MLNPluginLayerPropertyTypeColor +} MLNPluginLayerPropertyType; + +MLN_EXPORT +@interface MLNPluginLayerProperty : NSObject + ++ (MLNPluginLayerProperty *)propertyWithName:(NSString *)propertyName + propertyType:(MLNPluginLayerPropertyType)propertyType + defaultValue:(id)defaultValue; + +// The name of the property +@property (copy) NSString *propertyName; + +// The type of property +@property MLNPluginLayerPropertyType propertyType; + +// Single float default value +@property float singleFloatDefaultValue; + +// Color default value +#if TARGET_OS_IPHONE +@property (copy) UIColor *colorDefaultValue; +#else +@property NSColor *colorDefaultValue; +#endif + +@end + +typedef enum { + MLNPluginLayerTileKindGeometry, + MLNPluginLayerTileKindRaster, + MLNPluginLayerTileKindRasterDEM, + MLNPluginLayerTileKindNotRequired +} MLNPluginLayerTileKind; + +MLN_EXPORT +@interface MLNPluginLayerCapabilities : NSObject + +@property (copy) NSString *layerID; +@property BOOL requiresPass3D; + +//! This is a list of layer properties that this layer supports. +@property (copy) NSArray *layerProperties; + +@end + +@class MLNMapView; + +// Copied initially from MLNStyleLayerDrawingContext. Decided to copy instead of use since we +// might add additional properties here +/// A structure containing context needed to draw a frame in an ``MLNCustomStyleLayer``. +typedef struct MLNPluginLayerDrawingContext { + /// The size of the drawable area, in points. + CGSize size; + /// The center coordinate of the map view. + CLLocationCoordinate2D centerCoordinate; + /// The current zoom level of the map view. + double zoomLevel; + /// The heading (direction) in degrees clockwise from true north. + CLLocationDirection direction; + /// The current pitch of the map view in degrees, measured from the map plane. + CGFloat pitch; + /// The vertical field of view, in degrees, for the map’s perspective. + CGFloat fieldOfView; + /// A 4×4 matrix representing the map view’s current projection state. + MLNMatrix4 projectionMatrix; +} MLNPluginLayerDrawingContext; + +MLN_EXPORT +@interface MLNPluginLayer : NSObject + +/// Returns the layer capabilities of the plugin layer. +/// This must be overridden by the plug-in layer and return a set of capabilities ++ (MLNPluginLayerCapabilities *)layerCapabilities; + +// These are public methods that can be overridden by the plugin layer +/// Called when the layer is rendered +- (void)onRenderLayer:(MLNMapView *)mapView + renderEncoder:(id)renderEncoder; + +/// Called when the layer is updated in the render loop. This would update animations/etc +- (void)onUpdateLayer:(MLNPluginLayerDrawingContext)drawingContext; + +/// Called when the layer properties are updated. Can be on initial load from the JSON or when +/// dynamic properties are updated +- (void)onUpdateLayerProperties:(NSDictionary *)layerProperties; + +/// Added to a map view +- (void)didMoveToMapView:(MLNMapView *)mapView; + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MLNPluginLayer.mm b/platform/darwin/src/MLNPluginLayer.mm new file mode 100644 index 000000000..cff0044d0 --- /dev/null +++ b/platform/darwin/src/MLNPluginLayer.mm @@ -0,0 +1,80 @@ +#import "MLNPluginLayer.h" + +@implementation MLNPluginLayerProperty + ++(MLNPluginLayerProperty *)propertyWithName:(NSString *)propertyName + propertyType:(MLNPluginLayerPropertyType)propertyType + defaultValue:(id)defaultValue { + MLNPluginLayerProperty *tempResult = [[MLNPluginLayerProperty alloc] init]; + tempResult.propertyName = propertyName; + tempResult.propertyType = propertyType; + + if (propertyType == MLNPluginLayerPropertyTypeSingleFloat) { + if ([defaultValue isKindOfClass:[NSNumber class]]) { + tempResult.singleFloatDefaultValue = [defaultValue floatValue]; + } + } else if (propertyType == MLNPluginLayerPropertyTypeColor) { +#if TARGET_OS_IPHONE + if ([defaultValue isKindOfClass:[UIColor class]]) { +#else + if ([defaultValue isKindOfClass:[NSColor class]]) { +#endif + tempResult.colorDefaultValue = defaultValue; + } + } + + return tempResult; +} + + +-(id)init { + // Base class implemenation + if (self = [super init]) { + // Default setup + self.propertyType = MLNPluginLayerPropertyTypeUnknown; + self.propertyName = @"unknown"; + + // Default values for the various types + self.singleFloatDefaultValue = 1.0; + } + return self; +} + +@end + +@implementation MLNPluginLayer + +// This is the layer type in the style that is used ++(MLNPluginLayerCapabilities *)layerCapabilities { + + // Base class returns the class name just to return something + // TODO: Add an assert/etc or something to notify the developer that this needs to be overridden + return nil; + +} + +- (void)onRenderLayer:(MLNMapView *)mapView + renderEncoder:(id)renderEncoder { + // Base class does nothing +} + +- (void)onUpdateLayer:(MLNPluginLayerDrawingContext)drawingContext { + // Base class does nothing +} + +-(void)onUpdateLayerProperties:(NSDictionary *)layerProperties { + // Base class does nothing +} + +/// Added to a map view +- (void)didMoveToMapView:(MLNMapView *)mapView { + // Base class does nothing +} + + +@end + + + +@implementation MLNPluginLayerCapabilities +@end diff --git a/platform/darwin/src/MLNPluginStyleLayer.h b/platform/darwin/src/MLNPluginStyleLayer.h new file mode 100644 index 000000000..eaf7d8652 --- /dev/null +++ b/platform/darwin/src/MLNPluginStyleLayer.h @@ -0,0 +1,13 @@ +#import "MLNStyleLayer.h" + +NS_ASSUME_NONNULL_BEGIN + +@class MLNPluginLayer; + +@interface MLNPluginStyleLayer : MLNStyleLayer + +- (MLNPluginLayer *)pluginLayer; + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MLNPluginStyleLayer.mm b/platform/darwin/src/MLNPluginStyleLayer.mm new file mode 100644 index 000000000..c8dc03778 --- /dev/null +++ b/platform/darwin/src/MLNPluginStyleLayer.mm @@ -0,0 +1,35 @@ +#import "MLNPluginStyleLayer.h" +#import "MLNPluginStyleLayer_Private.h" +#import +#import +#import "MLNPluginLayer.h" + +@implementation MLNPluginStyleLayer + +-(void)getStats { + + mbgl::style::PluginLayer *l = (mbgl::style::PluginLayer *)self.rawLayer; + auto pl = l->impl(); + +} + +-(MLNPluginLayer *)pluginLayer { + + mbgl::style::PluginLayer *l = (mbgl::style::PluginLayer *)self.rawLayer; + if (l->_platformReference) { + MLNPluginLayer *pl = (__bridge MLNPluginLayer *)l->_platformReference; + return pl; + } + + return nil; + +} + + +@end + + +MLNStyleLayer* mbgl::PluginLayerPeerFactory::createPeer(style::Layer *rawLayer) { + return [[MLNPluginStyleLayer alloc] initWithRawLayer:rawLayer]; + +} diff --git a/platform/darwin/src/MLNPluginStyleLayer_Private.h b/platform/darwin/src/MLNPluginStyleLayer_Private.h new file mode 100644 index 000000000..f72b0ea2c --- /dev/null +++ b/platform/darwin/src/MLNPluginStyleLayer_Private.h @@ -0,0 +1,25 @@ +#pragma once + +#include "MLNStyleLayer_Private.h" + +#include + +namespace mbgl { + +class PluginLayerPeerFactory : public LayerPeerFactory, public mbgl::PluginLayerFactory { +public: + PluginLayerPeerFactory(std::string& layerType, + mbgl::style::LayerTypeInfo::Source source, + mbgl::style::LayerTypeInfo::Pass3D pass3D, + mbgl::style::LayerTypeInfo::Layout layout, + mbgl::style::LayerTypeInfo::FadingTiles fadingTiles, + mbgl::style::LayerTypeInfo::CrossTileIndex crossTileIndex, + mbgl::style::LayerTypeInfo::TileKind tileKind) + : mbgl::PluginLayerFactory(layerType, source, pass3D, layout, fadingTiles, crossTileIndex, tileKind) {} + + // LayerPeerFactory overrides. + LayerFactory* getCoreLayerFactory() final { return this; } + virtual MLNStyleLayer* createPeer(style::Layer*) final; +}; + +} // namespace mbgl diff --git a/platform/darwin/src/MLNStyleLayerManager.h b/platform/darwin/src/MLNStyleLayerManager.h index 0820175ad..aab007db5 100644 --- a/platform/darwin/src/MLNStyleLayerManager.h +++ b/platform/darwin/src/MLNStyleLayerManager.h @@ -18,12 +18,7 @@ public: MLNStyleLayer* createPeer(style::Layer*); -private: - LayerManagerDarwin(); - /** - * Enables a layer type for both JSON style and runtime API. - */ - void addLayerType(std::unique_ptr); +public: /** * Enables a layer type for JSON style only. * @@ -31,7 +26,15 @@ private: * in order to save binary size (the corresponding SDK layer wrappers * should be excluded from the project build). */ - void addLayerTypeCoreOnly(std::unique_ptr); + void addLayerTypeCoreOnly(std::unique_ptr) override; + + /** + * Enables a layer type for both JSON style and runtime API. + */ + void addLayerType(std::unique_ptr); + +private: + LayerManagerDarwin(); void registerCoreFactory(LayerFactory*); LayerPeerFactory* getPeerFactory(const style::LayerTypeInfo* typeInfo); diff --git a/platform/darwin/src/MLNStyleLayerManager.mm b/platform/darwin/src/MLNStyleLayerManager.mm index cb6fcbe08..4f5e0822b 100644 --- a/platform/darwin/src/MLNStyleLayerManager.mm +++ b/platform/darwin/src/MLNStyleLayerManager.mm @@ -109,7 +109,7 @@ void LayerManagerDarwin::registerCoreFactory(LayerFactory* factory) { LayerPeerFactory* LayerManagerDarwin::getPeerFactory(const mbgl::style::LayerTypeInfo* typeInfo) { for (const auto& factory: peerFactories) { - if (factory->getCoreLayerFactory()->getTypeInfo() == typeInfo) { + if (layerTypeInfoEquals(factory->getCoreLayerFactory()->getTypeInfo(), typeInfo)) { return factory.get(); } } @@ -127,7 +127,8 @@ LayerFactory* LayerManagerDarwin::getFactory(const mbgl::style::LayerTypeInfo* i } for (const auto& factory: coreFactories) { - if (factory->getTypeInfo() == info) { + if (layerTypeInfoEquals(factory->getTypeInfo(), info)) { + // if (factory->getTypeInfo() == info) { return factory.get(); } } diff --git a/platform/default/src/mbgl/layermanager/layer_manager.cpp b/platform/default/src/mbgl/layermanager/layer_manager.cpp index bad94e9b7..988c105eb 100644 --- a/platform/default/src/mbgl/layermanager/layer_manager.cpp +++ b/platform/default/src/mbgl/layermanager/layer_manager.cpp @@ -24,6 +24,15 @@ class LayerManagerDefault final : public LayerManager { public: LayerManagerDefault(); + /** + * Enables a layer type for JSON style only. + * + * We might not want to expose runtime API for some layer types + * in order to save binary size (the corresponding SDK layer wrappers + * should be excluded from the project build). + */ + void addLayerTypeCoreOnly(std::unique_ptr) override; + private: void addLayerType(std::unique_ptr); // LayerManager overrides. @@ -34,6 +43,10 @@ private: std::map typeToFactory; }; +void LayerManagerDefault::addLayerTypeCoreOnly(std::unique_ptr layerFactory) { + addLayerType(std::move(layerFactory)); +} + LayerManagerDefault::LayerManagerDefault() { #if !defined(MBGL_LAYER_FILL_DISABLE_ALL) addLayerType(std::make_unique()); @@ -90,7 +103,7 @@ void LayerManagerDefault::addLayerType(std::unique_ptr factory) { LayerFactory* LayerManagerDefault::getFactory(const mbgl::style::LayerTypeInfo* typeInfo) noexcept { assert(typeInfo); for (const auto& factory : factories) { - if (factory->getTypeInfo() == typeInfo) { + if (layerTypeInfoEquals(factory->getTypeInfo(), typeInfo)) { return factory.get(); } } diff --git a/platform/ios/BUILD.bazel b/platform/ios/BUILD.bazel index b818510d7..fa83c9af8 100644 --- a/platform/ios/BUILD.bazel +++ b/platform/ios/BUILD.bazel @@ -189,6 +189,7 @@ _IOS_APPLICATION_RESOURCES = [ "app/LaunchScreen.storyboard", "app/simple_route.json", "app/fill_filter_style.json", + "//platform/darwin:app/PluginLayerTestStyle.json", "//platform/darwin:test/amsterdam.geojson", ] + glob([ "app/*.lproj/**", diff --git a/platform/ios/MapLibre.docc/MapLibre.md b/platform/ios/MapLibre.docc/MapLibre.md index 8004db5db..32966f762 100644 --- a/platform/ios/MapLibre.docc/MapLibre.md +++ b/platform/ios/MapLibre.docc/MapLibre.md @@ -58,6 +58,7 @@ Powerful, free and open-source mapping toolkit with full control over data sourc - - +- ### Other Articles diff --git a/platform/ios/MapLibre.docc/PluginLayers.md b/platform/ios/MapLibre.docc/PluginLayers.md new file mode 100644 index 000000000..142a1e137 --- /dev/null +++ b/platform/ios/MapLibre.docc/PluginLayers.md @@ -0,0 +1,148 @@ +# Plugin Layers + +Plugin Layers are a way to add layer types that render themselves into the style parsing engine at runtime. It is a way to dynamically link new layer types to the MapLibre core -- and styling language -- without having to compile them into the library itself. + +Because the layers are bound at runtime, one or more different types of specialized layers can be added and different versions of layer types can be added. + +Currently Plugin Layers are only available on iOS/Darwin using the Metal Rendering pipeline. + + +## Creating a Plugin Layer + +Plugin Layers can be created by creating a descendant from MLNPluginLayer and add the class type to an instance of MLNMapView. The layer is self describing (e.g. the layer type, what properties are available, etc) and that information will be registered with the MapLibre core. When the plugin layer type is found in the style, the core will automatically instantiate it, pass along initial properties and manage the rendering of the layer. + +### Defining a layer's capabilities +The newly created layer class should override the layerCapabilites class method. Please note that this is a class method and not an instance method, so make sure it's prefaced with a + and not a -. + +The object that is returned from this method defines the layer type and the propeties that the layer expects. + +The triangle example (platform/darwin/app/PluginLayerExampleMetalRendering.mm) defines it's layer type "plugin-layer-metal-rendering" and two paint properties (scale and fill-color). These properties can be expression based. It's important to define the type of property (single float or color) and a default value. The properties returned by layerCapabilities will correspond to any properties in the "paint": part of the style. + +Initialization properties can also be added to the "properties": section of the layer style. + +```objc ++(MLNPluginLayerCapabilities *)layerCapabilities { + + MLNPluginLayerCapabilities *tempResult = [[MLNPluginLayerCapabilities alloc] init]; + tempResult.layerID = @"plugin-layer-metal-rendering"; + tempResult.requiresPass3D = YES; + + // Define the paint properties that this layer implements and + // what types they are + tempResult.layerProperties = @[ + // The scale property + [MLNPluginLayerProperty propertyWithName:@"scale" + propertyType:MLNPluginLayerPropertyTypeSingleFloat + defaultValue:@(1.0)], + + // The fill color property + [MLNPluginLayerProperty propertyWithName:@"fill-color" + propertyType:MLNPluginLayerPropertyTypeColor + defaultValue:[UIColor blueColor]] + + ]; + + return tempResult; + +} +``` + +For the triangle example, there are two initialization properties (offset-x and offset-y) for positioning and two paint properties, scale and fill-color. Here are three different instances of the plugin layer being added to the style. One that has a static scale (and uses the default color), one that has an expression based scale (and uses the default scale) and one that has an expression based scale and fill color. + + +```json +{ + "id": "metal-rendering-layer-1", + "type": "plugin-layer-metal-rendering", + "properties": { + "offset-x": -300, + "offset-y": -300 + }, + "paint": { + "scale": 2.5 + } +}, +{ + "id": "metal-rendering-layer-2", + "type": "plugin-layer-metal-rendering", + "properties": { + "offset-x": -300 + }, + "paint": { + "scale": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 5, + 0.5, + 15, + 3 + ] + } +}, +{ + "id": "metal-rendering-layer-3", + "type": "plugin-layer-metal-rendering", + "properties": { + "color1": "#DDAAFF", + "offset-x": 300 + }, + "paint": { + "scale": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 5, + 3, + 15, + 0.5 + ], + "fill-color": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 1, + "#ff0000", + 22, + "#00ff00" + ] + } +} +``` +![](PluginLayer.png) + +## Example 2: GLTF Plugin Layer + +In [this repository](https://github.com/AtlasProgramming/maplibre-gltf-models-plugin) you can find an example of a plugin layer that allows adding GLTF models. The following JSON is added to the style. + +``` +{ + "id": "model-layer", + "type": "hudhud::gltf-model-layer", + "properties": { + "model-source-resource":"models.json" + }, + "paint": { + "scale": 1.0 + } +} +``` + +@Video( + source: "gltfplugin.mp4", + poster: "gltfplugin.png", + alt: "GLTF Plugin in action.") { + GLTF Plugin showing 3D models of the Arc de Triomphe and the Eifel Tower. +} diff --git a/platform/ios/app/MBXViewController.mm b/platform/ios/app/MBXViewController.mm index b219244cd..4c13a767e 100644 --- a/platform/ios/app/MBXViewController.mm +++ b/platform/ios/app/MBXViewController.mm @@ -23,6 +23,11 @@ #import "MLNMapView_Experimental.h" #import +// Plug In Examples +#import "PluginLayerExample.h" +#import "PluginLayerExampleMetalRendering.h" +#import "MLNPluginStyleLayer.h" + static const CLLocationCoordinate2D WorldTourDestinations[] = { { .latitude = 38.8999418, .longitude = -77.033996 }, { .latitude = 37.7884307, .longitude = -122.3998631 }, @@ -270,10 +275,21 @@ CLLocationCoordinate2D randomWorldCoordinate(void) { return self; } +// This will add the plug-in layers. This is a demo of how +// extensible layers for the style can be added to the map view +-(void)addPluginLayers { + + [self.mapView addPluginLayerType:[PluginLayerExample class]]; + [self.mapView addPluginLayerType:[PluginLayerExampleMetalRendering class]]; + +} + - (void)viewDidLoad { [super viewDidLoad]; + [self addPluginLayers]; + // Keep track of current map state and debug preferences, // saving and restoring when the application's state changes. self.currentState = [MBXStateManager sharedManager].currentState; @@ -2314,10 +2330,19 @@ CLLocationCoordinate2D randomWorldCoordinate(void) { 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 + /// 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]; + /// Add MapLibre Styles if an `apiKey` exists NSString* apiKey = [MLNSettings apiKey]; if (apiKey.length) diff --git a/platform/ios/bazel/files.bzl b/platform/ios/bazel/files.bzl index 4667a1da8..bf9d220cd 100644 --- a/platform/ios/bazel/files.bzl +++ b/platform/ios/bazel/files.bzl @@ -11,7 +11,7 @@ MLN_IOS_SDK_HEADERS = [ "src/MLNUserLocation.h", "src/MLNUserLocationAnnotationView.h", "src/MLNUserLocationAnnotationViewStyle.h", - "src/MLNScaleBar.h", + "src/MLNScaleBar.h" ] MLN_IOS_PUBLIC_HEADERS = [ diff --git a/platform/ios/src/MLNMapView.h b/platform/ios/src/MLNMapView.h index f8455efe3..05107dac0 100644 --- a/platform/ios/src/MLNMapView.h +++ b/platform/ios/src/MLNMapView.h @@ -19,6 +19,7 @@ NS_ASSUME_NONNULL_BEGIN @class MLNPolygon; @class MLNScaleBar; @class MLNShape; +@class MLNPluginLayer; @protocol MLNMapViewDelegate; @protocol MLNAnnotation; @@ -2252,6 +2253,11 @@ vertically on the map. */ - (void)triggerRepaint; +/** + Adds a plug-in layer that is external to this library + */ +- (void)addPluginLayerType:(Class)pluginLayerClass; + @end NS_ASSUME_NONNULL_END diff --git a/platform/ios/src/MLNMapView.mm b/platform/ios/src/MLNMapView.mm index 56de8dc12..16acd1925 100644 --- a/platform/ios/src/MLNMapView.mm +++ b/platform/ios/src/MLNMapView.mm @@ -26,6 +26,13 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include #import "Mapbox.h" #import "MLNShape_Private.h" @@ -72,6 +79,9 @@ #import "MLNSettings_Private.h" #import "MLNActionJournalOptions_Private.h" #import "MLNMapProjection.h" +#import "MLNPluginLayer.h" +#import "MLNStyleLayerManager.h" +#include "MLNPluginStyleLayer_Private.h" #include #include @@ -442,6 +452,9 @@ public: @property (nonatomic) CADisplayLink *displayLink; @property (nonatomic, assign) BOOL needsDisplayRefresh; +// Plugin Layers +@property NSMutableArray *pluginLayers; + @end @implementation MLNMapView @@ -552,6 +565,12 @@ public: if (options) { + if (options.pluginLayers) { + for (Class c in options.pluginLayers) { + [self addPluginLayerType:c]; + } + } + if (options.styleURL) { self.styleURL = options.styleURL; } else if (options.styleJSON) { @@ -7624,6 +7643,146 @@ static void *windowScreenContext = &windowScreenContext; _mbglMap->triggerRepaint(); } +/** + Adds a plug-in layer that is external to this library + */ +-(void)addPluginLayerType:(Class)pluginLayerClass { + + auto layerManager = mbgl::LayerManager::get(); + auto darwinLayerManager = (mbgl::LayerManagerDarwin *)layerManager; + + MLNPluginLayerCapabilities *capabilities = [pluginLayerClass layerCapabilities]; + + std::string layerType = [capabilities.layerID UTF8String]; + + // Default values + mbgl::style::LayerTypeInfo::Source source = mbgl::style::LayerTypeInfo::Source::NotRequired; + mbgl::style::LayerTypeInfo::TileKind tileKind = mbgl::style::LayerTypeInfo::TileKind::NotRequired; + mbgl::style::LayerTypeInfo::FadingTiles fadingTiles = mbgl::style::LayerTypeInfo::FadingTiles::NotRequired; + mbgl::style::LayerTypeInfo::Layout layout = mbgl::style::LayerTypeInfo::Layout::NotRequired; + mbgl::style::LayerTypeInfo::CrossTileIndex crossTileIndex = mbgl::style::LayerTypeInfo::CrossTileIndex::NotRequired; + + mbgl::style::LayerTypeInfo::Pass3D pass3D = mbgl::style::LayerTypeInfo::Pass3D::NotRequired; + if (capabilities.requiresPass3D) { + pass3D = mbgl::style::LayerTypeInfo::Pass3D::Required; + } + + auto factory = std::make_unique(layerType, + source, + pass3D, + layout, + fadingTiles, + crossTileIndex, + tileKind); + + __weak MLNMapView *weakMapView = self; + + Class layerClass = pluginLayerClass; + factory->setOnLayerCreatedEvent([layerClass, weakMapView, pluginLayerClass](mbgl::style::PluginLayer *pluginLayer) { + + //NSLog(@"Creating Plugin Layer: %@", layerClass); + MLNPluginLayer *layer = [[layerClass alloc] init]; + if (!weakMapView.pluginLayers) { + weakMapView.pluginLayers = [NSMutableArray array]; + } + [weakMapView.pluginLayers addObject:layer]; + + // Use weak here so there isn't a retain cycle + MLNPluginLayer *weakPlugInLayer = layer; + + pluginLayer->_platformReference = (__bridge void *)layer; + + MLNPluginLayerCapabilities *capabilities = [pluginLayerClass layerCapabilities]; + auto pluginLayerImpl = (mbgl::style::PluginLayer::Impl *)pluginLayer->baseImpl.get(); + auto & pm = pluginLayerImpl->_propertyManager; + for (MLNPluginLayerProperty *property in capabilities.layerProperties) { + mbgl::style::PluginLayerProperty *p = new mbgl::style::PluginLayerProperty(); + switch (property.propertyType) { + case MLNPluginLayerPropertyTypeSingleFloat: + p->_propertyType = mbgl::style::PluginLayerProperty::PropertyType::SingleFloat; + p->_defaultSingleFloatValue = property.singleFloatDefaultValue; + break; + case MLNPluginLayerPropertyTypeColor: + { + p->_propertyType = mbgl::style::PluginLayerProperty::PropertyType::Color; + if (property.colorDefaultValue) { + CGFloat r, g, b, a; + [property.colorDefaultValue getRed:&r green:&g blue:&b alpha:&a]; + p->_defaultColorValue = mbgl::Color(r, g, b, a); + } + } + break; + default: + p->_propertyType = mbgl::style::PluginLayerProperty::PropertyType::Unknown; + break; + } + p->_propertyName = [property.propertyName UTF8String]; + pm.addProperty(p); + } + + // Set the render function + auto renderFunction = [weakPlugInLayer, weakMapView](mbgl::PaintParameters& paintParameters){ + + const mbgl::mtl::RenderPass& renderPass = static_cast(*paintParameters.renderPass); + id encoder = (__bridge id)renderPass.getMetalEncoder().get(); + + MLNMapView *strongMapView = weakMapView; + + const mbgl::TransformState& state = paintParameters.state; + + MLNPluginLayerDrawingContext drawingContext; + drawingContext.size = CGSizeMake(state.getSize().width, + state.getSize().height); + drawingContext.centerCoordinate = CLLocationCoordinate2DMake(state.getLatLng().latitude(), + state.getLatLng().longitude()); + drawingContext.zoomLevel = state.getZoom(); + drawingContext.direction = mbgl::util::rad2deg(-state.getBearing()); + drawingContext.pitch = state.getPitch(); + drawingContext.fieldOfView = state.getFieldOfView(); + mbgl::mat4 projMatrix; + state.getProjMatrix(projMatrix); + drawingContext.projectionMatrix = MLNMatrix4Make(projMatrix); + + //NSLog(@"Rendering"); + + // Call update with the scene state variables + [weakPlugInLayer onUpdateLayer:drawingContext]; + + // Call render + [weakPlugInLayer onRenderLayer:strongMapView + renderEncoder:encoder]; + }; + + // Set the lambdas + //auto pluginLayerImpl = (mbgl::style::PluginLayer::Impl *)pluginLayer->baseImpl.get(); + pluginLayerImpl->setRenderFunction(renderFunction); + + // Set the update properties function + pluginLayerImpl->setUpdatePropertiesFunction([weakPlugInLayer](const std::string & jsonProperties) { + // Use autorelease pools in lambdas + @autoreleasepool { + // Just wrap the string with NSData so it can be run through properties + NSData *d = [NSData dataWithBytesNoCopy:(void *)jsonProperties.data() length:jsonProperties.length() freeWhenDone:NO]; + NSError *error = nil; + NSDictionary *properties = [NSJSONSerialization JSONObjectWithData:d + options:0 + error:&error]; + if (error) { + // TODO: What should we do here? + } + [weakPlugInLayer onUpdateLayerProperties:properties]; + } + }); + + }); + + // TODO: Same question as above. Do we ever want to have a core only layer type? + // This could actually be something that we could set in the layer capabilities class + darwinLayerManager->addLayerType(std::move(factory)); + //darwinLayerManager->addLayerTypeCoreOnly(std::move(factory)); + +} + - (NSArray*)getActionJournalLogFiles { const auto& actionJournal = _mbglMap->getActionJournal(); diff --git a/src/mbgl/layermanager/layer_manager.cpp b/src/mbgl/layermanager/layer_manager.cpp index 26c0e7413..5dfeda299 100644 --- a/src/mbgl/layermanager/layer_manager.cpp +++ b/src/mbgl/layermanager/layer_manager.cpp @@ -11,6 +11,8 @@ namespace mbgl { +void LayerManager::addLayerTypeCoreOnly(std::unique_ptr) {} + std::unique_ptr LayerManager::createLayer(const std::string& type, const std::string& id, const style::conversion::Convertible& value, diff --git a/src/mbgl/plugin/plugin_layer.cpp b/src/mbgl/plugin/plugin_layer.cpp new file mode 100644 index 000000000..d976b8357 --- /dev/null +++ b/src/mbgl/plugin/plugin_layer.cpp @@ -0,0 +1,84 @@ +#include +#include + +#include + +namespace mbgl { +namespace style { + +PluginLayer::PluginLayer(const std::string& layerID, + const std::string& sourceID, + const style::LayerTypeInfo layerTypeInfo, + const std::string& layerProperties + + //,const style::conversion::Convertible& layerProperties + ) + : Layer(makeMutable(layerID, sourceID, layerTypeInfo, layerProperties)) {} + +PluginLayer::PluginLayer(Immutable impl_) + : Layer(std::move(impl_)) {} + +PluginLayer::~PluginLayer() = default; + +const PluginLayer::Impl& PluginLayer::impl() const { + return static_cast(*baseImpl); +} + +Mutable PluginLayer::mutableImpl() const { + return makeMutable(impl()); +} + +std::unique_ptr PluginLayer::cloneRef(const std::string& id_) const { + auto impl_ = mutableImpl(); + impl_->id = id_; + return std::make_unique(std::move(impl_)); +} + +void PluginLayer::Impl::stringifyLayout(rapidjson::Writer&) const {} + +Value PluginLayer::serialize() const { + auto result = Layer::serialize(); + return result; +} + +std::optional PluginLayer::setPropertyInternal(const std::string& name, + const conversion::Convertible& value) { + auto i = static_cast(baseImpl.get()); + auto pm = i->_propertyManager; + + // The properties should be defined when the plugin layer is created + PluginLayerProperty* property = pm.getProperty(name); // i->_propertyManager.getProperty(name); + if (property == nullptr) { + return Error{"layer doesn't support this property"}; + } + + Error error; + if (property->_propertyType == PluginLayerProperty::PropertyType::SingleFloat) { + const auto& tempValue = convert>(value, error, false, false); + if (!tempValue) { + return error; + } + property->setSingleFloat(tempValue.value()); + } else if (property->_propertyType == PluginLayerProperty::PropertyType::Color) { + const auto& tempValue = convert>(value, error, false, false); + if (!tempValue) { + return error; + } + property->setColor(tempValue.value()); + } + + return std::nullopt; +} + +StyleProperty PluginLayer::getProperty([[maybe_unused]] const std::string& name) const { + // TODO: Implement this via callback + // return getLayerProperty(*this, name); + return {}; +} + +Mutable PluginLayer::mutableBaseImpl() const { + return staticMutableCast(mutableImpl()); +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/plugin/plugin_layer.hpp b/src/mbgl/plugin/plugin_layer.hpp new file mode 100644 index 000000000..f8f95a4c4 --- /dev/null +++ b/src/mbgl/plugin/plugin_layer.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include + +namespace mbgl { + +namespace style { + +class PluginLayer final : public Layer { +public: + PluginLayer(const std::string& layerID, + const std::string& sourceID, + const style::LayerTypeInfo layerTypeInfo, + const std::string& layerProperties); + ~PluginLayer() override; + + // Private implementation + class Impl; + const Impl& impl() const; + + Mutable mutableImpl() const; + PluginLayer(Immutable); + std::unique_ptr cloneRef(const std::string& id) const final; + +public: + using OnRenderLayer = std::function; + using OnUpdateLayer = std::function; + using OnUpdateLayerProperties = std::function; + + void* _platformReference = nullptr; + +protected: + std::optional setPropertyInternal(const std::string& name, + const conversion::Convertible& value) final; + + StyleProperty getProperty(const std::string& name) const final; + Value serialize() const final; + + Mutable mutableBaseImpl() const final; +}; + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/plugin/plugin_layer_factory.cpp b/src/mbgl/plugin/plugin_layer_factory.cpp new file mode 100644 index 000000000..f33fb701f --- /dev/null +++ b/src/mbgl/plugin/plugin_layer_factory.cpp @@ -0,0 +1,188 @@ +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace mbgl { + +namespace plugins { + +// This is really hacky, but wanted to do it here to discuss if the +// const can be removed from teh regular LayerTypeInfo +struct NonConstLayerTypeInfo { + const char* type; + enum class Source { + Required, + NotRequired + } source; + enum class Pass3D { + Required, + NotRequired + } pass3d; + enum class Layout { + Required, + NotRequired + } layout; + enum class FadingTiles { + Required, + NotRequired + } fadingTiles; + enum class CrossTileIndex { + Required, + NotRequired + } crossTileIndex; + enum class TileKind : uint8_t { + Geometry, + Raster, + RasterDEM, + NotRequired + } tileKind; +}; + +} // namespace plugins + +style::LayerTypeInfo getDefaultInfo() { + style::LayerTypeInfo tempResult = {.type = "unknown", + .source = style::LayerTypeInfo::Source::Required, + .pass3d = style::LayerTypeInfo::Pass3D::Required, + .layout = style::LayerTypeInfo::Layout::NotRequired, + .fadingTiles = style::LayerTypeInfo::FadingTiles::NotRequired, + .crossTileIndex = style::LayerTypeInfo::CrossTileIndex::NotRequired, + .tileKind = style::LayerTypeInfo::TileKind::Geometry}; + return tempResult; +} + +PluginLayerFactory::PluginLayerFactory(std::string& layerType, + mbgl::style::LayerTypeInfo::Source source, + mbgl::style::LayerTypeInfo::Pass3D pass3D, + mbgl::style::LayerTypeInfo::Layout layout, + mbgl::style::LayerTypeInfo::FadingTiles fadingTiles, + mbgl::style::LayerTypeInfo::CrossTileIndex crossTileIndex, + mbgl::style::LayerTypeInfo::TileKind tileKind) + : _layerTypeInfo(getDefaultInfo()), + _layerType(layerType) { + _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); + lti->layout = (plugins::NonConstLayerTypeInfo::Layout)((int)layout); + lti->fadingTiles = (plugins::NonConstLayerTypeInfo::FadingTiles)((int)fadingTiles); + lti->crossTileIndex = (plugins::NonConstLayerTypeInfo::CrossTileIndex)((int)crossTileIndex); + lti->tileKind = (plugins::NonConstLayerTypeInfo::TileKind)((int)tileKind); +} + +const style::LayerTypeInfo* PluginLayerFactory::getTypeInfo() const noexcept { + return &_layerTypeInfo; +} + +void jsonStringFromConvertible(const style::conversion::Convertible& value, std::string& output) { + if (isObject(value)) { + output.append("{"); + bool firstItem = true; + eachMember(value, + [&output, &firstItem](const std::string& name, const style::conversion::Convertible& paramValue) + -> std::optional { + if (!firstItem) { + output.append(","); + } + firstItem = false; + output.append("\""); + output.append(name); + output.append("\":"); + + jsonStringFromConvertible(paramValue, output); + + return std::nullopt; + }); + output.append("}"); + } else if (isArray(value)) { + output.append("["); + bool firstItem = true; + + for (size_t i = 0; i < arrayLength(value); i++) { + if (!firstItem) { + output.append(","); + } + firstItem = false; + + auto itemValue = arrayMember(value, i); + jsonStringFromConvertible(itemValue, output); + } + + output.append("]"); + } else { + auto v = toValue(value); + if (auto iVal = v.value().getInt()) { + std::string tempResult = std::to_string(*iVal); + output.append(tempResult); + } else if (auto uIVal = v.value().getUint()) { + std::string tempResult = std::to_string(*uIVal); + output.append(tempResult); + + } else if (auto s = v.value().getString()) { + output.append("\""); + output.append(s->c_str()); + output.append("\""); + + } else if (auto d = v.value().getDouble()) { + output.append(std::to_string(*d)); + } + } +} + +std::unique_ptr PluginLayerFactory::createLayer(const std::string& id, + const style::conversion::Convertible& value) noexcept { + std::string layerProperties; + + if (auto memberValue = objectMember(value, "properties")) { + jsonStringFromConvertible(*memberValue, layerProperties); + if (isObject(*memberValue)) { + eachMember(*memberValue, + []([[maybe_unused]] const std::string& name, + [[maybe_unused]] const style::conversion::Convertible& paramValue) + -> std::optional { return std::nullopt; }); + } + } + + std::string source = "source"; + + auto tempResult = std::unique_ptr(new (std::nothrow) + style::PluginLayer(id, source, _layerTypeInfo, layerProperties + //,*customProperties + )); + + if (_onLayerCreated != nullptr) { + auto layerRaw = tempResult.get(); + auto pluginLayer = static_cast(layerRaw); + _onLayerCreated(pluginLayer); + auto pluginLayerImpl = (mbgl::style::PluginLayer::Impl*)pluginLayer->baseImpl.get(); + if (pluginLayerImpl->_updateLayerPropertiesFunction != nullptr) { + pluginLayerImpl->_updateLayerPropertiesFunction(layerProperties); + } + } + + return tempResult; +} + +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. + return nullptr; +} + +std::unique_ptr PluginLayerFactory::createRenderLayer(Immutable impl) noexcept { + auto localImpl = staticImmutableCast(impl); + auto tempResult = std::make_unique(staticImmutableCast(impl)); + tempResult->setRenderFunction(localImpl->_renderFunction); + tempResult->setUpdateFunction(localImpl->_updateFunction); + tempResult->setUpdatePropertiesFunction(localImpl->_updateLayerPropertiesFunction); + return tempResult; +} + +} // namespace mbgl diff --git a/src/mbgl/plugin/plugin_layer_factory.hpp b/src/mbgl/plugin/plugin_layer_factory.hpp new file mode 100644 index 000000000..0a976b37a --- /dev/null +++ b/src/mbgl/plugin/plugin_layer_factory.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include + +#include +#include + +namespace mbgl { + +namespace style { +// Forward +class PluginLayer; + +} // namespace style + +class PluginLayerFactory : public LayerFactory { +public: + PluginLayerFactory(std::string& layerType, + mbgl::style::LayerTypeInfo::Source source, + mbgl::style::LayerTypeInfo::Pass3D pass3D, + mbgl::style::LayerTypeInfo::Layout layout, + mbgl::style::LayerTypeInfo::FadingTiles fadingTiles, + mbgl::style::LayerTypeInfo::CrossTileIndex crossTileIndex, + mbgl::style::LayerTypeInfo::TileKind tileKind); + + using OnLayerCreatedEvent = std::function; + void setOnLayerCreatedEvent(OnLayerCreatedEvent onLayerCreated) { _onLayerCreated = onLayerCreated; } + +protected: + const style::LayerTypeInfo* getTypeInfo() const noexcept final; + std::unique_ptr createLayer(const std::string& id, + const style::conversion::Convertible& value) noexcept final; + std::unique_ptr createBucket(const BucketParameters&, + const std::vector>&) noexcept final; + std::unique_ptr createRenderLayer(Immutable) noexcept final; + +private: + // These is the layer type info that is setup during factory creation and returned in the getTypeInfo method + style::LayerTypeInfo _layerTypeInfo; + std::string _layerType; + + OnLayerCreatedEvent _onLayerCreated; +}; + +} // namespace mbgl diff --git a/src/mbgl/plugin/plugin_layer_impl.cpp b/src/mbgl/plugin/plugin_layer_impl.cpp new file mode 100644 index 000000000..d642d4809 --- /dev/null +++ b/src/mbgl/plugin/plugin_layer_impl.cpp @@ -0,0 +1,99 @@ +#include + +#include + +namespace mbgl { +namespace style { + +PluginLayer::Impl::Impl(std::string layerID, + std::string sourceID, + LayerTypeInfo layerTypeInfo, + const std::string& layerProperties) + : Layer::Impl(layerID, sourceID), + _layerTypeInfo(layerTypeInfo), + _layerProperties(layerProperties) {} + +bool PluginLayer::Impl::hasLayoutDifference([[maybe_unused]] const Layer::Impl& other) const { + return false; +} + +// Return this property as json +std::string PluginLayerProperty::asJSON() { + std::string tempResult; + + if (_propertyType == PropertyType::SingleFloat) { + tempResult = "\"" + _propertyName + "\":" + std::to_string(_singleFloatValue); + } else if (_propertyType == PropertyType::Color) { + // In RGBA format + tempResult = "\"" + _propertyName + "\":\"" + _dataDrivenColorValue.stringify() + "\""; + } + + return tempResult; +} + +void PluginLayerProperty::setPropertyValue([[maybe_unused]] const conversion::Convertible& value) {} + +const PropertyValue& PluginLayerProperty::getSingleFloat() const { + return _singleFloatProperty; +} + +void PluginLayerProperty::setSingleFloat(const PropertyValue& value) { + _singleFloatProperty = std::move(value); +} + +void PluginLayerProperty::setCurrentSingleFloatValue(float value) { + _singleFloatValue = value; +} + +PluginLayerProperty* PluginLayerPropertyManager::getProperty(const std::string& propertyName) { + if (_properties.count(propertyName) > 0) { + return _properties[propertyName]; + } + return nullptr; +} + +void PluginLayerPropertyManager::addProperty(PluginLayerProperty* property) { + _properties[property->_propertyName] = property; +} + +std::string PluginLayerPropertyManager::propertiesAsJSON() { + std::string tempResult = "{"; + + bool firstItem = true; + for (auto d : _properties) { + if (!firstItem) { + tempResult.append(", "); + } + firstItem = false; + tempResult.append(d.second->asJSON()); + } + tempResult.append("}"); + + return tempResult; +} + +std::vector PluginLayerPropertyManager::getProperties() { + std::vector tempResult; + tempResult.reserve(_properties.size()); + for (auto it = _properties.begin(); it != _properties.end(); ++it) { + tempResult.push_back(it->second); + } + return tempResult; +} + +const PropertyValue& PluginLayerProperty::getColor() const { + return _dataDrivenColorProperty; +} + +void PluginLayerProperty::setColor(const PropertyValue& value) { + _dataDrivenColorProperty = std::move(value); +} + +void PluginLayerProperty::setCurrentColorValue(mbgl::Color value) { + _dataDrivenColorValue = value; +} + +namespace conversion {} // namespace conversion + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/plugin/plugin_layer_impl.hpp b/src/mbgl/plugin/plugin_layer_impl.hpp new file mode 100644 index 000000000..512cec427 --- /dev/null +++ b/src/mbgl/plugin/plugin_layer_impl.hpp @@ -0,0 +1,122 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace mbgl { +namespace style { + +using namespace conversion; + +struct DataDrivenSingleFloatProperty : DataDrivenPaintProperty { + static float defaultValue() { return 1.f; } +}; + +struct DataDrivenColorProperty : DataDrivenPaintProperty { + static mbgl::Color defaultValue() { return mbgl::Color::black(); } + static constexpr auto expressionType() { return expression::type::ColorType{}; }; + using EvaluatorType = DataDrivenPropertyEvaluator; +}; + +class PluginLayerProperty { +public: + enum class PropertyType { + Unknown, + SingleFloat, + Color + }; + +public: + PropertyType _propertyType = PropertyType::Unknown; + std::string _propertyName; + void setPropertyValue(const conversion::Convertible& value); + +public: + const PropertyValue& getSingleFloat() const; + void setSingleFloat(const PropertyValue& value); + float _defaultSingleFloatValue = 1.0; + float _singleFloatValue = 0; + PropertyValue _singleFloatProperty; + void setCurrentSingleFloatValue(float value); + + // Color + const PropertyValue& getColor() const; + void setColor(const PropertyValue& value); + mbgl::Color _defaultColorValue = mbgl::Color::black(); + mbgl::Color _dataDrivenColorValue = mbgl::Color::black(); + PropertyValue _dataDrivenColorProperty; + void setCurrentColorValue(mbgl::Color value); + + // Return this property as json + std::string asJSON(); + +private: +}; + +class PluginLayerPropertyManager { +public: + PluginLayerProperty* getProperty(const std::string& propertyName); + void addProperty(PluginLayerProperty* property); + + std::string propertiesAsJSON(); + + std::vector getProperties(); + +private: + std::map _properties; +}; + +class PluginLayer::Impl : public Layer::Impl { +public: + Impl(std::string layerID, std::string sourceID, LayerTypeInfo layerTypeInfo, const std::string& layerProperties); + + using Layer::Impl::Impl; + + bool hasLayoutDifference(const Layer::Impl&) const override; + void stringifyLayout(rapidjson::Writer&) const override; + + const LayerTypeInfo* getTypeInfo() const noexcept final { return &_layerTypeInfo; } + + void setRenderFunction(OnRenderLayer renderFunction) { _renderFunction = renderFunction; } + + void setUpdateFunction(OnUpdateLayer updateFunction) { _updateFunction = updateFunction; } + + void setUpdatePropertiesFunction(OnUpdateLayerProperties updateLayerPropertiesFunction) { + _updateLayerPropertiesFunction = updateLayerPropertiesFunction; + } + + //! The property manager handles all of the custom properties for this layer type / instance + PluginLayerPropertyManager _propertyManager; + + //! Optional: Called when the layer is expected to render itself. + OnRenderLayer _renderFunction; + + //! Optional: Called when the layer is expected to update it's animations/etc. + // TODO: Does this need to be here or can it be done via the render function. Potentially, we could + // have this method called on a background thread/etc or use another way to parallalize work + OnUpdateLayer _updateFunction; + + //! Optional: Called when the layer properties change. The properties are passed as JSON for now + OnUpdateLayerProperties _updateLayerPropertiesFunction; + +private: + LayerTypeInfo _layerTypeInfo; + std::string _layerProperties; +}; + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/plugin/plugin_layer_properties.cpp b/src/mbgl/plugin/plugin_layer_properties.cpp new file mode 100644 index 000000000..bbf7a1330 --- /dev/null +++ b/src/mbgl/plugin/plugin_layer_properties.cpp @@ -0,0 +1,22 @@ + +#include +#include + +namespace mbgl { +namespace style { + +PluginLayerProperties::PluginLayerProperties(Immutable impl_) + : LayerProperties(std::move(impl_)) {} + +PluginLayerProperties::~PluginLayerProperties() = default; + +unsigned long PluginLayerProperties::constantsMask() const { + return 0; +} + +expression::Dependency PluginLayerProperties::getDependencies() const noexcept { + return expression::Dependency::None; +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/plugin/plugin_layer_properties.hpp b/src/mbgl/plugin/plugin_layer_properties.hpp new file mode 100644 index 000000000..68197468c --- /dev/null +++ b/src/mbgl/plugin/plugin_layer_properties.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include + +namespace mbgl { +namespace style { + +class PluginLayerProperties final : public LayerProperties { +public: + PluginLayerProperties(Immutable); + ~PluginLayerProperties() override; + + unsigned long constantsMask() const override; + + expression::Dependency getDependencies() const noexcept override; +}; + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/plugin/plugin_layer_render.cpp b/src/mbgl/plugin/plugin_layer_render.cpp new file mode 100644 index 000000000..5021fa33f --- /dev/null +++ b/src/mbgl/plugin/plugin_layer_render.cpp @@ -0,0 +1,199 @@ +#include +#include +#include +#include +#include +#if MLN_RENDER_BACKEND_METAL +#include +#include +#else +#include +#endif +#include +#include +#include +#include + +#include + +using namespace mbgl; + +namespace mbgl { +namespace gfx { +class Drawable; +} +} // namespace mbgl + +class RenderPluginLayerTweaker : public mbgl::gfx::DrawableTweaker { +public: + RenderPluginLayerTweaker(RenderPluginLayer* plugInRenderer) + : _plugInRenderer(plugInRenderer) {} + ~RenderPluginLayerTweaker() override = default; + + void init(mbgl::gfx::Drawable&) override; + + void execute(mbgl::gfx::Drawable&, mbgl::PaintParameters&) override; + +protected: + RenderPluginLayer* _plugInRenderer = nullptr; +}; + +void RenderPluginLayerTweaker::init([[maybe_unused]] mbgl::gfx::Drawable& drawablee) { + +}; + +void RenderPluginLayerTweaker::execute([[maybe_unused]] mbgl::gfx::Drawable& drawable, + [[maybe_unused]] mbgl::PaintParameters& paintParameters) { + // custom drawing + auto& context = paintParameters.context; + context.resetState(paintParameters.depthModeForSublayer(0, mbgl::gfx::DepthMaskType::ReadOnly), + paintParameters.colorModeForRenderPass()); + +#if MLN_RENDER_BACKEND_METAL + const auto& mtlRenderPass = static_cast(paintParameters.renderPass.get()); + mtlRenderPass->resetState(); + + style::mtl::CustomLayerRenderParameters parameters(paintParameters); +#else + style::CustomLayerRenderParameters parameters(paintParameters); +#endif + + _plugInRenderer->render(paintParameters); + + // Reset the view back to our original one, just in case the CustomLayer + // changed the viewport or Framebuffer. + paintParameters.backend.getDefaultRenderable().getResource().bind(); + + context.setDirtyState(); + context.bindGlobalUniformBuffers(*paintParameters.renderPass); +} + +RenderPluginLayer::RenderPluginLayer(Immutable _impl) + : RenderLayer(makeMutable(std::move(_impl))) {} + +RenderPluginLayer::~RenderPluginLayer() = default; + +void RenderPluginLayer::markLayerRenderable([[maybe_unused]] bool willRender, + [[maybe_unused]] UniqueChangeRequestVec& changes) { + isRenderable = true; +} + +/// Generate any changes needed by the layer +void RenderPluginLayer::update([[maybe_unused]] gfx::ShaderRegistry& shaderRegistery, + [[maybe_unused]] gfx::Context& context, + [[maybe_unused]] const TransformState& transformState, + [[maybe_unused]] const std::shared_ptr& updateParameters, + [[maybe_unused]] const RenderTree& renderTree, + [[maybe_unused]] UniqueChangeRequestVec& changes) { + // 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; + } +} + +void RenderPluginLayer::upload([[maybe_unused]] gfx::UploadPass& uploadPass) {} + +void RenderPluginLayer::render(PaintParameters& paintParameters) { + if (_renderFunction) { + _renderFunction(paintParameters); + } +} + +void RenderPluginLayer::prepare(const LayerPrepareParameters& layerParameters) { + if (_updateFunction) { + _updateFunction(layerParameters); + } +} + +// --- Private methods +void RenderPluginLayer::transition([[maybe_unused]] const TransitionParameters& parameters) { + // Called when switching between styles +} + +void RenderPluginLayer::evaluate(const PropertyEvaluationParameters& parameters) { + auto i = static_cast(baseImpl.get()); + + auto pm = i->_propertyManager; + for (auto property : pm.getProperties()) { + if (property->_propertyType == style::PluginLayerProperty::PropertyType::SingleFloat) { + auto& f = property->getSingleFloat(); + using Evaluator = typename style::DataDrivenSingleFloatProperty::EvaluatorType; + auto df = property->_defaultSingleFloatValue; + auto newF = f.evaluate(Evaluator(parameters, df), parameters.now); + auto v = newF.constant().value(); + property->setCurrentSingleFloatValue(v); + } else if (property->_propertyType == style::PluginLayerProperty::PropertyType::Color) { + auto& f = property->getColor(); + using Evaluator = typename style::DataDrivenColorProperty::EvaluatorType; + auto df = property->_defaultColorValue; + auto newF = f.evaluate(Evaluator(parameters, df), parameters.now); + auto v = newF.constant().value(); + property->setCurrentColorValue(v); + } + } + + std::string jsonProperties = pm.propertiesAsJSON(); + + i->_updateLayerPropertiesFunction(jsonProperties); +} + +bool RenderPluginLayer::hasTransition() const { + return true; +} + +bool RenderPluginLayer::hasCrossfade() const { + return false; +} +bool RenderPluginLayer::queryIntersectsFeature([[maybe_unused]] const GeometryCoordinates&, + [[maybe_unused]] const GeometryTileFeature&, + [[maybe_unused]] float, + [[maybe_unused]] const TransformState&, + [[maybe_unused]] float, + [[maybe_unused]] const mat4&, + [[maybe_unused]] const FeatureState&) const { + return false; +} + +void RenderPluginLayer::layerChanged([[maybe_unused]] const TransitionParameters& parameters, + [[maybe_unused]] const Immutable& impl, + [[maybe_unused]] UniqueChangeRequestVec& changes) {} + +/// 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&) { + return 0; +} + +/// Remove all the drawables for tiles +/// @return The number of drawables actually removed. +std::size_t RenderPluginLayer::removeAllDrawables() { + return 0; +} diff --git a/src/mbgl/plugin/plugin_layer_render.hpp b/src/mbgl/plugin/plugin_layer_render.hpp new file mode 100644 index 000000000..cc2ea2db7 --- /dev/null +++ b/src/mbgl/plugin/plugin_layer_render.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include +#include + +#include + +namespace mbgl { + +class RenderPluginLayer final : public RenderLayer { +public: + explicit RenderPluginLayer(Immutable); + ~RenderPluginLayer() override; + + void markLayerRenderable(bool willRender, UniqueChangeRequestVec& changes) override; + + /// Generate any changes needed by the layer + void update(gfx::ShaderRegistry&, + gfx::Context&, + const TransformState&, + const std::shared_ptr&, + const RenderTree&, + UniqueChangeRequestVec&) override; + + void prepare(const LayerPrepareParameters&) override; + + void upload(gfx::UploadPass&) override; + void render(PaintParameters&) override; + + void setRenderFunction(style::PluginLayer::OnRenderLayer renderFunction) { _renderFunction = renderFunction; } + void setUpdateFunction(style::PluginLayer::OnUpdateLayer updateFunction) { _updateFunction = updateFunction; } + void setUpdatePropertiesFunction(style::PluginLayer::OnUpdateLayerProperties updateLayerPropertiesFunction) { + _updateLayerPropertiesFunction = updateLayerPropertiesFunction; + } + +private: + void transition(const TransitionParameters&) override; + void evaluate(const PropertyEvaluationParameters&) override; + bool hasTransition() const override; + bool hasCrossfade() const override; + bool queryIntersectsFeature(const GeometryCoordinates&, + const GeometryTileFeature&, + float, + const TransformState&, + float, + const mat4&, + const FeatureState&) const override; + void updateColorRamp(); + + void layerChanged(const TransitionParameters& parameters, + const Immutable& impl, + UniqueChangeRequestVec& changes) override; + + /// Remove all drawables for the tile from the layer group + /// @return The number of drawables actually removed. + std::size_t removeTile(RenderPass, const OverscaledTileID&) override; + + /// Remove all the drawables for tiles + /// @return The number of drawables actually removed. + std::size_t removeAllDrawables() override; + + // The render methods + style::PluginLayer::OnRenderLayer _renderFunction = nullptr; + + style::PluginLayer::OnUpdateLayer _updateFunction = nullptr; + + style::PluginLayer::OnUpdateLayerProperties _updateLayerPropertiesFunction = nullptr; +}; + +} // namespace mbgl diff --git a/src/mbgl/style/layer.cpp b/src/mbgl/style/layer.cpp index 2b7f70132..121a637bc 100644 --- a/src/mbgl/style/layer.cpp +++ b/src/mbgl/style/layer.cpp @@ -12,6 +12,14 @@ namespace mbgl { namespace style { +// Added this to support plugins and that their LayerTypeInfo isn't the same point +// across the board +bool layerTypeInfoEquals(const mbgl::style::LayerTypeInfo* one, const mbgl::style::LayerTypeInfo* other) { + return ((strcmp(one->type, other->type) == 0) && (one->source == other->source) && (one->pass3d == other->pass3d) && + (one->layout == other->layout) && (one->fadingTiles == other->fadingTiles) && + (one->crossTileIndex == other->crossTileIndex) && (one->tileKind == other->tileKind)); +}; + static_assert(mbgl::underlying_type(Tile::Kind::Geometry) == mbgl::underlying_type(LayerTypeInfo::TileKind::Geometry), "tile kind error"); static_assert(mbgl::underlying_type(Tile::Kind::Raster) == mbgl::underlying_type(LayerTypeInfo::TileKind::Raster), diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 940c1e658..162caf3a7 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -18,6 +18,7 @@ add_library( ${PROJECT_SOURCE_DIR}/test/math/minmax.test.cpp ${PROJECT_SOURCE_DIR}/test/math/wrap.test.cpp ${PROJECT_SOURCE_DIR}/test/platform/settings.test.cpp + ${PROJECT_SOURCE_DIR}/test/plugin/plugin.test.cpp ${PROJECT_SOURCE_DIR}/test/renderer/image_manager.test.cpp ${PROJECT_SOURCE_DIR}/test/renderer/pattern_atlas.test.cpp ${PROJECT_SOURCE_DIR}/test/renderer/shader_registry.test.cpp diff --git a/test/plugin/plugin.test.cpp b/test/plugin/plugin.test.cpp new file mode 100644 index 000000000..319218ae3 --- /dev/null +++ b/test/plugin/plugin.test.cpp @@ -0,0 +1,236 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +using namespace mbgl; +using namespace mbgl::style; +using namespace std::literals::string_literals; + +template +class MapTest { +public: + util::RunLoop runLoop; + std::shared_ptr fileSource; + StubMapObserver observer; + Frontend frontend; + MapAdapter map; + + MapTest(float pixelRatio = 1, MapMode mode = MapMode::Static) + : fileSource(std::make_shared(ResourceOptions::Default(), ClientOptions())), + frontend(pixelRatio), + map(frontend, + observer, + fileSource, + MapOptions().withMapMode(mode).withSize(frontend.getSize()).withPixelRatio(pixelRatio)) {} + + explicit MapTest(MapOptions options) + : fileSource(std::make_shared()), + frontend(options.pixelRatio()), + map(frontend, observer, fileSource, options.withSize(frontend.getSize())) {} + + template + MapTest(const std::string& cachePath, + const std::string& assetPath, + float pixelRatio = 1, + MapMode mode = MapMode::Static, + typename std::enable_if_t>* = nullptr) + : fileSource(std::make_shared(ResourceOptions().withCachePath(cachePath).withAssetPath(assetPath), + ClientOptions())), + frontend(pixelRatio), + map(frontend, + observer, + fileSource, + MapOptions().withMapMode(mode).withSize(frontend.getSize()).withPixelRatio(pixelRatio)) {} + + template + MapTest(const ResourceOptions& resourceOptions, + const ClientOptions& clientOptions = ClientOptions(), + float pixelRatio = 1, + MapMode mode = MapMode::Static) + : fileSource(std::make_shared(resourceOptions, clientOptions)), + frontend(pixelRatio), + map(frontend, + observer, + fileSource, + MapOptions().withMapMode(mode).withSize(frontend.getSize()).withPixelRatio(pixelRatio)) {} +}; + +TEST(Plugin, PluginLayerProperty) { + bool _singleFloatValid = false; + bool _colorValid = false; + + std::cout << "Test: Plugin.PluginLayerProperty\n"; + + { + PluginLayerProperty p; + p._propertyType = PluginLayerProperty::PropertyType::SingleFloat; + p._singleFloatValue = 1.0; + auto json = p.asJSON(); + std::string testValue = R"("":1.000000)"; + std::cout << " _singleFloatValid: " << json << ";|" << testValue << "|\n"; + _singleFloatValid = json == testValue; + EXPECT_STREQ(testValue.c_str(), json.c_str()); + } + + { + PluginLayerProperty p; + p._propertyType = PluginLayerProperty::PropertyType::Color; + p._dataDrivenColorValue = Color(1, 1, 1, 1); + auto json = p.asJSON(); + std::string testValue = R"JSON("":"rgba(255,255,255,1)")JSON"; + std::cout << " _colorValid: |" << json << "|;|" << testValue << "|\n"; + _colorValid = json == testValue; + EXPECT_STREQ(testValue.c_str(), json.c_str()); + } + + ASSERT_TRUE(_colorValid); + ASSERT_TRUE(_singleFloatValid); +} + +TEST(Plugin, PluginLayerPropertyManager) { + PluginLayerPropertyManager pm; + PluginLayerProperty* p1 = new PluginLayerProperty(); + p1->_propertyType = PluginLayerProperty::PropertyType::SingleFloat; + p1->_propertyName = "float"; + p1->_singleFloatValue = 1.0; + pm.addProperty(p1); + + PluginLayerProperty* p2 = new PluginLayerProperty(); + p2->_propertyType = PluginLayerProperty::PropertyType::Color; + p2->_propertyName = "color"; + p2->_dataDrivenColorValue = Color(1, 1, 1, 1); + pm.addProperty(p2); + + auto json = pm.propertiesAsJSON(); + + std::cout << "Test: Plugin.PluginLayerPropertyManager\n"; + std::string testValue = R"JSON({"color":"rgba(255,255,255,1)", "float":1.000000})JSON"; + std::cout << " JSON: |" << json << "|;|" << testValue << "|\n"; + EXPECT_STREQ(testValue.c_str(), json.c_str()); + + bool _jsonValid = (json == testValue); + ASSERT_TRUE(_jsonValid); +} + +TEST(Plugin, PluginLayer) { + bool _layerCreated = false; + bool _initialPropertiesFound = false; + bool _layerRendered = false; + bool _paintPropertiesFound = false; + + // Create the plug-in layer type + std::string layerType = "plugin-layer-test"; + auto pluginLayerFactory = std::make_unique( + layerType, + mbgl::style::LayerTypeInfo::Source::NotRequired, + mbgl::style::LayerTypeInfo::Pass3D::NotRequired, + mbgl::style::LayerTypeInfo::Layout::NotRequired, + mbgl::style::LayerTypeInfo::FadingTiles::NotRequired, + mbgl::style::LayerTypeInfo::CrossTileIndex::NotRequired, + mbgl::style::LayerTypeInfo::TileKind::NotRequired); + pluginLayerFactory->setOnLayerCreatedEvent( + [&_layerRendered, &_layerCreated, &_initialPropertiesFound, &_paintPropertiesFound]( + mbgl::style::PluginLayer* pluginLayer) { + // std::cout << "On Layer Created\n"; + _layerCreated = true; + + auto pluginLayerImpl = (mbgl::style::PluginLayer::Impl*)pluginLayer->baseImpl.get(); + auto& pm = pluginLayerImpl->_propertyManager; + PluginLayerProperty* p = new PluginLayerProperty(); + p->_propertyName = "scale"; + p->_propertyType = PluginLayerProperty::PropertyType::SingleFloat; + p->_defaultSingleFloatValue = 1.0; + pm.addProperty(p); + pluginLayerImpl->setRenderFunction([&_layerRendered]([[maybe_unused]] PaintParameters& paintParameters) { + // std::cout << "On Layer Rendered\n"; + // std::cout << " Paint Properties.currentLayer: " << paintParameters.currentLayer << + // "\n"; + _layerRendered = true; + }); + + pluginLayerImpl->setUpdatePropertiesFunction( + [&_initialPropertiesFound, &_paintPropertiesFound](const std::string& properties) { + // std::cout << "On Layer Update Properties\n"; + // std::cout << " -> " << properties << "\n"; + + auto propertiesCheck = R"({"custom-property1":"custom-property-value1"})"; + if (properties == propertiesCheck) { + _initialPropertiesFound = true; + } + if (properties.find("scale") > 0) { + _paintPropertiesFound = true; + } + }); + }); + + auto lm = LayerManager::get(); + lm->addLayerTypeCoreOnly(std::move(pluginLayerFactory)); + + MapTest<> test{1, MapMode::Continuous}; + test.map.getStyle().loadJSON(R"STYLE({ + "version": 8, + "layers": [{ + "id": "plugin-layer-1", + "type": "plugin-layer-test", + "properties": { + "custom-property1":"custom-property-value1" + }, + "paint": {"scale": 1.0 } + }] + })STYLE"); + + for (int i = 0; i < 10; i++) { + test.runLoop.runOnce(); + } + + ASSERT_TRUE(_layerCreated); + ASSERT_TRUE(_initialPropertiesFound); + ASSERT_TRUE(_layerRendered); + ASSERT_TRUE(_paintPropertiesFound); +}