From c7bd4dcc801bce577a6330af50bf6ebf0d973e97 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Tue, 28 Apr 2026 09:57:53 -0400 Subject: [PATCH 01/63] Update to maplibre with color relief/hillshade --- dependencies/maplibre-native | 2 +- include/CameraOptions.h | 18 ++++++ include/Enums.h | 34 ++++++++++ include/FreeCameraOptions.h | 8 +-- include/Map.h | 14 ++++ include/MapObserver.h | 38 +++++++++-- include/RenderingStats.h | 33 ++++++++++ src/CameraOptions.cpp | 54 ++++++++++++++++ src/FreeCameraOptions.cpp | 4 +- src/Map.cpp | 70 ++++++++++++++++++++ src/MapObserver.cpp | 60 ++++++++++++++--- src/RenderingStats.cpp | 121 ++++++++++++----------------------- 12 files changed, 353 insertions(+), 103 deletions(-) diff --git a/dependencies/maplibre-native b/dependencies/maplibre-native index a37c8b8..d4bde4c 160000 --- a/dependencies/maplibre-native +++ b/dependencies/maplibre-native @@ -1 +1 @@ -Subproject commit a37c8b8f73de8d357f1847f103646b8e514abd58 +Subproject commit d4bde4c93eb15650d0ddb6822d24351f792ea649 diff --git a/include/CameraOptions.h b/include/CameraOptions.h index 3925ff0..60848a9 100644 --- a/include/CameraOptions.h +++ b/include/CameraOptions.h @@ -25,11 +25,14 @@ namespace DOTNET_NAMESPACE ~CameraOptions(); CameraOptions^ WithCenter(LatLng^ o); + CameraOptions^ WithCenterAltitude(System::Nullable o); CameraOptions^ WithPadding(EdgeInsets^ p); CameraOptions^ WithAnchor(System::Nullable o); CameraOptions^ WithZoom(System::Nullable o); CameraOptions^ WithBearing(System::Nullable o); CameraOptions^ WithPitch(System::Nullable o); + CameraOptions^ WithRoll(System::Nullable o); + CameraOptions^ WithFov(System::Nullable o); /// /// Coordinate at the center of the map. @@ -64,6 +67,21 @@ namespace DOTNET_NAMESPACE /// property System::Nullable Pitch { System::Nullable get(); System::Void set(System::Nullable value); } + /// + /// Camera roll, measured in degrees. + /// + property System::Nullable Roll { System::Nullable get(); System::Void set(System::Nullable value); } + + /// + /// Camera vertical field of view, measured in degrees. + /// + property System::Nullable Fov { System::Nullable get(); System::Void set(System::Nullable value); } + + /// + /// Altitude of the center of the map, in meters above sea level. + /// + property System::Nullable CenterAltitude { System::Nullable get(); System::Void set(System::Nullable value); } + static System::Boolean operator==(CameraOptions^ a, CameraOptions^ b); static System::Boolean operator!=(CameraOptions^ a, CameraOptions^ b); internal: diff --git a/include/Enums.h b/include/Enums.h index bab6453..418bbdb 100644 --- a/include/Enums.h +++ b/include/Enums.h @@ -14,6 +14,40 @@ namespace DOTNET_NAMESPACE None, HeightOnly, WidthAndHeight, + /// + /// Constrain the map so that the screen bounds match the lat/lng bounds set via BoundOptions. + /// + Screen, + }; + + /// + /// Selects the tile LOD (level of detail) algorithm used when rendering at high pitch. + /// + public enum class TileLodMode : System::Byte + { + /// + /// Default tile LOD algorithm (center-of-screen based). + /// + Default, + /// + /// Distance-based tile LOD algorithm (tiles closer to the camera are rendered at higher detail). + /// + Distance, + }; + + /// + /// The type of a map style source. + /// + public enum class SourceType : System::Byte + { + Vector, + Raster, + RasterDEM, + GeoJSON, + Video, + Annotations, + Image, + CustomVector, }; public enum class ViewportMode : System::UInt32 diff --git a/include/FreeCameraOptions.h b/include/FreeCameraOptions.h index 43872ed..2eb61ab 100644 --- a/include/FreeCameraOptions.h +++ b/include/FreeCameraOptions.h @@ -44,13 +44,14 @@ namespace DOTNET_NAMESPACE System::Void LookAtPoint(LatLng^ location, Vec3^ upVector); /// - /// Helper function for setting the orientation of the camera as a pitch and - /// a bearing.Both values are in degrees + /// Helper function for setting the orientation of the camera as a roll, pitch and + /// a bearing. All values are in degrees. /// + /// /// /// /// - System::Void SetPitchBearing(System::Double pitch, System::Double bearing); + System::Void SetRollPitchBearing(System::Double roll, System::Double pitch, System::Double bearing); /// /// Position of the camera in slightly modified web mercator coordinates @@ -70,7 +71,6 @@ namespace DOTNET_NAMESPACE /// orientation of the map: forward [0, 0, -1], up [0, -1, 0], right [1, 0, 0] /// /// Orientation can be set freely but certain constraints still apply - /// - Orientation must be representable with only pitch and bearing. /// - Pitch has an upper limit /// property Vec4^ Orientation { Vec4^ get(); System::Void set(Vec4^ value); } diff --git a/include/Map.h b/include/Map.h index bff89fb..95aa714 100644 --- a/include/Map.h +++ b/include/Map.h @@ -130,6 +130,8 @@ namespace DOTNET_NAMESPACE System::Void SetConstrainMode(ConstrainMode mode); System::Void SetViewportMode(ViewportMode mode); System::Void SetSize(Size^ size); + System::Void SetFrustumOffset(EdgeInsets^ offset); + EdgeInsets^ GetFrustumOffset(); ScreenCoordinate PixelForLatLng(LatLng^ latLng); LatLng^ LatLngForPixel(ScreenCoordinate pixel); System::Collections::Generic::IEnumerable^ PixelsForLatLngs(System::Collections::Generic::IEnumerable^ latLngs); @@ -159,7 +161,19 @@ namespace DOTNET_NAMESPACE /// property System::Byte PrefetchZoomDelta { System::Byte get(); System::Void set(System::Byte value); } property MapDebugOptions Debug { MapDebugOptions get(); System::Void set(MapDebugOptions value); } + property System::Boolean IsRenderingStatsViewEnabled { System::Boolean get(); System::Void set(System::Boolean value); } property System::Boolean IsFullyLoaded { System::Boolean get(); } + + /// + /// The number of map tile requests can be reduced by using a lower level of details away from + /// the camera. This can improve performance, particularly when the camera pitch is high. + /// See the maplibre-native documentation for details on the LOD heuristic parameters. + /// + property System::Double TileLodMinRadius { System::Double get(); System::Void set(System::Double value); } + property System::Double TileLodScale { System::Double get(); System::Void set(System::Double value); } + property System::Double TileLodPitchThreshold { System::Double get(); System::Void set(System::Double value); } + property System::Double TileLodZoomShift { System::Double get(); System::Void set(System::Double value); } + property TileLodMode TileLodMode_ { TileLodMode get(); System::Void set(TileLodMode value); } /// /// FreeCameraOptions provides more direct access to the underlying camera diff --git a/include/MapObserver.h b/include/MapObserver.h index eb4fb75..2488629 100644 --- a/include/MapObserver.h +++ b/include/MapObserver.h @@ -1,7 +1,10 @@ #pragma once #include "Convert.h" +#include "Enums.h" #include "NativeWrapper.h" #include +#include +#include #include #include @@ -58,8 +61,7 @@ namespace DOTNET_NAMESPACE delegate System::Void WillStartRenderingMapHandler(); delegate System::Void DidFinishRenderingMapHandler(RenderMode mode); delegate System::Void DidFinishLoadingStyleHandler(); - // TODO: implement the managed version - //delegate System::Void SourceChangedHandler(Source^ source); + delegate System::Void SourceChangedHandler(System::String^ id, SourceType type); delegate System::Void DidBecomeIdleHandler(); delegate System::Void StyleImageMissingHandler(System::String^ id); @@ -70,6 +72,13 @@ namespace DOTNET_NAMESPACE delegate System::Boolean CanRemoveUnusedStyleImageHandler(System::String^ id); delegate System::Void RegisterShadersHandler(ShaderRegistry^ shaderRegistry); + /// Fired when a shader begins compiling. + delegate System::Void PreCompileShaderHandler(System::UInt32 shaderType, System::Byte backendType, System::String^ defines); + /// Fired when a shader finishes compiling. + delegate System::Void PostCompileShaderHandler(System::UInt32 shaderType, System::Byte backendType, System::String^ defines); + /// Fired when a shader fails to compile. + delegate System::Void ShaderCompileFailedHandler(System::UInt32 shaderType, System::Byte backendType, System::String^ defines); + event CameraWillChangeHandler^ CameraWillChange; event CameraIsChangingHandler^ CameraIsChanging; event CameraDidChangeHandler^ CameraDidChange; @@ -81,8 +90,7 @@ namespace DOTNET_NAMESPACE event WillStartRenderingMapHandler^ WillStartRenderingMap; event DidFinishRenderingMapHandler^ DidFinishRenderingMap; event DidFinishLoadingStyleHandler^ DidFinishLoadingStyle; - // TODO: implement the managed version - //event SourceChangedHandler^ SourceChanged; + event SourceChangedHandler^ SourceChanged; event DidBecomeIdleHandler^ DidBecomeIdle; event StyleImageMissingHandler^ StyleImageMissing; event CanRemoveUnusedStyleImageHandler^ CanRemoveUnusedStyleImage; @@ -93,6 +101,13 @@ namespace DOTNET_NAMESPACE /// event RegisterShadersHandler^ RegisterShaders; + /// Fired when a shader begins compiling. + event PreCompileShaderHandler^ PreCompileShader; + /// Fired when a shader finishes compiling successfully. + event PostCompileShaderHandler^ PostCompileShader; + /// Fired when a shader fails to compile. + event ShaderCompileFailedHandler^ ShaderCompileFailed; + MapObserver(); ~MapObserver(); @@ -108,7 +123,7 @@ namespace DOTNET_NAMESPACE virtual System::Void onWillStartRenderingMap(); virtual System::Void onDidFinishRenderingMap(RenderMode mode); virtual System::Void onDidFinishLoadingStyle(); - //virtual System::Void onSourceChanged(Source^ source); + virtual System::Void onSourceChanged(System::String^ id, SourceType type); virtual System::Void onDidBecomeIdle(); virtual System::Void onStyleImageMissing(System::String^ id); @@ -120,6 +135,9 @@ namespace DOTNET_NAMESPACE /// virtual System::Boolean onCanRemoveUnusedStyleImage(System::String^ id); virtual System::Void onRegisterShaders(ShaderRegistry^ shaderRegistry); + virtual System::Void onPreCompileShader(System::UInt32 shaderType, System::Byte backendType, System::String^ defines); + virtual System::Void onPostCompileShader(System::UInt32 shaderType, System::Byte backendType, System::String^ defines); + virtual System::Void onShaderCompileFailed(System::UInt32 shaderType, System::Byte backendType, System::String^ defines); }; class NativeMapObserver : public mbgl::MapObserver @@ -135,7 +153,7 @@ namespace DOTNET_NAMESPACE void onDidFinishLoadingMap() override; void onDidFailLoadingMap(mbgl::MapLoadError type, const std::string& description) override; void onWillStartRenderingFrame() override; - void onDidFinishRenderingFrame(mbgl::MapObserver::RenderFrameStatus status) override; + void onDidFinishRenderingFrame(const mbgl::MapObserver::RenderFrameStatus& status) override; void onWillStartRenderingMap() override; void onDidFinishRenderingMap(mbgl::MapObserver::RenderMode mode) override; void onDidFinishLoadingStyle() override; @@ -144,6 +162,9 @@ namespace DOTNET_NAMESPACE void onStyleImageMissing(const std::string& id) override; bool onCanRemoveUnusedStyleImage(const std::string& id) override; void onRegisterShaders(mbgl::gfx::ShaderRegistry& shaderRegistry) override; + void onPreCompileShader(mbgl::shaders::BuiltIn, mbgl::gfx::Backend::Type, const std::string&) override; + void onPostCompileShader(mbgl::shaders::BuiltIn, mbgl::gfx::Backend::Type, const std::string&) override; + void onShaderCompileFailed(mbgl::shaders::BuiltIn, mbgl::gfx::Backend::Type, const std::string&) override; msclr::gcroot CameraWillChangeHandler; msclr::gcroot CameraIsChangingHandler; @@ -156,10 +177,13 @@ namespace DOTNET_NAMESPACE msclr::gcroot WillStartRenderingMapHandler; msclr::gcroot DidFinishRenderingMapHandler; msclr::gcroot DidFinishLoadingStyleHandler; - //msclr::gcroot SourceChangedHandler; + msclr::gcroot SourceChangedHandler; msclr::gcroot DidBecomeIdleHandler; msclr::gcroot StyleImageMissingHandler; msclr::gcroot CanRemoveUnusedStyleImageHandler; msclr::gcroot RegisterShadersHandler; + msclr::gcroot PreCompileShaderHandler; + msclr::gcroot PostCompileShaderHandler; + msclr::gcroot ShaderCompileFailedHandler; }; } diff --git a/include/RenderingStats.h b/include/RenderingStats.h index 998c258..a7b778a 100644 --- a/include/RenderingStats.h +++ b/include/RenderingStats.h @@ -18,14 +18,47 @@ namespace DOTNET_NAMESPACE ~RenderingStats(); property System::Boolean Zero { System::Boolean get(); } + + // --- Frame timing --- + property System::Double EncodingTime { System::Double get(); } + property System::Double RenderingTime { System::Double get(); } + property System::Int32 NumFrames { System::Int32 get(); } + + // --- Draw calls --- property System::Int32 NumDrawCalls { System::Int32 get(); System::Void set(System::Int32 value); } + property System::Int32 TotalDrawCalls { System::Int32 get(); } + + // --- Textures --- property System::Int32 NumActiveTextures { System::Int32 get(); System::Void set(System::Int32 value); } property System::Int32 NumCreatedTextures { System::Int32 get(); System::Void set(System::Int32 value); } + property System::Int32 NumTextureBindings { System::Int32 get(); } + property System::Int32 NumTextureUpdates { System::Int32 get(); } + property System::Int64 TextureUpdateBytes { System::Int64 get(); } + + // --- Buffers --- property System::Int32 NumBuffers { System::Int32 get(); System::Void set(System::Int32 value); } property System::Int32 NumFrameBuffers { System::Int32 get(); System::Void set(System::Int32 value); } + property System::Int32 NumIndexBuffers { System::Int32 get(); } + property System::Int64 IndexUpdateBytes { System::Int64 get(); } + property System::Int32 NumVertexBuffers { System::Int32 get(); } + property System::Int64 VertexUpdateBytes { System::Int64 get(); } + property System::Int32 NumUniformBuffers { System::Int32 get(); } + property System::Int32 NumUniformUpdates { System::Int32 get(); } + property System::Int64 UniformUpdateBytes { System::Int64 get(); } + property System::Int64 TotalBuffers { System::Int64 get(); } + property System::Int64 BufferUpdates { System::Int64 get(); } + property System::Int64 BufferUpdateBytes { System::Int64 get(); } + + // --- Memory --- property System::Int32 MemTextures { System::Int32 get(); System::Void set(System::Int32 value); } + property System::Int32 MemBuffers { System::Int32 get(); } property System::Int32 MemIndexBuffers { System::Int32 get(); System::Void set(System::Int32 value); } property System::Int32 MemVertexBuffers { System::Int32 get(); System::Void set(System::Int32 value); } + property System::Int32 MemUniformBuffers { System::Int32 get(); } + + // --- Stencil --- + property System::Int32 StencilClears { System::Int32 get(); } + property System::Int32 StencilUpdates { System::Int32 get(); } internal: RenderingStats(NativePointerHolder^ nativePointerHolder); }; diff --git a/src/CameraOptions.cpp b/src/CameraOptions.cpp index 37bfbac..0607124 100644 --- a/src/CameraOptions.cpp +++ b/src/CameraOptions.cpp @@ -32,6 +32,12 @@ namespace DOTNET_NAMESPACE return this; } + CameraOptions^ CameraOptions::WithCenterAltitude(System::Nullable o) + { + NativePointer->withCenterAltitude(o.HasValue ? std::optional(o.Value) : std::nullopt); + return this; + } + CameraOptions^ CameraOptions::WithPadding(EdgeInsets^ p) { if (p) @@ -102,6 +108,54 @@ namespace DOTNET_NAMESPACE return this; } + CameraOptions^ CameraOptions::WithRoll(System::Nullable o) + { + NativePointer->withRoll(o.HasValue ? std::optional(o.Value) : std::nullopt); + return this; + } + + CameraOptions^ CameraOptions::WithFov(System::Nullable o) + { + NativePointer->withFov(o.HasValue ? std::optional(o.Value) : std::nullopt); + return this; + } + + System::Nullable CameraOptions::CenterAltitude::get() + { + if (NativePointer->centerAltitude.has_value()) + return System::Nullable(NativePointer->centerAltitude.value()); + return System::Nullable(); + } + + System::Void CameraOptions::CenterAltitude::set(System::Nullable value) + { + NativePointer->centerAltitude = value.HasValue ? std::optional(value.Value) : std::nullopt; + } + + System::Nullable CameraOptions::Roll::get() + { + if (NativePointer->roll.has_value()) + return System::Nullable(NativePointer->roll.value()); + return System::Nullable(); + } + + System::Void CameraOptions::Roll::set(System::Nullable value) + { + NativePointer->roll = value.HasValue ? std::optional(value.Value) : std::nullopt; + } + + System::Nullable CameraOptions::Fov::get() + { + if (NativePointer->fov.has_value()) + return System::Nullable(NativePointer->fov.value()); + return System::Nullable(); + } + + System::Void CameraOptions::Fov::set(System::Nullable value) + { + NativePointer->fov = value.HasValue ? std::optional(value.Value) : std::nullopt; + } + LatLng^ CameraOptions::Center::get() { if (!_Center && NativePointer->center.has_value()) diff --git a/src/FreeCameraOptions.cpp b/src/FreeCameraOptions.cpp index e0ee0d0..1332cca 100644 --- a/src/FreeCameraOptions.cpp +++ b/src/FreeCameraOptions.cpp @@ -27,9 +27,9 @@ namespace DOTNET_NAMESPACE NativePointer->lookAtPoint(*location->NativePointer, *upVector->NativePointer); } - System::Void FreeCameraOptions::SetPitchBearing(System::Double pitch, System::Double bearing) + System::Void FreeCameraOptions::SetRollPitchBearing(System::Double roll, System::Double pitch, System::Double bearing) { - NativePointer->setPitchBearing(pitch, bearing); + NativePointer->setRollPitchBearing(roll, pitch, bearing); } Vec3^ FreeCameraOptions::Position::get() diff --git a/src/Map.cpp b/src/Map.cpp index 66782c6..e743088 100644 --- a/src/Map.cpp +++ b/src/Map.cpp @@ -374,6 +374,16 @@ namespace DOTNET_NAMESPACE NativePointer->setSize(*size->NativePointer); } + System::Void Map::SetFrustumOffset(EdgeInsets^ offset) + { + NativePointer->setFrustumOffset(*offset->NativePointer); + } + + EdgeInsets^ Map::GetFrustumOffset() + { + return gcnew EdgeInsets(EdgeInsets::CreateNativePointerHolder(NativePointer->getFrustumOffset())); + } + ScreenCoordinate Map::PixelForLatLng(LatLng^ latLng) { return ScreenCoordinate(NativePointer->pixelForLatLng(*latLng->NativePointer)); @@ -525,6 +535,66 @@ namespace DOTNET_NAMESPACE return NativePointer->isFullyLoaded(); } + System::Boolean Map::IsRenderingStatsViewEnabled::get() + { + return NativePointer->isRenderingStatsViewEnabled(); + } + + System::Void Map::IsRenderingStatsViewEnabled::set(System::Boolean value) + { + NativePointer->enableRenderingStatsView(value); + } + + System::Double Map::TileLodMinRadius::get() + { + return NativePointer->getTileLodMinRadius(); + } + + System::Void Map::TileLodMinRadius::set(System::Double value) + { + NativePointer->setTileLodMinRadius(value); + } + + System::Double Map::TileLodScale::get() + { + return NativePointer->getTileLodScale(); + } + + System::Void Map::TileLodScale::set(System::Double value) + { + NativePointer->setTileLodScale(value); + } + + System::Double Map::TileLodPitchThreshold::get() + { + return NativePointer->getTileLodPitchThreshold(); + } + + System::Void Map::TileLodPitchThreshold::set(System::Double value) + { + NativePointer->setTileLodPitchThreshold(value); + } + + System::Double Map::TileLodZoomShift::get() + { + return NativePointer->getTileLodZoomShift(); + } + + System::Void Map::TileLodZoomShift::set(System::Double value) + { + NativePointer->setTileLodZoomShift(value); + } + + TileLodMode Map::TileLodMode_::get() + { + return (TileLodMode)NativePointer->getTileLodMode(); + } + + System::Void Map::TileLodMode_::set(TileLodMode value) + { + NativePointer->setTileLodMode((mbgl::TileLodMode)value); + } + FreeCameraOptions_^ Map::FreeCameraOptions::get() { return gcnew FreeCameraOptions_(FreeCameraOptions_::CreateNativePointerHolder(NativePointer->getFreeCameraOptions())); diff --git a/src/MapObserver.cpp b/src/MapObserver.cpp index 585caaa..d619b9c 100644 --- a/src/MapObserver.cpp +++ b/src/MapObserver.cpp @@ -2,6 +2,7 @@ #include "ShaderRegistry.h" #include #include +#include namespace DOTNET_NAMESPACE { @@ -19,11 +20,14 @@ namespace DOTNET_NAMESPACE NativePointer->WillStartRenderingMapHandler = gcnew WillStartRenderingMapHandler(this, &MapObserver::onWillStartRenderingMap); NativePointer->DidFinishRenderingMapHandler = gcnew DidFinishRenderingMapHandler(this, &MapObserver::onDidFinishRenderingMap); NativePointer->DidFinishLoadingStyleHandler = gcnew DidFinishLoadingStyleHandler(this, &MapObserver::onDidFinishLoadingStyle); - //NativePointer->SourceChangedHandler = gcnew SourceChangedHandler(this, &MapObserver::onSourceChanged); + NativePointer->SourceChangedHandler = gcnew SourceChangedHandler(this, &MapObserver::onSourceChanged); NativePointer->DidBecomeIdleHandler = gcnew DidBecomeIdleHandler(this, &MapObserver::onDidBecomeIdle); NativePointer->StyleImageMissingHandler = gcnew StyleImageMissingHandler(this, &MapObserver::onStyleImageMissing); NativePointer->CanRemoveUnusedStyleImageHandler = gcnew CanRemoveUnusedStyleImageHandler(this, &MapObserver::onCanRemoveUnusedStyleImage); NativePointer->RegisterShadersHandler = gcnew RegisterShadersHandler(this, &MapObserver::onRegisterShaders); + NativePointer->PreCompileShaderHandler = gcnew PreCompileShaderHandler(this, &MapObserver::onPreCompileShader); + NativePointer->PostCompileShaderHandler = gcnew PostCompileShaderHandler(this, &MapObserver::onPostCompileShader); + NativePointer->ShaderCompileFailedHandler = gcnew ShaderCompileFailedHandler(this, &MapObserver::onShaderCompileFailed); } MapObserver::~MapObserver() @@ -85,13 +89,10 @@ namespace DOTNET_NAMESPACE DidFinishLoadingStyle(); } - // TODO: implement the managed version - /* - System::Void MapObserver::onSourceChanged(Source^ source) + System::Void MapObserver::onSourceChanged(System::String^ id, SourceType type) { - SourceChanged(source); + SourceChanged(id, type); } - */ System::Void MapObserver::onDidBecomeIdle() { @@ -114,6 +115,21 @@ namespace DOTNET_NAMESPACE RegisterShaders(shaderRegistry); } + System::Void MapObserver::onPreCompileShader(System::UInt32 shaderType, System::Byte backendType, System::String^ defines) + { + PreCompileShader(shaderType, backendType, defines); + } + + System::Void MapObserver::onPostCompileShader(System::UInt32 shaderType, System::Byte backendType, System::String^ defines) + { + PostCompileShader(shaderType, backendType, defines); + } + + System::Void MapObserver::onShaderCompileFailed(System::UInt32 shaderType, System::Byte backendType, System::String^ defines) + { + ShaderCompileFailed(shaderType, backendType, defines); + } + NativeMapObserver::NativeMapObserver() { } @@ -178,7 +194,7 @@ namespace DOTNET_NAMESPACE } } - void NativeMapObserver::onDidFinishRenderingFrame(mbgl::MapObserver::RenderFrameStatus status) + void NativeMapObserver::onDidFinishRenderingFrame(const mbgl::MapObserver::RenderFrameStatus& status) { if (static_cast(DidFinishRenderingFrameHandler)) { @@ -219,12 +235,12 @@ namespace DOTNET_NAMESPACE void NativeMapObserver::onSourceChanged(mbgl::style::Source& source) { - /* if (static_cast(SourceChangedHandler)) { - SourceChangedHandler->Invoke(gcnew Source(Source::CreateNativePointerHolder(source))); + SourceChangedHandler->Invoke( + Convert::ToSystemString(source.getID()), + (DOTNET_NAMESPACE::SourceType)source.getType()); } - */ } void NativeMapObserver::onDidBecomeIdle() @@ -260,4 +276,28 @@ namespace DOTNET_NAMESPACE RegisterShadersHandler->Invoke(gcnew ShaderRegistry(ShaderRegistry::CreateNativePointerHolder(&shaderRegistry, false))); } } + + void NativeMapObserver::onPreCompileShader(mbgl::shaders::BuiltIn type, mbgl::gfx::Backend::Type backend, const std::string& defines) + { + if (static_cast(PreCompileShaderHandler)) + { + PreCompileShaderHandler->Invoke(static_cast(type), static_cast(backend), Convert::ToSystemString(defines)); + } + } + + void NativeMapObserver::onPostCompileShader(mbgl::shaders::BuiltIn type, mbgl::gfx::Backend::Type backend, const std::string& defines) + { + if (static_cast(PostCompileShaderHandler)) + { + PostCompileShaderHandler->Invoke(static_cast(type), static_cast(backend), Convert::ToSystemString(defines)); + } + } + + void NativeMapObserver::onShaderCompileFailed(mbgl::shaders::BuiltIn type, mbgl::gfx::Backend::Type backend, const std::string& defines) + { + if (static_cast(ShaderCompileFailedHandler)) + { + ShaderCompileFailedHandler->Invoke(static_cast(type), static_cast(backend), Convert::ToSystemString(defines)); + } + } } diff --git a/src/RenderingStats.cpp b/src/RenderingStats.cpp index 1af436b..1eefedc 100644 --- a/src/RenderingStats.cpp +++ b/src/RenderingStats.cpp @@ -20,83 +20,46 @@ namespace DOTNET_NAMESPACE return NativePointer->isZero(); } - System::Int32 RenderingStats::NumDrawCalls::get() - { - return NativePointer->numDrawCalls; - } - - System::Void RenderingStats::NumDrawCalls::set(System::Int32 value) - { - NativePointer->numDrawCalls = value; - } - - System::Int32 RenderingStats::NumActiveTextures::get() - { - return NativePointer->numActiveTextures; - } - - System::Void RenderingStats::NumActiveTextures::set(System::Int32 value) - { - NativePointer->numActiveTextures = value; - } - - System::Int32 RenderingStats::NumCreatedTextures::get() - { - return NativePointer->numCreatedTextures; - } - - System::Void RenderingStats::NumCreatedTextures::set(System::Int32 value) - { - NativePointer->numCreatedTextures; - } - - System::Int32 RenderingStats::NumBuffers::get() - { - return NativePointer->numBuffers; - } - - System::Void RenderingStats::NumBuffers::set(System::Int32 value) - { - NativePointer->numBuffers; - } - - System::Int32 RenderingStats::NumFrameBuffers::get() - { - return NativePointer->numFrameBuffers; - } - - System::Void RenderingStats::NumFrameBuffers::set(System::Int32 value) - { - NativePointer->numFrameBuffers; - } - - System::Int32 RenderingStats::MemTextures::get() - { - return NativePointer->memTextures; - } - - System::Void RenderingStats::MemTextures::set(System::Int32 value) - { - NativePointer->memTextures; - } - - System::Int32 RenderingStats::MemIndexBuffers::get() - { - return NativePointer->memIndexBuffers; - } - - System::Void RenderingStats::MemIndexBuffers::set(System::Int32 value) - { - NativePointer->memIndexBuffers; - } - - System::Int32 RenderingStats::MemVertexBuffers::get() - { - return NativePointer->memVertexBuffers; - } - - System::Void RenderingStats::MemVertexBuffers::set(System::Int32 value) - { - NativePointer->memVertexBuffers = value; - } + System::Double RenderingStats::EncodingTime::get() { return NativePointer->encodingTime; } + System::Double RenderingStats::RenderingTime::get() { return NativePointer->renderingTime; } + System::Int32 RenderingStats::NumFrames::get() { return NativePointer->numFrames; } + + System::Int32 RenderingStats::NumDrawCalls::get() { return NativePointer->numDrawCalls; } + System::Void RenderingStats::NumDrawCalls::set(System::Int32 value) { NativePointer->numDrawCalls = value; } + System::Int32 RenderingStats::TotalDrawCalls::get() { return NativePointer->totalDrawCalls; } + + System::Int32 RenderingStats::NumActiveTextures::get() { return NativePointer->numActiveTextures; } + System::Void RenderingStats::NumActiveTextures::set(System::Int32 value) { NativePointer->numActiveTextures = value; } + System::Int32 RenderingStats::NumCreatedTextures::get() { return NativePointer->numCreatedTextures; } + System::Void RenderingStats::NumCreatedTextures::set(System::Int32 value) { NativePointer->numCreatedTextures = value; } + System::Int32 RenderingStats::NumTextureBindings::get() { return NativePointer->numTextureBindings; } + System::Int32 RenderingStats::NumTextureUpdates::get() { return NativePointer->numTextureUpdates; } + System::Int64 RenderingStats::TextureUpdateBytes::get() { return static_cast(NativePointer->textureUpdateBytes); } + + System::Int32 RenderingStats::NumBuffers::get() { return NativePointer->numBuffers; } + System::Void RenderingStats::NumBuffers::set(System::Int32 value) { NativePointer->numBuffers = value; } + System::Int32 RenderingStats::NumFrameBuffers::get() { return NativePointer->numFrameBuffers; } + System::Void RenderingStats::NumFrameBuffers::set(System::Int32 value) { NativePointer->numFrameBuffers = value; } + System::Int32 RenderingStats::NumIndexBuffers::get() { return NativePointer->numIndexBuffers; } + System::Int64 RenderingStats::IndexUpdateBytes::get() { return static_cast(NativePointer->indexUpdateBytes); } + System::Int32 RenderingStats::NumVertexBuffers::get() { return NativePointer->numVertexBuffers; } + System::Int64 RenderingStats::VertexUpdateBytes::get() { return static_cast(NativePointer->vertexUpdateBytes); } + System::Int32 RenderingStats::NumUniformBuffers::get() { return NativePointer->numUniformBuffers; } + System::Int32 RenderingStats::NumUniformUpdates::get() { return NativePointer->numUniformUpdates; } + System::Int64 RenderingStats::UniformUpdateBytes::get() { return static_cast(NativePointer->uniformUpdateBytes); } + System::Int64 RenderingStats::TotalBuffers::get() { return static_cast(NativePointer->totalBuffers); } + System::Int64 RenderingStats::BufferUpdates::get() { return static_cast(NativePointer->bufferUpdates); } + System::Int64 RenderingStats::BufferUpdateBytes::get() { return static_cast(NativePointer->bufferUpdateBytes); } + + System::Int32 RenderingStats::MemTextures::get() { return NativePointer->memTextures; } + System::Void RenderingStats::MemTextures::set(System::Int32 value) { NativePointer->memTextures = value; } + System::Int32 RenderingStats::MemBuffers::get() { return NativePointer->memBuffers; } + System::Int32 RenderingStats::MemIndexBuffers::get() { return NativePointer->memIndexBuffers; } + System::Void RenderingStats::MemIndexBuffers::set(System::Int32 value) { NativePointer->memIndexBuffers = value; } + System::Int32 RenderingStats::MemVertexBuffers::get() { return NativePointer->memVertexBuffers; } + System::Void RenderingStats::MemVertexBuffers::set(System::Int32 value) { NativePointer->memVertexBuffers = value; } + System::Int32 RenderingStats::MemUniformBuffers::get() { return NativePointer->memUniformBuffers; } + + System::Int32 RenderingStats::StencilClears::get() { return NativePointer->stencilClears; } + System::Int32 RenderingStats::StencilUpdates::get() { return NativePointer->stencilUpdates; } } From 488ed7f98f038b97d3a10b44575602e3359b549b Mon Sep 17 00:00:00 2001 From: acalcutt Date: Tue, 28 Apr 2026 11:12:35 -0400 Subject: [PATCH 02/63] add workflow --- .github/workflows/bump-version.yml | 221 +++++++++++++++++++++++++++++ .github/workflows/release.yml | 181 +++++++++++++++++++++++ CMakeLists.txt | 1 + 3 files changed, 403 insertions(+) create mode 100644 .github/workflows/bump-version.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/bump-version.yml b/.github/workflows/bump-version.yml new file mode 100644 index 0000000..bd8780d --- /dev/null +++ b/.github/workflows/bump-version.yml @@ -0,0 +1,221 @@ +name: Create bump version PR + +on: + workflow_dispatch: + inputs: + version: + description: Version bump type. + required: true + type: choice + options: + - patch + - minor + - major + - prepatch + - preminor + - premajor + - prerelease + preid: + description: Prerelease identifier (e.g., 'alpha', 'beta', 'rc'). Only used with pre* version types. + required: false + type: string + default: 'rc' + +permissions: + contents: write + pull-requests: write + +jobs: + bump-version-pr: + name: Bump version PR + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Create bump script + run: | + cat > bump-version.py << 'PYEOF' + import re, sys, os, subprocess, json + + version_type = sys.argv[1] + preid = sys.argv[2] if len(sys.argv) > 2 and sys.argv[2].strip() else 'rc' + cmake_path = 'CMakeLists.txt' + changelog_path = 'CHANGELOG.md' + + # ── Read current version from CMakeLists.txt ───────────────────────────── + cmake = open(cmake_path).read() + m = re.search(r'set\(MAPLIBRE_NET_VERSION "([^"]+)"\)', cmake) + if not m: + raise RuntimeError(f"Could not find MAPLIBRE_NET_VERSION in {cmake_path}") + current = m.group(1).strip() + print(f"Current version: {current}") + + # ── Parse version (supports X.Y.Z and X.Y.Z-pre.N) ────────────────────── + pre_match = re.match(r'^(\d+)\.(\d+)\.(\d+)-(.+?)\.(\d+)$', current) + rel_match = re.match(r'^(\d+)\.(\d+)\.(\d+)$', current) + + if pre_match: + major, minor, patch = int(pre_match.group(1)), int(pre_match.group(2)), int(pre_match.group(3)) + pre_tag, pre_num = pre_match.group(4), int(pre_match.group(5)) + is_pre = True + elif rel_match: + major, minor, patch = int(rel_match.group(1)), int(rel_match.group(2)), int(rel_match.group(3)) + pre_tag, pre_num = None, 0 + is_pre = False + else: + raise RuntimeError(f"Unrecognised version format: {current}") + + # ── Compute new version ────────────────────────────────────────────────── + if version_type == 'patch': + new_version = f"{major}.{minor}.{patch}" if is_pre else f"{major}.{minor}.{patch + 1}" + elif version_type == 'minor': + new_version = f"{major}.{minor}.0" if is_pre else f"{major}.{minor + 1}.0" + elif version_type == 'major': + new_version = f"{major}.0.0" if is_pre else f"{major + 1}.0.0" + elif version_type == 'prepatch': + new_version = f"{major}.{minor}.{patch + 1}-{preid}.1" + elif version_type == 'preminor': + new_version = f"{major}.{minor + 1}.0-{preid}.1" + elif version_type == 'premajor': + new_version = f"{major + 1}.0.0-{preid}.1" + elif version_type == 'prerelease': + if is_pre: + new_version = f"{major}.{minor}.{patch}-{pre_tag}.{pre_num + 1}" + else: + new_version = f"{major}.{minor}.{patch + 1}-{preid}.1" + else: + raise RuntimeError(f"Unknown version type: {version_type}") + + print(f"New version: {new_version}") + + # ── Update CMakeLists.txt ──────────────────────────────────────────────── + cmake = re.sub( + r'(set\(MAPLIBRE_NET_VERSION ")[^"]+(")', + rf'\g<1>{new_version}\g<2>', + cmake) + open(cmake_path, 'w').write(cmake) + print(f"Updated {cmake_path}") + + # ── Update CHANGELOG.md ────────────────────────────────────────────────── + if not os.path.exists(changelog_path): + print("No CHANGELOG.md found, creating one") + open(changelog_path, 'w').write('# Changelog\n\n') + + changelog = open(changelog_path).read() + + # Collect PRs since last tag that aren't already in the changelog + missing_entries = [] + try: + latest_tag = subprocess.check_output( + ['git', 'describe', '--tags', '--abbrev=0'], text=True).strip() + print(f"Latest tag: {latest_tag}") + commit_range = f"{latest_tag}..HEAD" + except subprocess.CalledProcessError: + print("No previous tags found, using all commits") + commit_range = 'HEAD' + + try: + commits = subprocess.check_output( + ['git', 'log', commit_range, '--oneline'], text=True) + except subprocess.CalledProcessError: + commits = '' + + pr_numbers = re.findall(r'#(\d+)', commits) + missing_prs = [n for n in pr_numbers if f'#{n}' not in changelog] + print(f"Found {len(missing_prs)} new PRs to add to changelog") + + if missing_prs: + try: + remote_url = subprocess.check_output( + ['git', 'config', '--get', 'remote.origin.url'], text=True).strip() + repo_match = re.search(r'github\.com[:/](.+?)(?:\.git)?$', remote_url) + repo_full = repo_match.group(1) if repo_match else None + except Exception: + repo_full = None + + for pr_num in missing_prs: + try: + pr_json = subprocess.check_output( + ['gh', 'pr', 'view', pr_num, '--json', 'title,author,number'], text=True) + pr = json.loads(pr_json) + if 'dependabot' in pr['author']['login']: + continue + pr_url = f"https://github.com/{repo_full}/pull/{pr['number']}" if repo_full else f"#{pr['number']}" + entry = f"- {pr['title']} ([#{pr['number']}]({pr_url})) (@{pr['author']['login']})" + missing_entries.append(entry) + print(f"Added: {entry}") + except Exception as e: + print(f"Could not fetch PR #{pr_num}: {e}") + + # Replace "## master" or "## main" with new version heading + changelog = re.sub(r'^## (?:master|main)\s*$', f'## {new_version}', changelog, flags=re.MULTILINE) + changelog = changelog.replace('- _...Add new stuff here..._\n', '') + + # Prepend fresh master section + master_section = '\n'.join([ + '## master', + '### ✨ Features and improvements', + '- _...Add new stuff here..._', + '', + '### 🐞 Bug fixes', + '- _...Add new stuff here..._', + '', + '', + ]) + + title_match = re.match(r'^(# .+?\n\n)', changelog) + if title_match: + title = title_match.group(1) + rest = changelog[len(title):] + else: + title = '# Changelog\n\n' + rest = changelog + + # Insert missing PR entries into the Bug fixes block of the new version section + if missing_entries: + bug_fix_pattern = re.compile( + r'^(## [^\n]+\n### ✨ Features and improvements\n(?:.*\n)*?### 🐞 Bug fixes\n)', + re.MULTILINE) + bf_match = bug_fix_pattern.search(rest) + if bf_match: + insert_at = bf_match.end() + entries_text = '\n' + '\n'.join(missing_entries) + '\n' + rest = rest[:insert_at] + entries_text + rest[insert_at:] + else: + rest = rest + '\n' + '\n'.join(missing_entries) + '\n' + + changelog = title + master_section + rest + open(changelog_path, 'w').write(changelog) + print("Updated CHANGELOG.md") + + with open(os.environ['GITHUB_OUTPUT'], 'a') as f: + f.write(f"version={new_version}\n") + PYEOF + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Bump version and update changelog + id: version-details + run: | + python bump-version.py "${{ inputs.version }}" "${{ inputs.preid }}" + rm bump-version.py + env: + GH_TOKEN: ${{ github.token }} + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v7 + with: + commit-message: Release v${{ steps.version-details.outputs.version }} + branch: release-v${{ steps.version-details.outputs.version }} + title: Release v${{ steps.version-details.outputs.version }} + body: | + Automated version bump to `v${{ steps.version-details.outputs.version }}`. + + Review the `CHANGELOG.md` entries before merging. Once merged, trigger the **Build and Release** workflow to publish the GitHub release. + labels: release diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..fa259e4 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,181 @@ +name: Build and Release + +on: + workflow_dispatch: + push: + branches: + - main + - master + +permissions: + contents: write + +jobs: + release-check: + name: Check if version is already released + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Get version and check GitHub releases + id: check + run: | + VERSION=$(grep -oP 'set\(MAPLIBRE_NET_VERSION "\K[^"]+' CMakeLists.txt | head -1) + + if echo "$VERSION" | grep -qP '\-(alpha|beta|rc)\.\d+'; then + RELEASE_TYPE="prerelease" + else + RELEASE_TYPE="regular" + fi + + HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ + "https://api.github.com/repos/${{ github.repository }}/releases/tags/v${VERSION}" \ + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}") + + if [ "$HTTP_STATUS" = "200" ]; then + PUBLISHED="true" + else + PUBLISHED="false" + fi + + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "published=$PUBLISHED" >> "$GITHUB_OUTPUT" + echo "release_type=$RELEASE_TYPE" >> "$GITHUB_OUTPUT" + echo "VERSION: $VERSION" + echo "RELEASE_TYPE: $RELEASE_TYPE" + echo "PUBLISHED: $PUBLISHED" + + outputs: + published: ${{ steps.check.outputs.published }} + version: ${{ steps.check.outputs.version }} + release_type: ${{ steps.check.outputs.release_type }} + + build: + name: Build Windows release + runs-on: windows-latest + needs: release-check + if: ${{ needs.release-check.outputs.published == 'false' }} + environment: release + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Initialize submodules (shallow) + run: git submodule update --init --recursive --depth 1 + + - name: Set up MSVC developer tools (x64) + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x64 + + - name: Cache vcpkg packages + id: vcpkg-cache + uses: actions/cache@v4 + with: + path: dependencies/maplibre-native/platform/windows/vendor/vcpkg/installed + key: vcpkg-x64-windows-${{ hashFiles('dependencies/maplibre-native/platform/windows/vendor/vcpkg/baseline.json', 'dependencies/maplibre-native/platform/windows/Get-VendorPackages.ps1') }} + + - name: Bootstrap vcpkg + run: | + & "dependencies\maplibre-native\platform\windows\vendor\vcpkg\bootstrap-vcpkg.bat" -disableMetrics + + - name: Install vcpkg packages + if: steps.vcpkg-cache.outputs.cache-hit != 'true' + run: | + $vcpkg = "dependencies\maplibre-native\platform\windows\vendor\vcpkg\vcpkg.exe" + $triplets = "dependencies\maplibre-native\platform\windows\vendor\vcpkg-custom-triplets" + & $vcpkg install curl dlfcn-win32 glfw3 libuv libjpeg-turbo libpng libwebp opengl-registry ` + --triplet x64-windows ` + --overlay-triplets="$triplets" ` + --disable-metrics ` + --clean-after-build + + - name: Configure CMake + run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release + + - name: Build + run: cmake --build build --target mbgl-dotnet --parallel + + - name: Package release artifacts + shell: pwsh + run: | + $version = "${{ needs.release-check.outputs.version }}" + $outDir = "build\Release" + + New-Item -ItemType Directory -Path publish -Force + + # Copy the managed assembly and its XML docs + Copy-Item "$outDir\MaplibreNative.NET.dll" publish\ + if (Test-Path "$outDir\MaplibreNative.NET.xml") { + Copy-Item "$outDir\MaplibreNative.NET.xml" publish\ + } + + # Copy any additional DLLs (vcpkg runtime dependencies) + Get-ChildItem "$outDir\*.dll" | + Where-Object { $_.Name -ne "MaplibreNative.NET.dll" } | + Copy-Item -Destination publish\ + + Compress-Archive -Path "publish\*" -DestinationPath "MaplibreNative.NET-v${version}-win-x64.zip" + Write-Host "Packaged: MaplibreNative.NET-v${version}-win-x64.zip" + Get-ChildItem publish\ + + - name: Upload build artifact + uses: actions/upload-artifact@v4 + with: + name: release-artifacts + path: MaplibreNative.NET-v${{ needs.release-check.outputs.version }}-win-x64.zip + retention-days: 7 + + release: + name: Publish GitHub Release + runs-on: ubuntu-latest + needs: [release-check, build] + if: ${{ needs.release-check.outputs.published == 'false' }} + environment: release + + steps: + - uses: actions/checkout@v4 + + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: release-artifacts + path: ./artifacts + + - name: Extract changelog for version + run: | + VERSION="${{ needs.release-check.outputs.version }}" + if [ -f CHANGELOG.md ]; then + awk -v ver="## $VERSION" \ + 'BEGIN{p=0} $0==ver{p=1; next} /^## / && p==1{exit} p==1{print}' \ + CHANGELOG.md > changelog_for_version.md + else + echo "No changelog entry for v${VERSION}." > changelog_for_version.md + fi + echo "--- Release notes ---" + cat changelog_for_version.md + + - name: Determine release flags + id: flags + run: | + if [[ "${{ needs.release-check.outputs.release_type }}" == 'regular' ]]; then + echo "prerelease=false" >> "$GITHUB_OUTPUT" + else + echo "prerelease=true" >> "$GITHUB_OUTPUT" + fi + + - name: Publish GitHub Release + uses: ncipollo/release-action@v1 + with: + tag: v${{ needs.release-check.outputs.version }} + name: v${{ needs.release-check.outputs.version }} + bodyFile: changelog_for_version.md + artifacts: ./artifacts/MaplibreNative.NET-v${{ needs.release-check.outputs.version }}-win-x64.zip + prerelease: ${{ steps.flags.outputs.prerelease }} + allowUpdates: true + draft: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/CMakeLists.txt b/CMakeLists.txt index e435290..a055c38 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.10 FATAL_ERROR) set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/dependencies/maplibre-native/platform/windows/custom-toolchain.cmake) +set(MAPLIBRE_NET_VERSION "1.0.0") project("MaplibreNative.NET" LANGUAGES CXX C) set(CMAKE_CXX_STANDARD 17) From fa12b89df9a9ef4bbe5be52bdab9f65487dcf0f9 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Tue, 28 Apr 2026 11:20:50 -0400 Subject: [PATCH 03/63] enable long paths --- .github/workflows/release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fa259e4..ec1c0c9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -59,6 +59,9 @@ jobs: environment: release steps: + - name: Enable Git long paths + run: git config --global core.longpaths true + - uses: actions/checkout@v4 with: fetch-depth: 0 From 0e44426186ea00875f770656bf34bf1536199490 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Tue, 28 Apr 2026 11:28:43 -0400 Subject: [PATCH 04/63] Update release.yml --- .github/workflows/release.yml | 64 ++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ec1c0c9..338216a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -52,11 +52,23 @@ jobs: release_type: ${{ steps.check.outputs.release_type }} build: - name: Build Windows release - runs-on: windows-latest + name: Build Windows ${{ matrix.arch }} + runs-on: ${{ matrix.runs-on }} needs: release-check if: ${{ needs.release-check.outputs.published == 'false' }} environment: release + strategy: + fail-fast: false + matrix: + include: + - runs-on: windows-latest + arch: x64 + vcpkg-triplet: x64-windows + msvc-arch: x64 + - runs-on: windows-11-arm + arch: arm64 + vcpkg-triplet: arm64-windows + msvc-arch: amd64_arm64 steps: - name: Enable Git long paths @@ -65,38 +77,34 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 + submodules: recursive - - name: Initialize submodules (shallow) - run: git submodule update --init --recursive --depth 1 - - - name: Set up MSVC developer tools (x64) + - name: Set up MSVC developer tools uses: ilammy/msvc-dev-cmd@v1 with: - arch: x64 + arch: ${{ matrix.msvc-arch }} + + - name: Get vcpkg commit ID + id: vcpkg-id + shell: pwsh + run: | + $id = ($(git submodule status .\dependencies\maplibre-native\platform\windows\vendor\vcpkg).Trim() -split ' ')[0] + Add-Content -Path $env:GITHUB_OUTPUT -Value "commit_id=$id" - - name: Cache vcpkg packages - id: vcpkg-cache + - name: Cache vcpkg binary cache uses: actions/cache@v4 with: - path: dependencies/maplibre-native/platform/windows/vendor/vcpkg/installed - key: vcpkg-x64-windows-${{ hashFiles('dependencies/maplibre-native/platform/windows/vendor/vcpkg/baseline.json', 'dependencies/maplibre-native/platform/windows/Get-VendorPackages.ps1') }} + path: dependencies/maplibre-native/platform/windows/vendor/vcpkg/archives + key: vcpkg-${{ matrix.vcpkg-triplet }}-${{ steps.vcpkg-id.outputs.commit_id }} - name: Bootstrap vcpkg run: | & "dependencies\maplibre-native\platform\windows\vendor\vcpkg\bootstrap-vcpkg.bat" -disableMetrics - - name: Install vcpkg packages - if: steps.vcpkg-cache.outputs.cache-hit != 'true' - run: | - $vcpkg = "dependencies\maplibre-native\platform\windows\vendor\vcpkg\vcpkg.exe" - $triplets = "dependencies\maplibre-native\platform\windows\vendor\vcpkg-custom-triplets" - & $vcpkg install curl dlfcn-win32 glfw3 libuv libjpeg-turbo libpng libwebp opengl-registry ` - --triplet x64-windows ` - --overlay-triplets="$triplets" ` - --disable-metrics ` - --clean-after-build - - name: Configure CMake + shell: pwsh + env: + VCPKG_BINARY_SOURCES: "clear;files,${{ github.workspace }}\\dependencies\\maplibre-native\\platform\\windows\\vendor\\vcpkg\\archives,readwrite" run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release - name: Build @@ -106,6 +114,7 @@ jobs: shell: pwsh run: | $version = "${{ needs.release-check.outputs.version }}" + $arch = "${{ matrix.arch }}" $outDir = "build\Release" New-Item -ItemType Directory -Path publish -Force @@ -121,15 +130,15 @@ jobs: Where-Object { $_.Name -ne "MaplibreNative.NET.dll" } | Copy-Item -Destination publish\ - Compress-Archive -Path "publish\*" -DestinationPath "MaplibreNative.NET-v${version}-win-x64.zip" - Write-Host "Packaged: MaplibreNative.NET-v${version}-win-x64.zip" + Compress-Archive -Path "publish\*" -DestinationPath "MaplibreNative.NET-v${version}-win-${arch}.zip" + Write-Host "Packaged: MaplibreNative.NET-v${version}-win-${arch}.zip" Get-ChildItem publish\ - name: Upload build artifact uses: actions/upload-artifact@v4 with: - name: release-artifacts - path: MaplibreNative.NET-v${{ needs.release-check.outputs.version }}-win-x64.zip + name: release-artifacts-${{ matrix.arch }} + path: MaplibreNative.NET-v${{ needs.release-check.outputs.version }}-win-${{ matrix.arch }}.zip retention-days: 7 release: @@ -145,7 +154,8 @@ jobs: - name: Download build artifacts uses: actions/download-artifact@v4 with: - name: release-artifacts + pattern: release-artifacts-* + merge-multiple: true path: ./artifacts - name: Extract changelog for version From b9a3c6763793ad317e6f4964cce993214e947eed Mon Sep 17 00:00:00 2001 From: acalcutt Date: Tue, 28 Apr 2026 11:38:02 -0400 Subject: [PATCH 05/63] Update release.yml --- .github/workflows/release.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 338216a..a016ee9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -88,7 +88,7 @@ jobs: id: vcpkg-id shell: pwsh run: | - $id = ($(git submodule status .\dependencies\maplibre-native\platform\windows\vendor\vcpkg).Trim() -split ' ')[0] + $id = git -C dependencies/maplibre-native/platform/windows/vendor/vcpkg rev-parse HEAD Add-Content -Path $env:GITHUB_OUTPUT -Value "commit_id=$id" - name: Cache vcpkg binary cache @@ -105,7 +105,12 @@ jobs: shell: pwsh env: VCPKG_BINARY_SOURCES: "clear;files,${{ github.workspace }}\\dependencies\\maplibre-native\\platform\\windows\\vendor\\vcpkg\\archives,readwrite" - run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release + run: > + cmake -B build -G Ninja + -DCMAKE_BUILD_TYPE=Release + -DMLN_WITH_OPENGL=ON + -DMLN_WITH_EGL=OFF + -DMLN_WITH_VULKAN=OFF - name: Build run: cmake --build build --target mbgl-dotnet --parallel From cc3e2ca0e3c6b03d5d02850131163b5e067ee9bd Mon Sep 17 00:00:00 2001 From: acalcutt Date: Tue, 28 Apr 2026 12:37:27 -0400 Subject: [PATCH 06/63] Update release.yml --- .github/workflows/release.yml | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a016ee9..9a534d8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -105,12 +105,23 @@ jobs: shell: pwsh env: VCPKG_BINARY_SOURCES: "clear;files,${{ github.workspace }}\\dependencies\\maplibre-native\\platform\\windows\\vendor\\vcpkg\\archives,readwrite" - run: > - cmake -B build -G Ninja - -DCMAKE_BUILD_TYPE=Release - -DMLN_WITH_OPENGL=ON - -DMLN_WITH_EGL=OFF - -DMLN_WITH_VULKAN=OFF + run: | + $maxAttempts = 3 + for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) { + Write-Host "CMake configure attempt $attempt of $maxAttempts" + cmake -B build -G Ninja ` + -DCMAKE_BUILD_TYPE=Release ` + -DMLN_WITH_OPENGL=ON ` + -DMLN_WITH_EGL=OFF ` + -DMLN_WITH_VULKAN=OFF + if ($LASTEXITCODE -eq 0) { break } + if ($attempt -lt $maxAttempts) { + Write-Host "Attempt $attempt failed (exit $LASTEXITCODE), cleaning and retrying in 30s..." + Remove-Item -Recurse -Force build -ErrorAction SilentlyContinue + } else { + exit $LASTEXITCODE + } + } - name: Build run: cmake --build build --target mbgl-dotnet --parallel From 018cffa209435d7bff5148c909aa26cbf8cb78c8 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Tue, 28 Apr 2026 14:11:53 -0400 Subject: [PATCH 07/63] Update release.yml --- .github/workflows/release.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9a534d8..6e9c225 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -113,7 +113,8 @@ jobs: -DCMAKE_BUILD_TYPE=Release ` -DMLN_WITH_OPENGL=ON ` -DMLN_WITH_EGL=OFF ` - -DMLN_WITH_VULKAN=OFF + -DMLN_WITH_VULKAN=OFF ` + "-DCMAKE_CXX_FLAGS=/wd4267" if ($LASTEXITCODE -eq 0) { break } if ($attempt -lt $maxAttempts) { Write-Host "Attempt $attempt failed (exit $LASTEXITCODE), cleaning and retrying in 30s..." From 747552930c40b25cf78190b818f31f74ad1574c5 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Tue, 28 Apr 2026 22:04:08 -0400 Subject: [PATCH 08/63] Update CMakeLists.txt --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a055c38..9d7a8cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/dependencies/maplibre-native/ set(MAPLIBRE_NET_VERSION "1.0.0") project("MaplibreNative.NET" LANGUAGES CXX C) -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) if(NOT MSVC) return() From 81400c6a2afef218cc29599d67cd8a50c6b52074 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Wed, 29 Apr 2026 09:03:06 -0400 Subject: [PATCH 09/63] Update Convert.h --- include/Convert.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/Convert.h b/include/Convert.h index 0ed2be2..db988a0 100644 --- a/include/Convert.h +++ b/include/Convert.h @@ -6,6 +6,7 @@ #include #include #include +#using namespace { From 04132657b16447caf84c4dd675264245c1e82d53 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Wed, 29 Apr 2026 09:45:31 -0400 Subject: [PATCH 10/63] Update Convert.h --- include/Convert.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/include/Convert.h b/include/Convert.h index db988a0..0c8b3d4 100644 --- a/include/Convert.h +++ b/include/Convert.h @@ -6,7 +6,6 @@ #include #include #include -#using namespace { @@ -58,11 +57,11 @@ namespace DOTNET_NAMESPACE } template - static std::chrono::time_point ToTimePoint(System::DateTime^ dateTime) + static std::chrono::time_point ToTimePoint(System::DateTime dateTime) { return std::chrono::time_point_cast( std::chrono::time_point( - std::chrono::nanoseconds((dateTime->Ticks - epochTicks) * 100) + std::chrono::nanoseconds((dateTime.Ticks - epochTicks) * 100) ) ); } From 06c5e63010cd1ebfb9d7d2d9dc35eaa4f4c79a17 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Wed, 29 Apr 2026 10:26:57 -0400 Subject: [PATCH 11/63] Update Image.h --- include/Image.h | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/include/Image.h b/include/Image.h index d97095d..5cfa798 100644 --- a/include/Image.h +++ b/include/Image.h @@ -57,19 +57,19 @@ namespace DOTNET_NAMESPACE System::Void Fill(System::Byte value) { - NativePointer->fill(value); + this->NativePointer->fill(value); } System::Void Resize(Size_^ size) { - NativePointer->resize(*size->NativePointer); + this->NativePointer->resize(*size->NativePointer); } cli::array^ GetData() { - cli::array^ result = gcnew cli::array(static_cast(NativePointer->bytes())); + cli::array^ result = gcnew cli::array(static_cast(this->NativePointer->bytes())); - System::Runtime::InteropServices::Marshal::Copy(System::IntPtr(NativePointer->data.get()), result, 0, result->Length); + System::Runtime::InteropServices::Marshal::Copy(System::IntPtr(this->NativePointer->data.get()), result, 0, result->Length); return result; } @@ -78,7 +78,7 @@ namespace DOTNET_NAMESPACE { System::IntPtr get() { - return System::IntPtr(NativePointer->data.get()); + return System::IntPtr(this->NativePointer->data.get()); } } @@ -86,7 +86,7 @@ namespace DOTNET_NAMESPACE { System::Boolean get() { - return NativePointer->valid(); + return this->NativePointer->valid(); } } @@ -94,7 +94,7 @@ namespace DOTNET_NAMESPACE { System::UInt64 get() { - return NativePointer->stride(); + return this->NativePointer->stride(); } } @@ -102,7 +102,7 @@ namespace DOTNET_NAMESPACE { System::UInt64 get() { - return NativePointer->bytes(); + return this->NativePointer->bytes(); } } @@ -110,7 +110,7 @@ namespace DOTNET_NAMESPACE { Size_^ get() { - return gcnew Size_(Size_::CreateNativePointerHolder(NativePointer->size)); + return gcnew Size_(Size_::CreateNativePointerHolder(this->NativePointer->size)); } } @@ -118,18 +118,18 @@ namespace DOTNET_NAMESPACE { System::Int64 get() { - return NativePointer->channels; + return this->NativePointer->channels; } } System::Boolean operator==(ImageBase^ rhs) { - return *NativePointer == *rhs->NativePointer; + return *this->NativePointer == *rhs->NativePointer; } System::Boolean operator!=(ImageBase^ rhs) { - return *NativePointer != *rhs->NativePointer; + return *this->NativePointer != *rhs->NativePointer; } /// From 1795c35b9d85ca935f5134290d2fe86163e41ef7 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Wed, 29 Apr 2026 12:46:17 -0400 Subject: [PATCH 12/63] Update Image.h --- include/Image.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/Image.h b/include/Image.h index 5cfa798..5117f2d 100644 --- a/include/Image.h +++ b/include/Image.h @@ -36,15 +36,15 @@ namespace DOTNET_NAMESPACE public ref class ImageBase : NativeWrapper> { public: - ImageBase() : NativeWrapper(CreateImage()) + ImageBase() : NativeWrapper>(CreateImage()) { } - ImageBase(Size_^ size) : NativeWrapper(CreateImage(size)) + ImageBase(Size_^ size) : NativeWrapper>(CreateImage(size)) { } - ImageBase(Size_^ size, cli::array^ srcData) : NativeWrapper(CreateImage(size, srcData)) + ImageBase(Size_^ size, cli::array^ srcData) : NativeWrapper>(CreateImage(size, srcData)) { } @@ -162,7 +162,7 @@ namespace DOTNET_NAMESPACE template ::Type> static System::Void Copy(T^ srcImg, T^ dstImg, PointUInt srcPt, PointUInt dstPt, Size_^ size); internal: - ImageBase(NativePointerHolder>^ nativePointerHolder) : NativeWrapper(nativePointerHolder) + ImageBase(NativePointerHolder>^ nativePointerHolder) : NativeWrapper>(nativePointerHolder) { } private: From 2c04060c1465c944d192998240c8f464b64f8c22 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Wed, 29 Apr 2026 14:24:04 -0400 Subject: [PATCH 13/63] build fixes --- include/ExternalRenderingContextFrontend.h | 2 ++ include/Point.h | 2 +- src/FileSource.cpp | 25 +++++++--------------- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/include/ExternalRenderingContextFrontend.h b/include/ExternalRenderingContextFrontend.h index 1fc328d..698c09b 100644 --- a/include/ExternalRenderingContextFrontend.h +++ b/include/ExternalRenderingContextFrontend.h @@ -1,6 +1,7 @@ #pragma once #include "NativeWrapper.h" #include "RendererFrontend.h" +#include #include #include #include @@ -66,6 +67,7 @@ namespace DOTNET_NAMESPACE void setObserver(mbgl::RendererObserver& observer) override; void update(std::shared_ptr parameters) override; void update(std::shared_ptr parameters, bool callUpdatedHandler); + const mbgl::TaggedScheduler& getThreadPool() const override { return _Backend->getThreadPool(); } void render(); NativeExternalRenderingContextBackend* getBackend(); mbgl::Renderer* getRenderer(); diff --git a/include/Point.h b/include/Point.h index 6f530ec..14c540f 100644 --- a/include/Point.h +++ b/include/Point.h @@ -11,7 +11,7 @@ namespace DOTNET_NAMESPACE Y = y; } - PointDouble(mbgl::Point& point) + PointDouble(const mbgl::Point& point) { X = point.x; Y = point.y; diff --git a/src/FileSource.cpp b/src/FileSource.cpp index d8ddc3b..ae791d8 100644 --- a/src/FileSource.cpp +++ b/src/FileSource.cpp @@ -9,21 +9,6 @@ #include #include -namespace -{ - using namespace ::DOTNET_NAMESPACE; - - void RequestCallback(mbgl::Response response, FileSource::Callback^ callback) - { - callback->Invoke(gcnew Response(Response::CreateNativePointerHolder(response))); - } - - void ForwardCallback(System::Action^ callback) - { - callback->Invoke(); - } -} - namespace DOTNET_NAMESPACE { FileSource::FileSource(NativePointerHolder>^ nativePointerHolder) : NativeWrapper(nativePointerHolder) @@ -41,11 +26,14 @@ namespace DOTNET_NAMESPACE AsyncRequest^ FileSource::Request(Resource^ resource, Callback^ callback) { + auto callbackRef = msclr::gcroot(callback); return gcnew AsyncRequest( AsyncRequest::CreateNativePointerHolder( NativePointer->get()->request( *resource->NativePointer, - std::bind(&RequestCallback, std::placeholders::_1, msclr::gcroot(callback)) + [callbackRef](mbgl::Response response) mutable { + callbackRef->Invoke(gcnew Response(Response::CreateNativePointerHolder(response))); + } ) .release() ) @@ -54,10 +42,13 @@ namespace DOTNET_NAMESPACE System::Void FileSource::Forward(Resource^ resource, Response^ response, System::Action^ callback) { + auto callbackRef = msclr::gcroot(callback); NativePointer->get()->forward( *resource->NativePointer, *response->NativePointer, - std::bind(&ForwardCallback, msclr::gcroot(callback)) + [callbackRef]() mutable { + callbackRef->Invoke(); + } ); } From 67c27ce30a262c1e695579d785bd93ae0fcfd994 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Wed, 29 Apr 2026 15:25:39 -0400 Subject: [PATCH 14/63] Update FileSource.cpp --- src/FileSource.cpp | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/src/FileSource.cpp b/src/FileSource.cpp index ae791d8..76d2d3d 100644 --- a/src/FileSource.cpp +++ b/src/FileSource.cpp @@ -9,6 +9,29 @@ #include #include +// Free helper functions — lambdas are only forbidden inside managed class +// member functions (C3923), not inside regular free functions. +namespace +{ + using namespace ::DOTNET_NAMESPACE; + + mbgl::FileSource::Callback MakeRequestCallback(FileSource::Callback^ callback) + { + auto ref = msclr::gcroot(callback); + return [ref](mbgl::Response response) mutable { + ref->Invoke(gcnew Response(Response::CreateNativePointerHolder(response))); + }; + } + + std::function MakeForwardCallback(System::Action^ callback) + { + auto ref = msclr::gcroot(callback); + return [ref]() mutable { + ref->Invoke(); + }; + } +} + namespace DOTNET_NAMESPACE { FileSource::FileSource(NativePointerHolder>^ nativePointerHolder) : NativeWrapper(nativePointerHolder) @@ -26,14 +49,11 @@ namespace DOTNET_NAMESPACE AsyncRequest^ FileSource::Request(Resource^ resource, Callback^ callback) { - auto callbackRef = msclr::gcroot(callback); return gcnew AsyncRequest( AsyncRequest::CreateNativePointerHolder( NativePointer->get()->request( *resource->NativePointer, - [callbackRef](mbgl::Response response) mutable { - callbackRef->Invoke(gcnew Response(Response::CreateNativePointerHolder(response))); - } + MakeRequestCallback(callback) ) .release() ) @@ -42,13 +62,10 @@ namespace DOTNET_NAMESPACE System::Void FileSource::Forward(Resource^ resource, Response^ response, System::Action^ callback) { - auto callbackRef = msclr::gcroot(callback); NativePointer->get()->forward( *resource->NativePointer, *response->NativePointer, - [callbackRef]() mutable { - callbackRef->Invoke(); - } + MakeForwardCallback(callback) ); } From 48662cd85c72502836d61800046224dfc1ed58a8 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Wed, 29 Apr 2026 16:03:27 -0400 Subject: [PATCH 15/63] fixes --- src/Image.cpp | 2 +- src/Map.cpp | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Image.cpp b/src/Image.cpp index fca8f96..5087a52 100644 --- a/src/Image.cpp +++ b/src/Image.cpp @@ -9,7 +9,7 @@ namespace DOTNET_NAMESPACE template T^ ImageBase::Clone() { - return gcnew T(T::CreateNativePointerHolder(NativePointer->clone())); + return gcnew T(T::CreateNativePointerHolder(this->NativePointer->clone())); } template diff --git a/src/Map.cpp b/src/Map.cpp index e743088..3e84970 100644 --- a/src/Map.cpp +++ b/src/Map.cpp @@ -40,6 +40,14 @@ namespace callback->Invoke(exception); } + + mbgl::Map::StillImageCallback MakeStillImageCallback(Map::StillImageCallback^ callback) + { + auto ref = msclr::gcroot(callback); + return [ref](std::exception_ptr eptr) mutable { + StillImageCallbackHandler(eptr, ref); + }; + } } namespace DOTNET_NAMESPACE @@ -92,11 +100,7 @@ namespace DOTNET_NAMESPACE System::Void Map::RenderStill(StillImageCallback^ callback) { - NativePointer->renderStill(std::bind( - &StillImageCallbackHandler, - std::placeholders::_1, - msclr::gcroot(callback) - )); + NativePointer->renderStill(MakeStillImageCallback(callback)); } System::Void Map::RenderStill(CameraOptions^ camera, MapDebugOptions debugOptions, StillImageCallback^ callback) @@ -104,11 +108,7 @@ namespace DOTNET_NAMESPACE NativePointer->renderStill( *camera->NativePointer, (mbgl::MapDebugOptions)debugOptions, - std::bind( - &StillImageCallbackHandler, - std::placeholders::_1, - msclr::gcroot(callback) - ) + MakeStillImageCallback(callback) ); } From 81938a539fecd54d1a5740339f2cecda586879d7 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Wed, 29 Apr 2026 17:00:17 -0400 Subject: [PATCH 16/63] Update Resource.cpp --- src/Resource.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Resource.cpp b/src/Resource.cpp index 37322d1..488c2a4 100644 --- a/src/Resource.cpp +++ b/src/Resource.cpp @@ -271,7 +271,7 @@ namespace DOTNET_NAMESPACE { if (value) { - NativePointer->priorData = std::shared_ptr(&Convert::ToStdString(value)); + NativePointer->priorData = std::make_shared(Convert::ToStdString(value)); } else { From 66f90123f47fc13f13d3fcbefb36b627ed6a3b9d Mon Sep 17 00:00:00 2001 From: acalcutt Date: Wed, 29 Apr 2026 19:34:00 -0400 Subject: [PATCH 17/63] fixes --- src/Response.cpp | 2 +- src/RunLoop.cpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Response.cpp b/src/Response.cpp index 1b5da7c..3c1abf8 100644 --- a/src/Response.cpp +++ b/src/Response.cpp @@ -98,7 +98,7 @@ namespace DOTNET_NAMESPACE { if (value) { - NativePointer->data = std::shared_ptr(&Convert::ToStdString(value)); + NativePointer->data = std::make_shared(Convert::ToStdString(value)); } else { diff --git a/src/RunLoop.cpp b/src/RunLoop.cpp index 799a221..64c0cdf 100644 --- a/src/RunLoop.cpp +++ b/src/RunLoop.cpp @@ -31,9 +31,12 @@ namespace DOTNET_NAMESPACE } #pragma managed(push, off) +#pragma warning(push) +#pragma warning(disable: 5308) #undef _M_CEE #include #define _M_CEE +#pragma warning(pop) namespace DOTNET_NAMESPACE { From f21e0bca9c24f81d7628b36f6ccc7b8baf71d9cb Mon Sep 17 00:00:00 2001 From: acalcutt Date: Wed, 29 Apr 2026 20:11:06 -0400 Subject: [PATCH 18/63] fixes --- include/Point.h | 2 +- src/Style.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/include/Point.h b/include/Point.h index 14c540f..b163403 100644 --- a/include/Point.h +++ b/include/Point.h @@ -130,7 +130,7 @@ namespace DOTNET_NAMESPACE Y = y; } - PointShort(mbgl::Point& point) + PointShort(const mbgl::Point& point) { X = point.x; Y = point.y; diff --git a/src/Style.cpp b/src/Style.cpp index 7ad8446..59792d6 100644 --- a/src/Style.cpp +++ b/src/Style.cpp @@ -1,11 +1,12 @@ #include "Convert.h" #include "FileSource.h" #include "Style.h" +#include #include namespace DOTNET_NAMESPACE { - Style::Style(FileSource^ fileSource, System::Single pixelRatio) : NativeWrapper(new mbgl::style::Style(*fileSource->NativePointer, pixelRatio)) + Style::Style(FileSource^ fileSource, System::Single pixelRatio) : NativeWrapper(new mbgl::style::Style(*fileSource->NativePointer, pixelRatio, mbgl::TaggedScheduler{mbgl::Scheduler::GetBackground(), {}})) { } From c7a77c1bc119ee250aff4618dd6f1b97191327e4 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Wed, 29 Apr 2026 20:52:23 -0400 Subject: [PATCH 19/63] Update Vector.cpp --- src/Vector.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Vector.cpp b/src/Vector.cpp index 26a200e..133f9ec 100644 --- a/src/Vector.cpp +++ b/src/Vector.cpp @@ -92,12 +92,14 @@ namespace DOTNET_NAMESPACE System::Collections::Generic::IEnumerator^ Vec3::GetEnumerator() { - return gcnew VectorEnumerator(&NativePointer->begin(), &NativePointer->end()); + auto begin = NativePointer->begin(); auto end = NativePointer->end(); + return gcnew VectorEnumerator(&begin, &end); } System::Collections::IEnumerator^ Vec3::GetEnumeratorObject() { - return gcnew VectorEnumerator(&NativePointer->begin(), &NativePointer->end()); + auto begin = NativePointer->begin(); auto end = NativePointer->end(); + return gcnew VectorEnumerator(&begin, &end); } System::Double Vec3::default::get(System::Int32 index) @@ -139,12 +141,14 @@ namespace DOTNET_NAMESPACE System::Collections::Generic::IEnumerator^ Vec4::GetEnumerator() { - return gcnew VectorEnumerator(&NativePointer->begin(), &NativePointer->end()); + auto begin = NativePointer->begin(); auto end = NativePointer->end(); + return gcnew VectorEnumerator(&begin, &end); } System::Collections::IEnumerator^ Vec4::GetEnumeratorObject() { - return gcnew VectorEnumerator(&NativePointer->begin(), &NativePointer->end()); + auto begin = NativePointer->begin(); auto end = NativePointer->end(); + return gcnew VectorEnumerator(&begin, &end); } System::Double Vec4::default::get(System::Int32 index) @@ -201,12 +205,14 @@ namespace DOTNET_NAMESPACE System::Collections::Generic::IEnumerator^ Mat4::GetEnumerator() { - return gcnew VectorEnumerator(&NativePointer->begin(), &NativePointer->end()); + auto begin = NativePointer->begin(); auto end = NativePointer->end(); + return gcnew VectorEnumerator(&begin, &end); } System::Collections::IEnumerator^ Mat4::GetEnumeratorObject() { - return gcnew VectorEnumerator(&NativePointer->begin(), &NativePointer->end()); + auto begin = NativePointer->begin(); auto end = NativePointer->end(); + return gcnew VectorEnumerator(&begin, &end); } System::Double Mat4::default::get(System::Int32 index) From 6c3ddd8723f19186db2cbd9e0af3e60015c94f19 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Wed, 29 Apr 2026 22:46:44 -0400 Subject: [PATCH 20/63] Update release.yml --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6e9c225..464f89b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -68,7 +68,7 @@ jobs: - runs-on: windows-11-arm arch: arm64 vcpkg-triplet: arm64-windows - msvc-arch: amd64_arm64 + msvc-arch: arm64 steps: - name: Enable Git long paths From 54db433fe863e14547838f78490e7965c20e57b6 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Wed, 29 Apr 2026 22:56:05 -0400 Subject: [PATCH 21/63] output a .NET 8-loadable assembly --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d7a8cf..e6cc154 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,8 +18,8 @@ set(ASSEMBLY_NAME "MaplibreNative.NET") set_target_properties( mbgl-dotnet PROPERTIES - COMMON_LANGUAGE_RUNTIME "" - VS_GLOBAL_CLRSupport "true" + COMMON_LANGUAGE_RUNTIME "NetCore" + VS_GLOBAL_CLRSupport "NetCore" OUTPUT_NAME ${ASSEMBLY_NAME} ) From 472654d16d2a4fe70ab2db03f767eefe19e96378 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Wed, 29 Apr 2026 23:21:22 -0400 Subject: [PATCH 22/63] fix: ENABLE_EXPORTS ON + /clr:netcore for Ninja to fix LNK1146 empty /implib: --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e6cc154..838706a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ set_target_properties( PROPERTIES COMMON_LANGUAGE_RUNTIME "NetCore" VS_GLOBAL_CLRSupport "NetCore" + ENABLE_EXPORTS ON OUTPUT_NAME ${ASSEMBLY_NAME} ) @@ -38,7 +39,7 @@ if(CMAKE_GENERATOR STREQUAL "Ninja") string(REPLACE "/RTC1" "" CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG}) string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) - target_compile_options(mbgl-dotnet PRIVATE "/clr" "/EHa") + target_compile_options(mbgl-dotnet PRIVATE "/clr:netcore" "/EHa") add_custom_command( TARGET mbgl-dotnet From 51dbbe103e9cffeee02fa5f617b0e7042b4ff2d4 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Wed, 29 Apr 2026 23:48:23 -0400 Subject: [PATCH 23/63] fix: add /AI .NET Core reference assemblies path for Ninja + /clr:netcore to fix C5275/C1190 --- CMakeLists.txt | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 838706a..3a3e23c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,39 @@ if(CMAKE_GENERATOR STREQUAL "Ninja") string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) target_compile_options(mbgl-dotnet PRIVATE "/clr:netcore" "/EHa") + # With Ninja, MSVC doesn't auto-configure the .NET Core assembly search path + # (unlike the VS generator). Explicitly add /AI pointing to the .NET reference + # assemblies so the compiler finds the correct System.Runtime.dll instead of + # the .NET Framework 4.x one from C:\Windows\Microsoft.NET\Framework64\. + find_program(_DOTNET_EXE dotnet) + if(_DOTNET_EXE) + execute_process( + COMMAND "${_DOTNET_EXE}" --version + OUTPUT_VARIABLE _DOTNET_VER + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + ) + string(REGEX MATCH "^[0-9]+" _DOTNET_MAJ "${_DOTNET_VER}") + file(GLOB _NETREF_CANDIDATES + "C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/${_DOTNET_MAJ}.*/ref/net${_DOTNET_MAJ}.0" + ) + if(_NETREF_CANDIDATES) + list(SORT _NETREF_CANDIDATES ORDER DESCENDING) + list(GET _NETREF_CANDIDATES 0 _NETREF_DIR) + file(TO_NATIVE_PATH "${_NETREF_DIR}" _NETREF_DIR_NATIVE) + target_compile_options(mbgl-dotnet PRIVATE "/AI${_NETREF_DIR_NATIVE}") + message(STATUS "Using .NET reference assemblies: ${_NETREF_DIR_NATIVE}") + else() + message(WARNING "Could not find .NET Core reference assemblies for /clr:netcore — compile will likely fail with C5275") + endif() + unset(_NETREF_CANDIDATES) + unset(_NETREF_DIR) + unset(_NETREF_DIR_NATIVE) + unset(_DOTNET_VER) + unset(_DOTNET_MAJ) + endif() + unset(_DOTNET_EXE) + add_custom_command( TARGET mbgl-dotnet POST_BUILD From 1cc91fee76eb23abb1538ecdce0ff84e4fa19d5b Mon Sep 17 00:00:00 2001 From: acalcutt Date: Wed, 29 Apr 2026 23:57:29 -0400 Subject: [PATCH 24/63] use Visual Studio 17 2022 instead of ninja --- .github/workflows/release.yml | 7 ++-- CMakeLists.txt | 65 ++++------------------------------- 2 files changed, 10 insertions(+), 62 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 464f89b..4b6d862 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -65,10 +65,12 @@ jobs: arch: x64 vcpkg-triplet: x64-windows msvc-arch: x64 + vs-arch: x64 - runs-on: windows-11-arm arch: arm64 vcpkg-triplet: arm64-windows msvc-arch: arm64 + vs-arch: ARM64 steps: - name: Enable Git long paths @@ -109,8 +111,7 @@ jobs: $maxAttempts = 3 for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) { Write-Host "CMake configure attempt $attempt of $maxAttempts" - cmake -B build -G Ninja ` - -DCMAKE_BUILD_TYPE=Release ` + cmake -B build -G "Visual Studio 17 2022" -A ${{ matrix.vs-arch }} ` -DMLN_WITH_OPENGL=ON ` -DMLN_WITH_EGL=OFF ` -DMLN_WITH_VULKAN=OFF ` @@ -125,7 +126,7 @@ jobs: } - name: Build - run: cmake --build build --target mbgl-dotnet --parallel + run: cmake --build build --target mbgl-dotnet --config Release --parallel - name: Package release artifacts shell: pwsh diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a3e23c..f630718 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,70 +20,17 @@ set_target_properties( PROPERTIES COMMON_LANGUAGE_RUNTIME "NetCore" VS_GLOBAL_CLRSupport "NetCore" - ENABLE_EXPORTS ON OUTPUT_NAME ${ASSEMBLY_NAME} ) target_compile_options(mbgl-dotnet PRIVATE "/doc" "/GT") -if(CMAKE_GENERATOR STREQUAL "Ninja") - set(_TARGET_DIRECTORY "${CMAKE_BINARY_DIR}/${CMAKE_BUILD_TYPE}") - - set_target_properties( - mbgl-dotnet - PROPERTIES - ARCHIVE_OUTPUT_DIRECTORY ${_TARGET_DIRECTORY} - LIBRARY_OUTPUT_DIRECTORY ${_TARGET_DIRECTORY} - RUNTIME_OUTPUT_DIRECTORY ${_TARGET_DIRECTORY} - ) - - string(REPLACE "/RTC1" "" CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG}) - string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) - target_compile_options(mbgl-dotnet PRIVATE "/clr:netcore" "/EHa") - - # With Ninja, MSVC doesn't auto-configure the .NET Core assembly search path - # (unlike the VS generator). Explicitly add /AI pointing to the .NET reference - # assemblies so the compiler finds the correct System.Runtime.dll instead of - # the .NET Framework 4.x one from C:\Windows\Microsoft.NET\Framework64\. - find_program(_DOTNET_EXE dotnet) - if(_DOTNET_EXE) - execute_process( - COMMAND "${_DOTNET_EXE}" --version - OUTPUT_VARIABLE _DOTNET_VER - OUTPUT_STRIP_TRAILING_WHITESPACE - ERROR_QUIET - ) - string(REGEX MATCH "^[0-9]+" _DOTNET_MAJ "${_DOTNET_VER}") - file(GLOB _NETREF_CANDIDATES - "C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/${_DOTNET_MAJ}.*/ref/net${_DOTNET_MAJ}.0" - ) - if(_NETREF_CANDIDATES) - list(SORT _NETREF_CANDIDATES ORDER DESCENDING) - list(GET _NETREF_CANDIDATES 0 _NETREF_DIR) - file(TO_NATIVE_PATH "${_NETREF_DIR}" _NETREF_DIR_NATIVE) - target_compile_options(mbgl-dotnet PRIVATE "/AI${_NETREF_DIR_NATIVE}") - message(STATUS "Using .NET reference assemblies: ${_NETREF_DIR_NATIVE}") - else() - message(WARNING "Could not find .NET Core reference assemblies for /clr:netcore — compile will likely fail with C5275") - endif() - unset(_NETREF_CANDIDATES) - unset(_NETREF_DIR) - unset(_NETREF_DIR_NATIVE) - unset(_DOTNET_VER) - unset(_DOTNET_MAJ) - endif() - unset(_DOTNET_EXE) - - add_custom_command( - TARGET mbgl-dotnet - POST_BUILD - COMMAND xdcmake "..\\*.xdc" /nologo /assembly:"${ASSEMBLY_NAME}" /out:"${ASSEMBLY_NAME}.xml" - WORKING_DIRECTORY ${_TARGET_DIRECTORY} - VERBATIM - ) - - unset(_TARGET_DIRECTORY) -endif() +add_custom_command( + TARGET mbgl-dotnet + POST_BUILD + COMMAND xdcmake "$\\..\\*.xdc" /nologo /assembly:"${ASSEMBLY_NAME}" /out:"$\\${ASSEMBLY_NAME}.xml" + VERBATIM +) target_compile_definitions( mbgl-dotnet From 23e1d82715b3c91a9684f162834723158e7889b6 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Thu, 30 Apr 2026 00:08:22 -0400 Subject: [PATCH 25/63] fix: add VS_GLOBAL_TargetFramework net8.0 to fix NETSDK1013 with VS generator --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index f630718..1855da0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ set_target_properties( PROPERTIES COMMON_LANGUAGE_RUNTIME "NetCore" VS_GLOBAL_CLRSupport "NetCore" + VS_GLOBAL_TargetFramework "net8.0" OUTPUT_NAME ${ASSEMBLY_NAME} ) From 6ac94b7ea953c923cf68c698bafdc25588f3ea47 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Thu, 30 Apr 2026 17:34:57 -0400 Subject: [PATCH 26/63] Add MapLibreLogger to forward mbgl::Log to C# --- CMakeLists.txt | 1 + include/MapLibreLogger.h | 41 ++++++++++++++++++++++++++++++++ src/MapLibreLogger.cpp | 50 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 include/MapLibreLogger.h create mode 100644 src/MapLibreLogger.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1855da0..f031c02 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,6 +71,7 @@ target_sources( ${PROJECT_SOURCE_DIR}/src/LatLng.cpp ${PROJECT_SOURCE_DIR}/src/Map.cpp ${PROJECT_SOURCE_DIR}/src/MapObserver.cpp + ${PROJECT_SOURCE_DIR}/src/MapLibreLogger.cpp ${PROJECT_SOURCE_DIR}/src/MapOptions.cpp ${PROJECT_SOURCE_DIR}/src/OverscaledTileID.cpp ${PROJECT_SOURCE_DIR}/src/PremultipliedImage.cpp diff --git a/include/MapLibreLogger.h b/include/MapLibreLogger.h new file mode 100644 index 0000000..4b747a4 --- /dev/null +++ b/include/MapLibreLogger.h @@ -0,0 +1,41 @@ +#pragma once + +namespace DOTNET_NAMESPACE +{ + public enum class LogEventSeverity : System::Byte + { + Debug = 0, + Info, + Warning, + Error, + }; + + public enum class LogEvent : System::Byte + { + General = 0, + Setup, + Shader, + ParseStyle, + ParseTile, + Render, + Style, + Database, + HttpRequest, + Sprite, + Image, + OpenGL, + JNI, + Android, + Crash, + Glyph, + Timing, + }; + + public delegate void MapLibreLogHandler(LogEventSeverity severity, LogEvent eventType, System::Int64 code, System::String^ message); + + public ref class MapLibreLogger + { + public: + static void SetObserver(MapLibreLogHandler^ handler); + }; +} diff --git a/src/MapLibreLogger.cpp b/src/MapLibreLogger.cpp new file mode 100644 index 0000000..75d3209 --- /dev/null +++ b/src/MapLibreLogger.cpp @@ -0,0 +1,50 @@ +#include "MapLibreLogger.h" +#include "Convert.h" +#include +#include + +namespace +{ + class ManagedLogObserver : public mbgl::Log::Observer + { + private: + msclr::gcroot _handler; + + public: + ManagedLogObserver(msclr::gcroot handler) + : _handler(handler) {} + + bool onRecord(mbgl::EventSeverity severity, mbgl::Event event, int64_t code, const std::string& msg) override + { + try + { + if ((DOTNET_NAMESPACE::MapLibreLogHandler^)_handler != nullptr) + { + ((DOTNET_NAMESPACE::MapLibreLogHandler^)_handler)( + (DOTNET_NAMESPACE::LogEventSeverity)severity, + (DOTNET_NAMESPACE::LogEvent)event, + code, + DOTNET_NAMESPACE::Convert::ToSystemString(msg) + ); + } + } + catch (...) {} + return true; + } + }; +} + +namespace DOTNET_NAMESPACE +{ + void MapLibreLogger::SetObserver(MapLibreLogHandler^ handler) + { + if (handler == nullptr) + { + mbgl::Log::setObserver(nullptr); + return; + } + + auto msclrHandler = msclr::gcroot(handler); + mbgl::Log::setObserver(std::make_unique(msclrHandler)); + } +} From 78ad165cd32cd5a3179026ce021d5e82b5703ff3 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Thu, 30 Apr 2026 17:35:48 -0400 Subject: [PATCH 27/63] Update CMakeLists.txt --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f031c02..3c85af0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,7 +71,7 @@ target_sources( ${PROJECT_SOURCE_DIR}/src/LatLng.cpp ${PROJECT_SOURCE_DIR}/src/Map.cpp ${PROJECT_SOURCE_DIR}/src/MapObserver.cpp - ${PROJECT_SOURCE_DIR}/src/MapLibreLogger.cpp + ${PROJECT_SOURCE_DIR}/src/MapLibreLogger.cpp ${PROJECT_SOURCE_DIR}/src/MapOptions.cpp ${PROJECT_SOURCE_DIR}/src/OverscaledTileID.cpp ${PROJECT_SOURCE_DIR}/src/PremultipliedImage.cpp From 053dd20eacf51b66334a666063876621d6cb8777 Mon Sep 17 00:00:00 2001 From: acalcutt <3792408+acalcutt@users.noreply.github.com> Date: Thu, 30 Apr 2026 21:37:07 +0000 Subject: [PATCH 28/63] Release v1.0.1 --- CHANGELOG.md | 9 +++++++++ CMakeLists.txt | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9ec5992 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog + +## master +### ✨ Features and improvements +- _...Add new stuff here..._ + +### 🐞 Bug fixes +- _...Add new stuff here..._ + diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c85af0..f0369a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.10 FATAL_ERROR) set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/dependencies/maplibre-native/platform/windows/custom-toolchain.cmake) -set(MAPLIBRE_NET_VERSION "1.0.0") +set(MAPLIBRE_NET_VERSION "1.0.1") project("MaplibreNative.NET" LANGUAGES CXX C) set(CMAKE_CXX_STANDARD 20) From dec4a36401248d28aeae31e1d46bb0f339aab6ca Mon Sep 17 00:00:00 2001 From: acalcutt Date: Thu, 30 Apr 2026 20:36:57 -0400 Subject: [PATCH 29/63] Add GeoJSON source/layer API to Style + MaplibreNative.NET.WPF project - Style.h/cpp: AddGeoJsonSource, SetGeoJsonSourceUrl, SetGeoJsonSourceData, HasSource, RemoveSource, AddCircleLayer, HasLayer, RemoveLayer - Add MaplibreNative.NET.WPF C# class library (MaplibreMapHost, DelegateMapObserver) with reflection-based GeoJSON layer helpers (SetWifiGeoJsonLayer, SetWifiGeoJsonLayerData, RemoveWifiGeoJsonLayer) and double-click zoom --- MaplibreNative.NET.WPF/DelegateMapObserver.cs | 29 + MaplibreNative.NET.WPF/MaplibreMapHost.cs | 810 ++++++++++++++++++ .../MaplibreNative.NET.WPF.csproj | 26 + include/Style.h | 58 +- src/Style.cpp | 88 ++ 5 files changed, 978 insertions(+), 33 deletions(-) create mode 100644 MaplibreNative.NET.WPF/DelegateMapObserver.cs create mode 100644 MaplibreNative.NET.WPF/MaplibreMapHost.cs create mode 100644 MaplibreNative.NET.WPF/MaplibreNative.NET.WPF.csproj diff --git a/MaplibreNative.NET.WPF/DelegateMapObserver.cs b/MaplibreNative.NET.WPF/DelegateMapObserver.cs new file mode 100644 index 0000000..6a4560c --- /dev/null +++ b/MaplibreNative.NET.WPF/DelegateMapObserver.cs @@ -0,0 +1,29 @@ +using System; +using MaplibreNative; + +namespace MaplibreNative.WPF; + +/// +/// Simple MapObserver that forwards events to a delegate, useful for logging. +/// +public class DelegateMapObserver : MapObserver +{ + private readonly Action _log; + + public DelegateMapObserver(Action log) { _log = log; } + + protected override void onDidFailLoadingMap(MapLoadError type, string description) + => _log("Fail", $"[{type}] {description}"); + + protected override void onDidFinishLoadingMap() + => _log("Finish", "Map loaded"); + + protected override void onWillStartLoadingMap() + => _log("Will", "WillStartLoading"); + + protected override void onDidFinishLoadingStyle() + => _log("Style", "DidFinishLoadingStyle"); + + protected override void onDidFinishRenderingFrame(RenderFrameStatus status) + => _log("Frame", $"mode={status.mode} needsRepaint={status.needsRepaint}"); +} diff --git a/MaplibreNative.NET.WPF/MaplibreMapHost.cs b/MaplibreNative.NET.WPF/MaplibreMapHost.cs new file mode 100644 index 0000000..d021867 --- /dev/null +++ b/MaplibreNative.NET.WPF/MaplibreMapHost.cs @@ -0,0 +1,810 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Input; +using System.Windows.Interop; +using System.Windows.Media; +using System.Windows.Media.Effects; +using System.Windows.Threading; +using MaplibreNative; + +namespace MaplibreNative.WPF; + +/// +/// WPF HwndHost that embeds a MapLibre Native OpenGL map. +/// Handles its own OpenGL context, RunLoop, pan/zoom input, and an +/// optional navigation control overlay (zoom-in, zoom-out, reset-north). +/// +public class MaplibreMapHost : HwndHost +{ + private static readonly string LogPath = + System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "map_log.txt"); + private static void Log(string s) + { + try { System.IO.File.AppendAllText(LogPath, $"[{DateTime.Now:HH:mm:ss.fff}] {s}\n"); } catch { } + } + + // ── Public properties ───────────────────────────────────────────────────── + + public string StyleUrl + { + get => (string)GetValue(StyleUrlProperty); + set => SetValue(StyleUrlProperty, value); + } + public static readonly DependencyProperty StyleUrlProperty = + DependencyProperty.Register(nameof(StyleUrl), typeof(string), typeof(MaplibreMapHost), + new PropertyMetadata("https://demotiles.maplibre.org/style.json", OnStyleUrlChanged)); + + private static void OnStyleUrlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is MaplibreMapHost h && h._map != null && e.NewValue is string url) + h._map.Style.LoadURL(url); + } + + /// + /// Show or hide the built-in navigation control (zoom in/out + north reset). + /// Default is true. + /// + public bool ShowNavigationControls + { + get => (bool)GetValue(ShowNavigationControlsProperty); + set => SetValue(ShowNavigationControlsProperty, value); + } + public static readonly DependencyProperty ShowNavigationControlsProperty = + DependencyProperty.Register(nameof(ShowNavigationControls), typeof(bool), typeof(MaplibreMapHost), + new PropertyMetadata(true, OnShowNavigationControlsChanged)); + + private static void OnShowNavigationControlsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var h = (MaplibreMapHost)d; + if (h._navPopup != null) + h._navPopup.IsOpen = (bool)e.NewValue && h.IsVisible; + } + + // ── Public camera helpers ───────────────────────────────────────────────── + + public void CenterOn(double latitude, double longitude, double zoom = 14.0) + { + if (_map == null) return; + var cam = new CameraOptions(); + cam.Center = new LatLng(latitude, longitude); + cam.Zoom = zoom; + _map.JumpTo(cam); + _renderNeedsUpdate = true; + } + + public void ZoomIn() + { + if (_map == null) return; + _map.ScaleBy(2.0, new System.Nullable()); + _renderNeedsUpdate = true; + } + + public void ZoomOut() + { + if (_map == null) return; + _map.ScaleBy(0.5, new System.Nullable()); + _renderNeedsUpdate = true; + } + + public void ResetNorth() + { + if (_map == null) return; + var cam = new CameraOptions(); + cam.Bearing = 0.0; + _map.JumpTo(cam); + _renderNeedsUpdate = true; + } + + // ── GeoJSON layer management ────────────────────────────────────────────── + // These methods call APIs added to Style in the C++/CLI wrapper. They use + // reflection so the WPF library keeps compiling against older DLL builds + // that may not have those methods yet (pre-release DLL). + + private static System.Reflection.MethodInfo? _miAddGeoJsonSource; + private static System.Reflection.MethodInfo? _miSetGeoJsonSourceUrl; + private static System.Reflection.MethodInfo? _miSetGeoJsonSourceData; + private static System.Reflection.MethodInfo? _miHasSource; + private static System.Reflection.MethodInfo? _miRemoveSource; + private static System.Reflection.MethodInfo? _miAddCircleLayer; + private static System.Reflection.MethodInfo? _miHasLayer; + private static System.Reflection.MethodInfo? _miRemoveLayer; + private static bool _styleApiResolved; + + private static void EnsureStyleApiResolved(object styleObj) + { + if (_styleApiResolved) return; + _styleApiResolved = true; + var t = styleObj.GetType(); + _miAddGeoJsonSource = t.GetMethod("AddGeoJsonSource", [typeof(string), typeof(string)]); + _miSetGeoJsonSourceUrl = t.GetMethod("SetGeoJsonSourceUrl", [typeof(string), typeof(string)]); + _miSetGeoJsonSourceData= t.GetMethod("SetGeoJsonSourceData",[typeof(string), typeof(string)]); + _miHasSource = t.GetMethod("HasSource", [typeof(string)]); + _miRemoveSource = t.GetMethod("RemoveSource", [typeof(string)]); + _miAddCircleLayer = t.GetMethod("AddCircleLayer", [typeof(string), typeof(string), typeof(string), typeof(float), typeof(float), typeof(string)]); + _miHasLayer = t.GetMethod("HasLayer", [typeof(string)]); + _miRemoveLayer = t.GetMethod("RemoveLayer", [typeof(string)]); + } + + private static bool StyleApiAvailable => _miAddGeoJsonSource != null; + + /// + /// Add or update a GeoJSON source that fetches from a URL, with circle + /// layers for open (green), WEP (orange) and secure (red) access points. + /// Requires a DLL build that includes the GeoJSON source/layer APIs. + /// + public void SetWifiGeoJsonLayer(string sourceId, string geoJsonUrl) + { + if (_map == null) return; + var style = _map.Style; + EnsureStyleApiResolved(style); + if (!StyleApiAvailable) { Log("SetWifiGeoJsonLayer: Style API not available in this DLL build"); return; } + + bool hasSource = (bool)_miHasSource!.Invoke(style, [sourceId])!; + if (!hasSource) + { + _miAddGeoJsonSource!.Invoke(style, [sourceId, geoJsonUrl]); + _AddWifiCircleLayers(style, sourceId); + } + else + { + _miSetGeoJsonSourceUrl!.Invoke(style, [sourceId, geoJsonUrl]); + } + _renderNeedsUpdate = true; + } + + /// + /// Update an existing GeoJSON source with inline GeoJSON string data. + /// Use this for live/frequently-updated data to avoid HTTP round-trips. + /// If the source does not exist yet, it is created (with circle layers). + /// Requires a DLL build that includes the GeoJSON source/layer APIs. + /// + public void SetWifiGeoJsonLayerData(string sourceId, string geoJson) + { + if (_map == null) return; + var style = _map.Style; + EnsureStyleApiResolved(style); + if (!StyleApiAvailable) { Log("SetWifiGeoJsonLayerData: Style API not available in this DLL build"); return; } + + bool hasSource = (bool)_miHasSource!.Invoke(style, [sourceId])!; + if (!hasSource) + { + _miAddGeoJsonSource!.Invoke(style, [sourceId, ""]); + _AddWifiCircleLayers(style, sourceId); + } + _miSetGeoJsonSourceData!.Invoke(style, [sourceId, geoJson]); + _renderNeedsUpdate = true; + } + + /// Remove a GeoJSON wifi layer set (3 security-type circle layers + source). + public void RemoveWifiGeoJsonLayer(string sourceId) + { + if (_map == null) return; + var style = _map.Style; + EnsureStyleApiResolved(style); + if (!StyleApiAvailable) return; + + _miRemoveLayer!.Invoke(style, [sourceId + "_open"]); + _miRemoveLayer!.Invoke(style, [sourceId + "_wep"]); + _miRemoveLayer!.Invoke(style, [sourceId + "_secure"]); + _miRemoveSource!.Invoke(style, [sourceId]); + _renderNeedsUpdate = true; + } + + private static void _AddWifiCircleLayers(object style, string sourceId) + { + void AddIfMissing(string lid, string color) + { + bool has = (bool)_miHasLayer!.Invoke(style, [lid])!; + if (!has) _miAddCircleLayer!.Invoke(style, [lid, sourceId, color, 5f, 0.85f, null]); + } + AddIfMissing(sourceId + "_open", "#00802b"); // green + AddIfMissing(sourceId + "_wep", "#cc7a00"); // orange + AddIfMissing(sourceId + "_secure", "#b30000"); // red + } + + // ── Win32 / WGL P/Invoke ────────────────────────────────────────────────── + + [DllImport("user32.dll")] private static extern IntPtr CreateWindowEx( + uint dwExStyle, string lpClassName, string lpWindowName, uint dwStyle, + int x, int y, int nWidth, int nHeight, + IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr lpParam); + + [DllImport("user32.dll")] private static extern bool DestroyWindow(IntPtr hWnd); + [DllImport("user32.dll")] private static extern bool SetWindowPos( + IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags); + + [DllImport("user32.dll")] private static extern IntPtr GetDC(IntPtr hWnd); + [DllImport("user32.dll")] private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC); + + [DllImport("opengl32.dll")] private static extern IntPtr wglCreateContext(IntPtr hDC); + [DllImport("opengl32.dll")] private static extern bool wglDeleteContext(IntPtr hGLRC); + [DllImport("opengl32.dll")] private static extern bool wglMakeCurrent(IntPtr hDC, IntPtr hGLRC); + [DllImport("opengl32.dll")] private static extern IntPtr wglGetProcAddress(string procName); + [DllImport("opengl32.dll")] private static extern void glViewport(int x, int y, int width, int height); + [DllImport("opengl32.dll")] private static extern uint glGetError(); + [DllImport("opengl32.dll")] private static extern void glClearColor(float r, float g, float b, float a); + [DllImport("opengl32.dll")] private static extern void glClear(uint mask); + + private delegate void glBindFramebufferDelegate(uint target, uint framebuffer); + private glBindFramebufferDelegate? glBindFramebuffer; + + private const uint GL_COLOR_BUFFER_BIT = 0x00004000; + private const uint GL_DEPTH_BUFFER_BIT = 0x00000100; + private const uint GL_STENCIL_BUFFER_BIT = 0x00000400; + private const uint GL_FRAMEBUFFER = 0x8D40; + + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + private delegate IntPtr WglCreateContextAttribsARBDelegate(IntPtr hDC, IntPtr hShareContext, int[] attribList); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + private struct WNDCLASSEXA + { + public uint cbSize, style; + public IntPtr lpfnWndProc; + public int cbClsExtra, cbWndExtra; + public IntPtr hInstance, hIcon, hCursor, hbrBackground; + [MarshalAs(UnmanagedType.LPStr)] public string? lpszMenuName; + [MarshalAs(UnmanagedType.LPStr)] public string lpszClassName; + public IntPtr hIconSm; + } + + [DllImport("user32.dll", CharSet = CharSet.Ansi)] private static extern ushort RegisterClassExA(ref WNDCLASSEXA wc); + [DllImport("user32.dll")] private static extern IntPtr DefWindowProcA(IntPtr hWnd, uint msg, IntPtr w, IntPtr l); + [DllImport("kernel32.dll")] private static extern IntPtr GetModuleHandleW(IntPtr lpModuleName); + + private const int WGL_CONTEXT_MAJOR_VERSION_ARB = 0x2091; + private const int WGL_CONTEXT_MINOR_VERSION_ARB = 0x2092; + private const int WGL_CONTEXT_PROFILE_MASK_ARB = 0x9126; + private const uint CS_OWNDC = 0x0020; + private const string MapLibreWindowClass = "MapLibreGLChild"; + + private static WndProcDelegate? _wndProcKeepAlive; + private static bool _classRegistered; + + private static void EnsureWindowClassRegistered() + { + if (_classRegistered) return; + _wndProcKeepAlive = (hWnd, msg, w, l) => DefWindowProcA(hWnd, msg, w, l); + var wc = new WNDCLASSEXA + { + cbSize = (uint)Marshal.SizeOf(), + style = CS_OWNDC, + lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcKeepAlive), + hInstance = GetModuleHandleW(IntPtr.Zero), + lpszClassName = MapLibreWindowClass, + }; + RegisterClassExA(ref wc); + _classRegistered = true; + } + + [DllImport("gdi32.dll")] private static extern int ChoosePixelFormat(IntPtr hdc, ref PIXELFORMATDESCRIPTOR ppfd); + [DllImport("gdi32.dll")] private static extern bool SetPixelFormat(IntPtr hdc, int iPixelFormat, ref PIXELFORMATDESCRIPTOR ppfd); + [DllImport("gdi32.dll")] private static extern bool SwapBuffers(IntPtr hdc); + + [StructLayout(LayoutKind.Sequential)] + private struct POINT { public int X, Y; } + [DllImport("user32.dll")] private static extern bool ScreenToClient(IntPtr hWnd, ref POINT lpPoint); + [DllImport("user32.dll")] private static extern IntPtr SetCapture(IntPtr hWnd); + [DllImport("user32.dll")] private static extern bool ReleaseCapture(); + + [StructLayout(LayoutKind.Sequential)] + private struct PIXELFORMATDESCRIPTOR + { + public ushort nSize, nVersion; + public uint dwFlags; + public byte iPixelType, cColorBits, cRedBits, cRedShift, cGreenBits, cGreenShift, cBlueBits, cBlueShift; + public byte cAlphaBits, cAlphaShift, cAccumBits, cAccumRedBits, cAccumGreenBits, cAccumBlueBits, cAccumAlphaBits; + public byte cDepthBits, cStencilBits, cAuxBuffers, iLayerType, bReserved; + public uint dwLayerMask, dwVisibleMask, dwDamageMask; + } + + private const uint PFD_DRAW_TO_WINDOW = 0x00000004; + private const uint PFD_SUPPORT_OPENGL = 0x00000020; + private const uint PFD_DOUBLEBUFFER = 0x00000001; + private const uint WS_CHILD = 0x40000000; + private const uint WS_VISIBLE = 0x10000000; + private const uint WS_CLIPCHILDREN = 0x02000000; + private const uint WS_CLIPSIBLINGS = 0x04000000; + + // ── State ───────────────────────────────────────────────────────────────── + + private IntPtr _childHwnd = IntPtr.Zero; + private IntPtr _hDC = IntPtr.Zero; + private IntPtr _hGLRC = IntPtr.Zero; + + private ExternalRenderingContextFrontend? _frontend; + private Map? _map; + private RunLoop? _runLoop; + private DispatcherTimer? _renderTimer; + private bool _glReady; + private bool _initialized; + private bool _renderNeedsUpdate = true; + private float _dpi = 1.0f; + private int _renderTickCount; + + // ── Input state ─────────────────────────────────────────────────────────── + + private bool _isDragging; + private short _lastMouseX; + private short _lastMouseY; + + // ── Navigation popup ────────────────────────────────────────────────────── + + private Popup? _navPopup; + private RotateTransform? _compassRotate; + + // ── HwndHost overrides ──────────────────────────────────────────────────── + + protected override HandleRef BuildWindowCore(HandleRef hwndParent) + { + EnsureWindowClassRegistered(); + int initW = Math.Max(1, (int)ActualWidth); + int initH = Math.Max(1, (int)ActualHeight); + _childHwnd = CreateWindowEx( + 0, MapLibreWindowClass, "", + WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, + 0, 0, initW, initH, + hwndParent.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); + + IsVisibleChanged += OnIsVisibleChanged; + Dispatcher.BeginInvoke(DispatcherPriority.Loaded, (Action)TryInitialize); + + return new HandleRef(this, _childHwnd); + } + + private void OnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) + { + bool visible = (bool)e.NewValue; + if (visible) + Dispatcher.BeginInvoke(DispatcherPriority.Loaded, (Action)TryInitialize); + else + _renderTimer?.Stop(); + + if (_navPopup != null) + _navPopup.IsOpen = visible && ShowNavigationControls; + } + + private void TryInitialize() + { + if (_initialized) { _renderTimer?.Start(); return; } + if (!IsVisible || ActualWidth < 2 || ActualHeight < 2 || _childHwnd == IntPtr.Zero) return; + + _dpi = GetDpiScale(); + int physW = Math.Max(1, (int)(ActualWidth * _dpi)); + int physH = Math.Max(1, (int)(ActualHeight * _dpi)); + SetWindowPos(_childHwnd, IntPtr.Zero, 0, 0, physW, physH, 0x0040); + + _initialized = true; + try { InitOpenGl(); Log("InitOpenGl OK"); } catch (Exception ex) { Log($"InitOpenGl EX: {ex}"); throw; } + try { InitMaplibre(); Log("InitMaplibre OK"); } catch (Exception ex) { Log($"InitMaplibre EX: {ex}"); throw; } + try { InitNavPopup(); } catch (Exception ex) { Log($"InitNavPopup EX: {ex}"); } + + // Hide nav popup when the host window loses focus so it doesn't float + // over unrelated windows (WPF Popup creates its own top-level HWND). + var parentWin = System.Windows.Window.GetWindow(this); + if (parentWin != null) + { + parentWin.Deactivated += (_, _) => { if (_navPopup != null) _navPopup.IsOpen = false; }; + parentWin.Activated += (_, _) => { if (_navPopup != null) _navPopup.IsOpen = ShowNavigationControls && IsVisible; }; + } + + _renderTimer?.Start(); + } + + protected override void DestroyWindowCore(HandleRef hwnd) + { + IsVisibleChanged -= OnIsVisibleChanged; + _renderTimer?.Stop(); + _renderTimer = null; + + if (_navPopup != null) { _navPopup.IsOpen = false; _navPopup = null; } + + _map?.Dispose(); _map = null; + _frontend?.Dispose(); _frontend = null; + _runLoop?.Dispose(); _runLoop = null; + + if (_hGLRC != IntPtr.Zero) { wglMakeCurrent(IntPtr.Zero, IntPtr.Zero); wglDeleteContext(_hGLRC); _hGLRC = IntPtr.Zero; } + if (_hDC != IntPtr.Zero) { ReleaseDC(_childHwnd, _hDC); _hDC = IntPtr.Zero; } + if (_childHwnd != IntPtr.Zero) { DestroyWindow(_childHwnd); _childHwnd = IntPtr.Zero; } + } + + protected override void OnRenderSizeChanged(SizeChangedInfo info) + { + base.OnRenderSizeChanged(info); + float dpi = GetDpiScale(); + int wP = Math.Max(1, (int)(info.NewSize.Width * dpi)); + int hP = Math.Max(1, (int)(info.NewSize.Height * dpi)); + + if (_childHwnd != IntPtr.Zero) + SetWindowPos(_childHwnd, IntPtr.Zero, 0, 0, wP, hP, 0x0040); + + if (_frontend != null) + { + _map?.SetSize(new MaplibreNative.Size((uint)info.NewSize.Width, (uint)info.NewSize.Height)); + _frontend.Backend.Size = new MaplibreNative.Size((uint)wP, (uint)hP); + } + _renderNeedsUpdate = true; + + PositionNavPopup(); + + if (!_initialized && IsVisible) + Dispatcher.BeginInvoke(DispatcherPriority.Loaded, (Action)TryInitialize); + } + + // ── Win32 mouse input → MapLibre camera ─────────────────────────────────── + + private const int WM_LBUTTONDOWN = 0x0201; + private const int WM_LBUTTONUP = 0x0202; + private const int WM_LBUTTONDBLCLK = 0x0203; + private const int WM_MOUSEMOVE = 0x0200; + private const int WM_MOUSEWHEEL = 0x020A; + + protected override IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) + { + if (hwnd == _childHwnd && _map != null) + { + long lp = lParam.ToInt64(); + short cx = (short)(lp & 0xFFFF); + short cy = (short)((lp >> 16) & 0xFFFF); + + switch (msg) + { + case WM_LBUTTONDOWN: + _isDragging = true; + _lastMouseX = cx; + _lastMouseY = cy; + SetCapture(hwnd); + handled = true; + break; + + case WM_LBUTTONDBLCLK: + { + // Cancel any drag initiated by the preceding WM_LBUTTONDOWN, + // then zoom in 1 level centred on the cursor. + _isDragging = false; + ReleaseCapture(); + double anchorX = cx / _dpi; + double anchorY = cy / _dpi; + _map.ScaleBy(2.0, new System.Nullable(new PointDouble(anchorX, anchorY))); + _renderNeedsUpdate = true; + handled = true; + break; + } + + case WM_MOUSEMOVE: + if (_isDragging) + { + double dx = (cx - _lastMouseX) / _dpi; + double dy = (cy - _lastMouseY) / _dpi; + _map.MoveBy(new PointDouble(dx, dy)); + _lastMouseX = cx; + _lastMouseY = cy; + _renderNeedsUpdate = true; + handled = true; + } + break; + + case WM_LBUTTONUP: + _isDragging = false; + ReleaseCapture(); + handled = true; + break; + + case WM_MOUSEWHEEL: + { + int wheelDelta = (short)((wParam.ToInt64() >> 16) & 0xFFFF); + var pt = new POINT { X = (short)(lp & 0xFFFF), Y = (short)((lp >> 16) & 0xFFFF) }; + ScreenToClient(hwnd, ref pt); + double anchorX = pt.X / _dpi; + double anchorY = pt.Y / _dpi; + double scale = Math.Pow(1.15, wheelDelta / 120.0); + _map.ScaleBy(scale, new System.Nullable(new PointDouble(anchorX, anchorY))); + _renderNeedsUpdate = true; + handled = true; + break; + } + } + } + return base.WndProc(hwnd, msg, wParam, lParam, ref handled); + } + + // ── Navigation popup ────────────────────────────────────────────────────── + + private void InitNavPopup() + { + // Outer border groups all three buttons with a shared drop-shadow. + var outerBorder = new Border + { + CornerRadius = new CornerRadius(4), + Effect = new DropShadowEffect + { + BlurRadius = 6, + ShadowDepth = 2, + Opacity = 0.25, + Color = Colors.Black, + Direction = 270, + }, + }; + + var panel = new StackPanel { Width = 29 }; + outerBorder.Child = panel; + + // Zoom-in (+) + var zoomInBtn = MakeNavButton("+", ZoomIn); + SetButtonCorners(zoomInBtn, topLeft: 4, topRight: 4, bottomRight: 0, bottomLeft: 0); + panel.Children.Add(zoomInBtn); + + // Divider + panel.Children.Add(new Border + { + Height = 1, + Background = new SolidColorBrush(Color.FromRgb(218, 218, 218)), + }); + + // Zoom-out (−) + var zoomOutBtn = MakeNavButton("\u2212", ZoomOut); + SetButtonCorners(zoomOutBtn, topLeft: 0, topRight: 0, bottomRight: 0, bottomLeft: 0); + panel.Children.Add(zoomOutBtn); + + // Divider + panel.Children.Add(new Border + { + Height = 1, + Background = new SolidColorBrush(Color.FromRgb(218, 218, 218)), + }); + + // Compass / reset-north (↑, rotates with map bearing) + _compassRotate = new RotateTransform { CenterX = 0.5, CenterY = 0.5 }; + var compassIcon = new TextBlock + { + Text = "\u2191", // ↑ + FontSize = 16, + FontWeight = FontWeights.Bold, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + IsHitTestVisible = false, + RenderTransformOrigin = new Point(0.5, 0.5), + RenderTransform = _compassRotate, + }; + var compassBtn = MakeNavButton(null, ResetNorth); + SetButtonCorners(compassBtn, topLeft: 0, topRight: 0, bottomRight: 4, bottomLeft: 4); + compassBtn.Child = compassIcon; + panel.Children.Add(compassBtn); + + _navPopup = new Popup + { + AllowsTransparency = true, + StaysOpen = true, + IsHitTestVisible = true, + PlacementTarget = this, + Placement = PlacementMode.Relative, + Child = outerBorder, + }; + + PositionNavPopup(); + _navPopup.IsOpen = ShowNavigationControls && IsVisible; + } + + /// Creates a 29×29 white nav button with hover effect. + private static Border MakeNavButton(string? text, Action onClick) + { + var btn = new Border + { + Width = 29, + Height = 29, + Background = Brushes.White, + Cursor = Cursors.Hand, + }; + + if (text != null) + { + btn.Child = new TextBlock + { + Text = text, + FontSize = 20, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + IsHitTestVisible = false, + }; + } + + btn.MouseLeftButtonDown += (_, e) => { onClick(); e.Handled = true; }; + btn.MouseEnter += (_, _) => btn.Background = new SolidColorBrush(Color.FromRgb(240, 240, 240)); + btn.MouseLeave += (_, _) => btn.Background = Brushes.White; + return btn; + } + + private static void SetButtonCorners(Border btn, double topLeft, double topRight, double bottomRight, double bottomLeft) + => btn.CornerRadius = new CornerRadius(topLeft, topRight, bottomRight, bottomLeft); + + private void PositionNavPopup() + { + if (_navPopup == null) return; + const double margin = 10; + const double panelWidth = 29; + // Place in the top-right corner (mirrors maplibre-gl-js default). + _navPopup.HorizontalOffset = ActualWidth - panelWidth - margin; + _navPopup.VerticalOffset = margin; + } + + private void UpdateCompassBearing() + { + if (_compassRotate == null || _map == null) return; + double bearing = _map.GetCameraOptions()?.Bearing ?? 0.0; + // Rotate the arrow counter-clockwise to indicate bearing offset from north. + _compassRotate.Angle = -bearing; + } + + // ── Helpers ─────────────────────────────────────────────────────────────── + + private float GetDpiScale() + { + var src = PresentationSource.FromVisual(this); + var m = src?.CompositionTarget?.TransformToDevice.M11; + return (float)(m ?? 1.0); + } + + // ── OpenGL init ─────────────────────────────────────────────────────────── + + private void InitOpenGl() + { + if (_glReady) return; + _hDC = GetDC(_childHwnd); + Log($"InitOpenGl: hDC={_hDC}"); + + var pfd = new PIXELFORMATDESCRIPTOR + { + nSize = (ushort)Marshal.SizeOf(), + nVersion = 1, + dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + iPixelType = 0, + cColorBits = 24, + cDepthBits = 24, + cStencilBits = 8, + }; + + int fmt = ChoosePixelFormat(_hDC, ref pfd); + SetPixelFormat(_hDC, fmt, ref pfd); + Log($"InitOpenGl: format={fmt}"); + + IntPtr tempCtx = wglCreateContext(_hDC); + wglMakeCurrent(_hDC, tempCtx); + + IntPtr pAttribs = wglGetProcAddress("wglCreateContextAttribsARB"); + if (pAttribs != IntPtr.Zero) + { + var createAttribs = Marshal.GetDelegateForFunctionPointer(pAttribs); + int[] attribs = + [ + WGL_CONTEXT_MAJOR_VERSION_ARB, 3, + WGL_CONTEXT_MINOR_VERSION_ARB, 3, + WGL_CONTEXT_PROFILE_MASK_ARB, 0x00000002, // Compatibility Profile + 0, + ]; + _hGLRC = createAttribs(_hDC, IntPtr.Zero, attribs); + wglMakeCurrent(IntPtr.Zero, IntPtr.Zero); + wglDeleteContext(tempCtx); + wglMakeCurrent(_hDC, _hGLRC); + } + else + { + _hGLRC = tempCtx; + } + + IntPtr pBindFb = wglGetProcAddress("glBindFramebuffer"); + if (pBindFb != IntPtr.Zero) + glBindFramebuffer = Marshal.GetDelegateForFunctionPointer(pBindFb); + + _glReady = true; + Log($"InitOpenGl done: hGLRC={_hGLRC}"); + } + + // ── MapLibre init ───────────────────────────────────────────────────────── + + private void InitMaplibre() + { + float dpi = _dpi; + int wL = Math.Max(1, (int)ActualWidth); + int hL = Math.Max(1, (int)ActualHeight); + int wP = Math.Max(1, (int)(ActualWidth * dpi)); + int hP = Math.Max(1, (int)(ActualHeight * dpi)); + + var sizeLogical = new MaplibreNative.Size((uint)wL, (uint)hL); + var sizePhysical = new MaplibreNative.Size((uint)wP, (uint)hP); + + // MapLibre uses libuv for async I/O. A RunLoop on this thread is required + // before creating the Map; RunOnce() is called every timer tick to pump it. + _runLoop = new RunLoop(RunLoop.Type.New); + + _frontend = new ExternalRenderingContextFrontend(_hDC, _hGLRC, sizePhysical, dpi); + _frontend.Updated += _ => { _renderNeedsUpdate = true; }; + Log($"InitMaplibre: logical={wL}x{hL} physical={wP}x{hP} dpi={dpi}"); + + var mapOptions = new MapOptions() + .WithMapMode(MapMode.Continuous) + .WithSize(sizeLogical) + .WithPixelRatio(dpi); + + var resOptions = new ResourceOptions() + .WithCachePath(System.IO.Path.Combine(System.IO.Path.GetTempPath(), "maplibre_cache.db")) + .WithAssetPath(AppDomain.CurrentDomain.BaseDirectory); + + var obs = new MaplibreNative.WPF.DelegateMapObserver((type, msg) => + Log($"[MapObserver:{type}] {msg}")); + + _map = new Map(_frontend, obs, mapOptions, resOptions); + Log("Map created"); + + _map.Style.LoadURL(StyleUrl); + Log($"Style.LoadURL: {StyleUrl}"); + + var initCam = new CameraOptions(); + initCam.Center = new LatLng(20.0, 0.0); + initCam.Zoom = 2.0; + _map.JumpTo(initCam); + + _renderTimer = new DispatcherTimer(DispatcherPriority.Render) + { + Interval = TimeSpan.FromMilliseconds(16) + }; + _renderTimer.Tick += OnRenderTick; + } + + private void OnRenderTick(object? sender, EventArgs e) + { + // Sync child window size if it has drifted (e.g. after DPI change). + if (_frontend != null && _map != null && ActualWidth > 1 && ActualHeight > 1) + { + float curDpi = GetDpiScale(); + var sizePhys = new MaplibreNative.Size( + (uint)(ActualWidth * curDpi), (uint)(ActualHeight * curDpi)); + var backendSize = _frontend.Backend.Size; + if (backendSize.Width != sizePhys.Width || backendSize.Height != sizePhys.Height) + { + _map.SetSize(new MaplibreNative.Size((uint)ActualWidth, (uint)ActualHeight)); + _frontend.Backend.Size = sizePhys; + if (_childHwnd != IntPtr.Zero) + SetWindowPos(_childHwnd, IntPtr.Zero, 0, 0, + (int)sizePhys.Width, (int)sizePhys.Height, 0x0040); + _renderNeedsUpdate = true; + } + } + + // Pump libuv so HTTP responses for style/tiles are delivered. + _runLoop?.RunOnce(); + + if (_renderNeedsUpdate && _glReady && _frontend != null) + { + _renderNeedsUpdate = false; + wglMakeCurrent(_hDC, _hGLRC); + + var sz = _frontend.Backend.Size; + glBindFramebuffer?.Invoke(GL_FRAMEBUFFER, 0); + glViewport(0, 0, (int)Math.Max(1, sz.Width), (int)Math.Max(1, sz.Height)); + glClearColor(0f, 0f, 0f, 1f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + + try { _frontend.Render(); } catch (Exception ex) { Log($"Render EX: {ex}"); } + + SwapBuffers(_hDC); + + _renderTickCount++; + if (_renderTickCount <= 5 || _renderTickCount % 120 == 0) + Log($"Render #{_renderTickCount}"); + + // Update compass arrow to reflect current bearing. + UpdateCompassBearing(); + } + } +} diff --git a/MaplibreNative.NET.WPF/MaplibreNative.NET.WPF.csproj b/MaplibreNative.NET.WPF/MaplibreNative.NET.WPF.csproj new file mode 100644 index 0000000..cbcd028 --- /dev/null +++ b/MaplibreNative.NET.WPF/MaplibreNative.NET.WPF.csproj @@ -0,0 +1,26 @@ + + + + net8.0-windows10.0.19041.0 + enable + true + x64 + + + + + + <_MaplibreNativeDll Condition="Exists('..\build\Release\MaplibreNative.NET.dll')">..\build\Release\MaplibreNative.NET.dll + <_MaplibreNativeDll Condition="'$(_MaplibreNativeDll)' == ''">..\..\VistumblerCS\lib\x64\MaplibreNative.NET.dll + + + + + + $(_MaplibreNativeDll) + + + + diff --git a/include/Style.h b/include/Style.h index 0cdbca6..55d5b05 100644 --- a/include/Style.h +++ b/include/Style.h @@ -27,40 +27,32 @@ namespace DOTNET_NAMESPACE System::String^ GetURL(); System::String^ GetName(); - /* - CameraOptions^ GetDefaultCamera(); - TransitionOptions^ GetTransitionOptions(); - System::Void SetTransitionOptions(TransitionOptions^ options); - Light* getLight(); - const Light* getLight() const; + // GeoJSON sources + /// Add a GeoJSON source that fetches its data from a URL. + System::Void AddGeoJsonSource(System::String^ sourceId, System::String^ url); + /// Update the URL of an existing GeoJSON source (no-op if source not found or wrong type). + System::Void SetGeoJsonSourceUrl(System::String^ sourceId, System::String^ url); + /// Set the data of an existing GeoJSON source from an inline GeoJSON string. + System::Void SetGeoJsonSourceData(System::String^ sourceId, System::String^ geojsonString); + /// Remove a source by id. Returns true if removed. + System::Boolean RemoveSource(System::String^ sourceId); + /// Returns true if a source with the given id exists. + System::Boolean HasSource(System::String^ sourceId); + + // Circle layers + /// + /// Add a circle layer above all existing layers. + /// color: CSS hex string e.g. "#ff0000". + /// filter: optional JSON expression string e.g. "["==", ["get","type"], "wifi"]" or empty/null. + /// + System::Void AddCircleLayer(System::String^ layerId, System::String^ sourceId, + System::String^ color, float radius, float opacity, + System::String^ filterJson); + /// Remove a layer by id. Returns true if removed. + System::Boolean RemoveLayer(System::String^ layerId); + /// Returns true if a layer with the given id exists. + System::Boolean HasLayer(System::String^ layerId); - void setLight(std::unique_ptr); - - // Images - optional getImage(const std::string&) const; - void addImage(std::unique_ptr); - void removeImage(const std::string&); - - // Sources - std::vector< Source*> getSources(); - std::vector getSources() const; - - Source* getSource(const std::string&); - const Source* getSource(const std::string&) const; - - void addSource(std::unique_ptr); - std::unique_ptr removeSource(const std::string& sourceID); - - // Layers - std::vector< Layer*> getLayers(); - std::vector getLayers() const; - - Layer* getLayer(const std::string&); - const Layer* getLayer(const std::string&) const; - - void addLayer(std::unique_ptr, const optional& beforeLayerID = {}); - std::unique_ptr removeLayer(const std::string& layerID); - */ internal: Style(NativePointerHolder^ nativePointerHolder); }; diff --git a/src/Style.cpp b/src/Style.cpp index 59792d6..72ae88e 100644 --- a/src/Style.cpp +++ b/src/Style.cpp @@ -3,6 +3,10 @@ #include "Style.h" #include #include +#include +#include +#include +#include namespace DOTNET_NAMESPACE { @@ -42,4 +46,88 @@ namespace DOTNET_NAMESPACE { return Convert::ToSystemString(NativePointer->getName()); } + + System::Void Style::AddGeoJsonSource(System::String^ sourceId, System::String^ url) + { + std::string id = Convert::ToStdString(sourceId); + std::string uri = Convert::ToStdString(url); + auto src = std::make_unique(id); + src->setURL(uri); + NativePointer->addSource(std::move(src)); + } + + System::Void Style::SetGeoJsonSourceUrl(System::String^ sourceId, System::String^ url) + { + std::string id = Convert::ToStdString(sourceId); + std::string uri = Convert::ToStdString(url); + auto* raw = NativePointer->getSource(id); + if (!raw) return; + auto* gjs = raw->as(); + if (!gjs) return; + gjs->setURL(uri); + } + + System::Void Style::SetGeoJsonSourceData(System::String^ sourceId, System::String^ geojsonString) + { + std::string id = Convert::ToStdString(sourceId); + std::string geoJson = Convert::ToStdString(geojsonString); + auto* raw = NativePointer->getSource(id); + if (!raw) return; + auto* gjs = raw->as(); + if (!gjs) return; + mbgl::style::conversion::Error err; + auto parsed = mbgl::style::conversion::parseGeoJSON(geoJson, err); + if (parsed) { + gjs->setGeoJSON(*parsed); + } + } + + System::Boolean Style::RemoveSource(System::String^ sourceId) + { + std::string id = Convert::ToStdString(sourceId); + return NativePointer->removeSource(id) != nullptr; + } + + System::Boolean Style::HasSource(System::String^ sourceId) + { + std::string id = Convert::ToStdString(sourceId); + return NativePointer->getSource(id) != nullptr; + } + + System::Void Style::AddCircleLayer(System::String^ layerId, System::String^ sourceId, + System::String^ color, float radius, float opacity, + System::String^ filterJson) + { + std::string lid = Convert::ToStdString(layerId); + std::string sid = Convert::ToStdString(sourceId); + + auto layer = std::make_unique(lid, sid); + + // Parse hex color string (e.g. "#rrggbb") + std::string colorStr = Convert::ToStdString(color); + auto parsedColor = mbgl::Color::parse(colorStr); + if (parsedColor) { + layer->setCircleColor(mbgl::style::PropertyValue(*parsedColor)); + } + + layer->setCircleRadius(mbgl::style::PropertyValue(radius)); + layer->setCircleOpacity(mbgl::style::PropertyValue(opacity)); + + // filterJson is reserved for future use; filter parsing requires full + // JSON conversion infrastructure which is intentionally omitted here. + + NativePointer->addLayer(std::move(layer)); + } + + System::Boolean Style::RemoveLayer(System::String^ layerId) + { + std::string id = Convert::ToStdString(layerId); + return NativePointer->removeLayer(id) != nullptr; + } + + System::Boolean Style::HasLayer(System::String^ layerId) + { + std::string id = Convert::ToStdString(layerId); + return NativePointer->getLayer(id) != nullptr; + } } From 66fc0f96b280df4a3639e0f08a6e201b226641a4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 20:40:51 -0400 Subject: [PATCH 30/63] Release v1.0.2 (#2) Co-authored-by: acalcutt <3792408+acalcutt@users.noreply.github.com> --- CHANGELOG.md | 5 +++++ CMakeLists.txt | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ec5992..8c14195 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,3 +7,8 @@ ### 🐞 Bug fixes - _...Add new stuff here..._ +## 1.0.2 +### ✨ Features and improvements + +### 🐞 Bug fixes + diff --git a/CMakeLists.txt b/CMakeLists.txt index f0369a1..894d0b7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.10 FATAL_ERROR) set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/dependencies/maplibre-native/platform/windows/custom-toolchain.cmake) -set(MAPLIBRE_NET_VERSION "1.0.1") +set(MAPLIBRE_NET_VERSION "1.0.2") project("MaplibreNative.NET" LANGUAGES CXX C) set(CMAKE_CXX_STANDARD 20) From dab61b32c5e31b25b7232e61f90f164972102728 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Thu, 30 Apr 2026 22:43:54 -0400 Subject: [PATCH 31/63] Update release.yml --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4b6d862..f35c380 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -204,7 +204,7 @@ jobs: tag: v${{ needs.release-check.outputs.version }} name: v${{ needs.release-check.outputs.version }} bodyFile: changelog_for_version.md - artifacts: ./artifacts/MaplibreNative.NET-v${{ needs.release-check.outputs.version }}-win-x64.zip + artifacts: ./artifacts/MaplibreNative.NET-v${{ needs.release-check.outputs.version }}-win-*.zip prerelease: ${{ steps.flags.outputs.prerelease }} allowUpdates: true draft: false From 3dde6f3a566e94c373df8865f0b5809fc8eb6219 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Thu, 30 Apr 2026 22:51:29 -0400 Subject: [PATCH 32/63] Fix WPF csproj DLL path (remove VistumblerCS reference); fix release workflow to publish both x64 and arm64 zips --- MaplibreNative.NET.WPF/MaplibreNative.NET.WPF.csproj | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/MaplibreNative.NET.WPF/MaplibreNative.NET.WPF.csproj b/MaplibreNative.NET.WPF/MaplibreNative.NET.WPF.csproj index cbcd028..79f68eb 100644 --- a/MaplibreNative.NET.WPF/MaplibreNative.NET.WPF.csproj +++ b/MaplibreNative.NET.WPF/MaplibreNative.NET.WPF.csproj @@ -9,11 +9,13 @@ its output directory alongside this assembly. --> - + <_MaplibreNativeDll Condition="Exists('..\build\Release\MaplibreNative.NET.dll')">..\build\Release\MaplibreNative.NET.dll - <_MaplibreNativeDll Condition="'$(_MaplibreNativeDll)' == ''">..\..\VistumblerCS\lib\x64\MaplibreNative.NET.dll + <_MaplibreNativeDll Condition="'$(_MaplibreNativeDll)' == ''">$(MSBuildThisFileDirectory)..\build\Release\MaplibreNative.NET.dll From 823130513fb37f9fadecd53fc0a472804544440d Mon Sep 17 00:00:00 2001 From: acalcutt Date: Thu, 30 Apr 2026 22:53:17 -0400 Subject: [PATCH 33/63] Remove fork-specific name from WPF csproj comment --- MaplibreNative.NET.WPF/MaplibreNative.NET.WPF.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MaplibreNative.NET.WPF/MaplibreNative.NET.WPF.csproj b/MaplibreNative.NET.WPF/MaplibreNative.NET.WPF.csproj index 79f68eb..4800f6b 100644 --- a/MaplibreNative.NET.WPF/MaplibreNative.NET.WPF.csproj +++ b/MaplibreNative.NET.WPF/MaplibreNative.NET.WPF.csproj @@ -11,7 +11,7 @@ <_MaplibreNativeDll Condition="Exists('..\build\Release\MaplibreNative.NET.dll')">..\build\Release\MaplibreNative.NET.dll From ef38d9c32a3b760191cb7ffb35c4c500c24347c1 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Thu, 30 Apr 2026 22:59:21 -0400 Subject: [PATCH 34/63] Support multiple .NET versions in build; update examples to net8.0 - CMakeLists: add MAPLIBRE_NET_DOTNET_VERSION cache variable (default net8.0) - release.yml: add dotnet matrix dimension (net8.0, net9.0); pass version to cmake; include dotnet in artifact/zip names - Examples: update TargetFramework from net7.0-windows to net8.0-windows --- .github/workflows/release.yml | 15 +++++++++------ CMakeLists.txt | 4 +++- .../Example.Console/Example.Console.csproj | 2 +- Examples/WPF/Example.WPF/Example.WPF.csproj | 2 +- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f35c380..203641d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -52,7 +52,7 @@ jobs: release_type: ${{ steps.check.outputs.release_type }} build: - name: Build Windows ${{ matrix.arch }} + name: Build Windows ${{ matrix.arch }} ${{ matrix.dotnet }} runs-on: ${{ matrix.runs-on }} needs: release-check if: ${{ needs.release-check.outputs.published == 'false' }} @@ -60,6 +60,7 @@ jobs: strategy: fail-fast: false matrix: + dotnet: [net8.0, net9.0] include: - runs-on: windows-latest arch: x64 @@ -115,7 +116,8 @@ jobs: -DMLN_WITH_OPENGL=ON ` -DMLN_WITH_EGL=OFF ` -DMLN_WITH_VULKAN=OFF ` - "-DCMAKE_CXX_FLAGS=/wd4267" + "-DCMAKE_CXX_FLAGS=/wd4267" ` + "-DMAPLIBRE_NET_DOTNET_VERSION=${{ matrix.dotnet }}" if ($LASTEXITCODE -eq 0) { break } if ($attempt -lt $maxAttempts) { Write-Host "Attempt $attempt failed (exit $LASTEXITCODE), cleaning and retrying in 30s..." @@ -133,6 +135,7 @@ jobs: run: | $version = "${{ needs.release-check.outputs.version }}" $arch = "${{ matrix.arch }}" + $dotnet = "${{ matrix.dotnet }}" $outDir = "build\Release" New-Item -ItemType Directory -Path publish -Force @@ -148,15 +151,15 @@ jobs: Where-Object { $_.Name -ne "MaplibreNative.NET.dll" } | Copy-Item -Destination publish\ - Compress-Archive -Path "publish\*" -DestinationPath "MaplibreNative.NET-v${version}-win-${arch}.zip" - Write-Host "Packaged: MaplibreNative.NET-v${version}-win-${arch}.zip" + Compress-Archive -Path "publish\*" -DestinationPath "MaplibreNative.NET-v${version}-win-${arch}-${dotnet}.zip" + Write-Host "Packaged: MaplibreNative.NET-v${version}-win-${arch}-${dotnet}.zip" Get-ChildItem publish\ - name: Upload build artifact uses: actions/upload-artifact@v4 with: - name: release-artifacts-${{ matrix.arch }} - path: MaplibreNative.NET-v${{ needs.release-check.outputs.version }}-win-${{ matrix.arch }}.zip + name: release-artifacts-${{ matrix.arch }}-${{ matrix.dotnet }} + path: MaplibreNative.NET-v${{ needs.release-check.outputs.version }}-win-${{ matrix.arch }}-${{ matrix.dotnet }}.zip retention-days: 7 release: diff --git a/CMakeLists.txt b/CMakeLists.txt index 894d0b7..cfbead9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,8 @@ endif() add_library(mbgl-dotnet SHARED) +set(MAPLIBRE_NET_DOTNET_VERSION "net8.0" CACHE STRING "Target .NET version for the C++/CLI wrapper (e.g. net8.0, net9.0)") + set(ASSEMBLY_NAME "MaplibreNative.NET") set_target_properties( @@ -20,7 +22,7 @@ set_target_properties( PROPERTIES COMMON_LANGUAGE_RUNTIME "NetCore" VS_GLOBAL_CLRSupport "NetCore" - VS_GLOBAL_TargetFramework "net8.0" + VS_GLOBAL_TargetFramework "${MAPLIBRE_NET_DOTNET_VERSION}" OUTPUT_NAME ${ASSEMBLY_NAME} ) diff --git a/Examples/Console/Example.Console/Example.Console.csproj b/Examples/Console/Example.Console/Example.Console.csproj index 3f27b1a..1550b8e 100644 --- a/Examples/Console/Example.Console/Example.Console.csproj +++ b/Examples/Console/Example.Console/Example.Console.csproj @@ -2,7 +2,7 @@ Exe - net7.0-windows + net8.0-windows enable enable true diff --git a/Examples/WPF/Example.WPF/Example.WPF.csproj b/Examples/WPF/Example.WPF/Example.WPF.csproj index 1f1c84a..96f8f68 100644 --- a/Examples/WPF/Example.WPF/Example.WPF.csproj +++ b/Examples/WPF/Example.WPF/Example.WPF.csproj @@ -2,7 +2,7 @@ WinExe - net7.0-windows + net8.0-windows enable true From 405ec640dc8c5d1dab1e5b0185fccfb7ac90744a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 23:02:38 -0400 Subject: [PATCH 35/63] Release v1.0.3 (#3) * Release v1.0.3 * Add multiple release targets to changelog --------- Co-authored-by: acalcutt <3792408+acalcutt@users.noreply.github.com> Co-authored-by: Andrew Calcutt --- CHANGELOG.md | 4 ++++ CMakeLists.txt | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c14195..25dfd10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ ### 🐞 Bug fixes - _...Add new stuff here..._ +## 1.0.3 +### ✨ Features and improvements +- Add multiple release targets + ## 1.0.2 ### ✨ Features and improvements diff --git a/CMakeLists.txt b/CMakeLists.txt index cfbead9..48f4beb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.10 FATAL_ERROR) set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/dependencies/maplibre-native/platform/windows/custom-toolchain.cmake) -set(MAPLIBRE_NET_VERSION "1.0.2") +set(MAPLIBRE_NET_VERSION "1.0.3") project("MaplibreNative.NET" LANGUAGES CXX C) set(CMAKE_CXX_STANDARD 20) From a9c687f877573ead126b7869c6ec6f8d3e5f6dfe Mon Sep 17 00:00:00 2001 From: acalcutt Date: Thu, 30 Apr 2026 23:06:55 -0400 Subject: [PATCH 36/63] Fix matrix: add arch as top-level dimension so dotnet x arch cross-multiplies --- .github/workflows/release.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 203641d..8fe2116 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -61,14 +61,15 @@ jobs: fail-fast: false matrix: dotnet: [net8.0, net9.0] + arch: [x64, arm64] include: - - runs-on: windows-latest - arch: x64 + - arch: x64 + runs-on: windows-latest vcpkg-triplet: x64-windows msvc-arch: x64 vs-arch: x64 - - runs-on: windows-11-arm - arch: arm64 + - arch: arm64 + runs-on: windows-11-arm vcpkg-triplet: arm64-windows msvc-arch: arm64 vs-arch: ARM64 From f640e1b59bc17b5827c8511318d8bb724a46471e Mon Sep 17 00:00:00 2001 From: acalcutt Date: Thu, 30 Apr 2026 23:58:09 -0400 Subject: [PATCH 37/63] README: rewrite build instructions for local PowerShell workflow - Update prerequisites (.NET 8 SDK required, VS 2022 only) - Document vendor/vector-tile remote fix needed during submodule init - Add vcpkg bootstrap + Get-VendorPackages step (was missing entirely) - Replace cmd/Ninja configure with correct VS 2022 PowerShell cmake invocation - Document VSCMD_ARG_TGT_ARCH requirement for vcpkg triplet selection - Add MAPLIBRE_NET_DOTNET_VERSION cmake option docs - Update .NET version references from 7.0 to 8.0+ --- README.md | 108 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 69 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index b532400..4c6b388 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ # MaplibreNative.NET -This is a .NET wrapper to the [maplibre-native](https://github.com/maplibre/maplibre-native). It is meant to be used in Windows and works with .NET Framework and .NET Core. +This is a .NET wrapper to the [maplibre-native](https://github.com/maplibre/maplibre-native). It is meant to be used on Windows and requires **.NET 8.0 or newer** (uses C++/CLI `/clr:netcore`). ## Using in your project Just add a reference to the `MaplibreNative.NET.dll` (see below how to build from sources), and you can immediately use the classes in the `MaplibreNative` namespace. -This is a minimal example using the `HeadlessFrontend` renderer, in a project with .NET 7.0 (Windows) and WPF enabled: +This is a minimal example using the `HeadlessFrontend` renderer, in a project with .NET 8.0+ (Windows) and WPF enabled: ```cs using MaplibreNative; @@ -68,75 +68,105 @@ using (var map = new Map(frontend, new MapObserver(), new MapOptions().WithSize( ``` You can check other examples at the [Examples](https://github.com/tdcosta100/MaplibreNative.NET/tree/main/Examples) directory. -**Attention:** the examples have a reference to `MaplibreNative.NET.dll` located at `build\Release` subdirectory. So you have to build MaplibreNative.NET with `Release` configuration before building the examples. +**Attention:** the examples reference `MaplibreNative.NET.dll` at `build\Release`. Build MaplibreNative.NET with `Release` configuration before building the examples (see *Building the sources* below). ## Building the sources ### Prerequisites -As this is a .NET build, `Microsoft Visual Studio` is required. This project was developed using a `Microsoft Visual Studio 2022` environment, but it should work with `Microsoft Visual Studio 2019` as well. - -To install the required Visual Studio components, open Visual Studio Installer and check `Desktop Development with C++` option. Make sure `C++ CMake tools for Windows` is selected in the right pane. If `git` is not already installed, select `Git for Windows` option in `Individual Components`. When Visual Studio finishes the install process, everything is ready to start. +- **Visual Studio 2022** with the **Desktop Development with C++** workload. + In Visual Studio Installer, confirm that these individual components are selected: + - `C++ CMake tools for Windows` + - `Git for Windows` (if git is not already installed) +- **.NET 8 SDK** (or newer) — required for the C++/CLI `/clr:netcore` compilation. +- **PowerShell** (included with Windows). ### Downloading sources -Open `x64 Native Tools Command Prompt for VS 2022` and then clone the repository: - -```cmd -git clone --recurse-submodules -j8 https://github.com/tdcosta100/MaplibreNative.NET.git -cd MaplibreNative.NET +```powershell +git clone -j8 https://github.com/andrewpavlov/MaplibreNative.NET-ac.git +cd MaplibreNative.NET-ac +git submodule update --init dependencies/maplibre-native ``` -### Configuring +Then initialize the nested vendor submodules. One submodule (`vendor/vector-tile`) may need its remote corrected manually before the full recursive init succeeds: -Configure the build with the following command: +```powershell +# Fix the vector-tile remote (only needed once) +Set-Location dependencies/maplibre-native/vendor/vector-tile +git remote set-url origin https://github.com/maplibre/mvt-cpp.git +git fetch origin +git checkout 8ad3bf0b78bf9cb0dba0abaccec0c180e1c27ab3 +Set-Location ../../../.. -```cmd -cmake . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release +# Initialize all remaining nested submodules +git submodule update --init --recursive dependencies/maplibre-native ``` -It will take some time to build and install all components on which the project depends. +### Installing vendor packages (vcpkg) + +This step downloads and builds all native dependencies via vcpkg. It only needs to be done once (results are cached). -### Alternative configure commands +```powershell +Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned -To configure build with EGL support (ANGLE libraries will be build), use the following command: +# Bootstrap vcpkg (only needed once) +.\dependencies\maplibre-native\platform\windows\vendor\vcpkg\bootstrap-vcpkg.bat -disableMetrics -```cmd -cmake . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DMLN_WITH_EGL=ON +# Install packages (~20–40 min on first run) +.\dependencies\maplibre-native\platform\windows\Get-VendorPackages.ps1 -Triplet x64-windows -Renderer OpenGL -With-ICU ``` -To configure build with OSMesa (software rendering), use the following command: +### Configuring -``` -cmake . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DMLN_WITH_OSMESA=ON +The CMake toolchain reads `VSCMD_ARG_TGT_ARCH` to select the vcpkg triplet. Either run the following from a **Developer PowerShell for VS 2022** (which sets this automatically), or set it manually: + +```powershell +$cmake = "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe" + +$env:VSCMD_ARG_TGT_ARCH = "x64" +$env:VCPKG_BINARY_SOURCES = "clear;files,$PWD\dependencies\maplibre-native\platform\windows\vendor\vcpkg\archives,readwrite" + +& $cmake -B build -G "Visual Studio 17 2022" -A x64 ` + -DMLN_WITH_OPENGL=ON ` + -DMLN_WITH_EGL=OFF ` + -DMLN_WITH_VULKAN=OFF ` + "-DCMAKE_CXX_FLAGS=/wd4267" ``` -**WARNING:** as OSMesa doesn't have static libraries, it's necessary to copy `libglapi.dll`, `libGLESv2.dll` and `osmesa.dll` from `dependencies\maplibre-native\platform\windows\vendor\mesa3d\` to executable/dll directory you want to use, otherwise it won't run. +To target a specific .NET version (default is `net8.0`): + +```powershell +& $cmake -B build -G "Visual Studio 17 2022" -A x64 ` + -DMLN_WITH_OPENGL=ON -DMLN_WITH_EGL=OFF -DMLN_WITH_VULKAN=OFF ` + "-DCMAKE_CXX_FLAGS=/wd4267" ` + "-DMAPLIBRE_NET_DOTNET_VERSION=net9.0" +``` ### Building -Finally, build the project with the following command: +Build only the managed wrapper (fastest): -```cmd -cmake --build build +```powershell +& $cmake --build build --target mbgl-dotnet --config Release --parallel ``` -### Building with Microsoft Visual Studio +Or open `build\MaplibreNative.NET.sln` in Visual Studio, set configuration to `Release`, and build the `mbgl-dotnet` target. -Just omit the `-G Ninja` option from the configure command: +The output DLL will be at `build\Release\MaplibreNative.NET.dll`. -```cmd -cmake . -B build -``` +### Alternative renderers -The same can be done with alternative configure commands: +To configure with EGL support (ANGLE): -```cmd -cmake . -B build -DMLN_WITH_EGL=ON +```powershell +& $cmake -B build -G "Visual Studio 17 2022" -A x64 -DMLN_WITH_EGL=ON "-DCMAKE_CXX_FLAGS=/wd4267" ``` -or -```cmd -cmake . -B build -DMLN_WITH_OSMESA=ON + +To configure with OSMesa (software rendering): + +```powershell +& $cmake -B build -G "Visual Studio 17 2022" -A x64 -DMLN_WITH_OSMESA=ON "-DCMAKE_CXX_FLAGS=/wd4267" ``` -Once configure is done, open the file `build\MaplibreNative.NET.sln`. Build the target `ALL_BUILD` to build all targets, or pick a specific target. Don't forget to pick a build configuration (`Release`, `RelWithDebInfo`, `MinSizeRel` or `Debug`), otherwise the project will be built with default configuration (`Debug`). +**Note for OSMesa:** copy `libglapi.dll`, `libGLESv2.dll`, and `osmesa.dll` from `dependencies\maplibre-native\platform\windows\vendor\mesa3d\` next to your application's executable. From 5cb178ed26d3265edbaf759b38fc8a84e258b3b9 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Fri, 1 May 2026 00:00:56 -0400 Subject: [PATCH 38/63] README: restore upstream repo URL for clone command --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4c6b388..eacb46e 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ You can check other examples at the [Examples](https://github.com/tdcosta100/Map ### Downloading sources ```powershell -git clone -j8 https://github.com/andrewpavlov/MaplibreNative.NET-ac.git +git clone -j8 https://github.com/tdcosta100/MaplibreNative.NET.git cd MaplibreNative.NET-ac git submodule update --init dependencies/maplibre-native ``` From 03137d144574ed1baded71b807bcddf3d750dfa0 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Fri, 1 May 2026 00:02:14 -0400 Subject: [PATCH 39/63] README: remove vector-tile remote workaround (local issue only) --- README.md | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/README.md b/README.md index eacb46e..77f5533 100644 --- a/README.md +++ b/README.md @@ -86,20 +86,7 @@ You can check other examples at the [Examples](https://github.com/tdcosta100/Map ```powershell git clone -j8 https://github.com/tdcosta100/MaplibreNative.NET.git cd MaplibreNative.NET-ac -git submodule update --init dependencies/maplibre-native -``` - -Then initialize the nested vendor submodules. One submodule (`vendor/vector-tile`) may need its remote corrected manually before the full recursive init succeeds: -```powershell -# Fix the vector-tile remote (only needed once) -Set-Location dependencies/maplibre-native/vendor/vector-tile -git remote set-url origin https://github.com/maplibre/mvt-cpp.git -git fetch origin -git checkout 8ad3bf0b78bf9cb0dba0abaccec0c180e1c27ab3 -Set-Location ../../../.. - -# Initialize all remaining nested submodules git submodule update --init --recursive dependencies/maplibre-native ``` From c2ce04b08f97b7199d9660bda36dc31fa3aa105c Mon Sep 17 00:00:00 2001 From: acalcutt Date: Fri, 1 May 2026 00:05:10 -0400 Subject: [PATCH 40/63] README: use --config core.longpaths=true --recurse-submodules in clone; note no --depth 1 --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 77f5533..916a9be 100644 --- a/README.md +++ b/README.md @@ -84,12 +84,12 @@ You can check other examples at the [Examples](https://github.com/tdcosta100/Map ### Downloading sources ```powershell -git clone -j8 https://github.com/tdcosta100/MaplibreNative.NET.git -cd MaplibreNative.NET-ac - -git submodule update --init --recursive dependencies/maplibre-native +git clone --config core.longpaths=true --recurse-submodules -j8 https://github.com/tdcosta100/MaplibreNative.NET.git +cd MaplibreNative.NET ``` +> **Note:** `--depth 1` is intentionally omitted. The `maplibre-native` submodule is pinned to a specific commit that may not be at a branch tip, which can cause shallow clones to fail. + ### Installing vendor packages (vcpkg) This step downloads and builds all native dependencies via vcpkg. It only needs to be done once (results are cached). From 0db6ba6b53569075bb788efde56df44cb97d126f Mon Sep 17 00:00:00 2001 From: acalcutt Date: Fri, 1 May 2026 00:06:06 -0400 Subject: [PATCH 41/63] README: remove depth 1 note --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 916a9be..e42456f 100644 --- a/README.md +++ b/README.md @@ -88,8 +88,6 @@ git clone --config core.longpaths=true --recurse-submodules -j8 https://github.c cd MaplibreNative.NET ``` -> **Note:** `--depth 1` is intentionally omitted. The `maplibre-native` submodule is pinned to a specific commit that may not be at a branch tip, which can cause shallow clones to fail. - ### Installing vendor packages (vcpkg) This step downloads and builds all native dependencies via vcpkg. It only needs to be done once (results are cached). From 3c0fe3526aa161300f2e7f7685eea7b440bf265e Mon Sep 17 00:00:00 2001 From: acalcutt Date: Fri, 1 May 2026 00:08:11 -0400 Subject: [PATCH 42/63] Add Windows CI workflow for pull requests --- .github/workflows/windows-ci.yml | 91 ++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 .github/workflows/windows-ci.yml diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml new file mode 100644 index 0000000..ee5ae6b --- /dev/null +++ b/.github/workflows/windows-ci.yml @@ -0,0 +1,91 @@ +name: Windows CI + +on: + pull_request: + branches: + - main + - master + +jobs: + build: + name: Build Windows ${{ matrix.arch }} ${{ matrix.dotnet }} + runs-on: ${{ matrix.runs-on }} + strategy: + fail-fast: false + matrix: + dotnet: [net8.0, net9.0] + arch: [x64, arm64] + include: + - arch: x64 + runs-on: windows-latest + vcpkg-triplet: x64-windows + msvc-arch: x64 + vs-arch: x64 + - arch: arm64 + runs-on: windows-11-arm + vcpkg-triplet: arm64-windows + msvc-arch: arm64 + vs-arch: ARM64 + + steps: + - name: Enable Git long paths + run: git config --global core.longpaths true + + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Set up MSVC developer tools + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: ${{ matrix.msvc-arch }} + + - name: Get vcpkg commit ID + id: vcpkg-id + shell: pwsh + run: | + $id = git -C dependencies/maplibre-native/platform/windows/vendor/vcpkg rev-parse HEAD + Add-Content -Path $env:GITHUB_OUTPUT -Value "commit_id=$id" + + - name: Cache vcpkg binary cache + uses: actions/cache@v4 + with: + path: dependencies/maplibre-native/platform/windows/vendor/vcpkg/archives + key: vcpkg-${{ matrix.vcpkg-triplet }}-${{ steps.vcpkg-id.outputs.commit_id }} + + - name: Bootstrap vcpkg + run: | + & "dependencies\maplibre-native\platform\windows\vendor\vcpkg\bootstrap-vcpkg.bat" -disableMetrics + + - name: Configure CMake + shell: pwsh + env: + VCPKG_BINARY_SOURCES: "clear;files,${{ github.workspace }}\\dependencies\\maplibre-native\\platform\\windows\\vendor\\vcpkg\\archives,readwrite" + run: | + $maxAttempts = 3 + for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) { + Write-Host "CMake configure attempt $attempt of $maxAttempts" + cmake -B build -G "Visual Studio 17 2022" -A ${{ matrix.vs-arch }} ` + -DMLN_WITH_OPENGL=ON ` + -DMLN_WITH_EGL=OFF ` + -DMLN_WITH_VULKAN=OFF ` + "-DCMAKE_CXX_FLAGS=/wd4267" ` + "-DMAPLIBRE_NET_DOTNET_VERSION=${{ matrix.dotnet }}" + if ($LASTEXITCODE -eq 0) { break } + if ($attempt -lt $maxAttempts) { + Write-Host "Attempt $attempt failed (exit $LASTEXITCODE), cleaning and retrying in 30s..." + Remove-Item -Recurse -Force build -ErrorAction SilentlyContinue + } else { + exit $LASTEXITCODE + } + } + + - name: Build + run: cmake --build build --target mbgl-dotnet --config Release --parallel + + - name: Upload build artifact + uses: actions/upload-artifact@v4 + with: + name: ci-build-${{ matrix.arch }}-${{ matrix.dotnet }} + path: build\Release\MaplibreNative.NET.dll + retention-days: 3 From ec7234308cc9499ef2b4437c2fb07a0a4bc0869d Mon Sep 17 00:00:00 2001 From: acalcutt Date: Fri, 1 May 2026 00:10:55 -0400 Subject: [PATCH 43/63] CI: also run on push to main/master --- .github/workflows/windows-ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index ee5ae6b..1e586e1 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -5,6 +5,10 @@ on: branches: - main - master + push: + branches: + - main + - master jobs: build: From 2d214b2e757f248fa51f50b7ec8db7e0b8ba749c Mon Sep 17 00:00:00 2001 From: acalcutt Date: Fri, 1 May 2026 09:53:43 -0400 Subject: [PATCH 44/63] Add full Layer and Source type wrapping - Add Layer base ref class (Id, SourceId, SourceLayer, Visible, MinZoom, MaxZoom, Type) - Add concrete layer wrappers: FillLayer, LineLayer, CircleLayer, SymbolLayer, RasterLayer, BackgroundLayer, HeatmapLayer, HillshadeLayer, FillExtrusionLayer, ColorReliefLayer (PR #3965) - Add Source base ref class (Id, Type, Attribution, IsVolatile) - Add concrete source wrappers: GeoJSONSource, VectorSource, RasterSource, RasterDEMSource, ImageSource - Add Style::AddXxxLayer / AddXxxSource factory methods returning typed wrappers - Add Style::GetLayer, GetLayers, GetSource, GetSources for retrieval - Update CMakeLists.txt with new cpp files - Update Console example to demo programmatic style API - Update README with Programmatic style API section --- CMakeLists.txt | 4 + Examples/Console/Example.Console/Program.cs | 52 ++ README.md | 152 +++++ include/Layer.h | 73 +++ include/Layers.h | 527 +++++++++++++++ include/Source.h | 44 ++ include/Sources.h | 146 +++++ include/Style.h | 91 ++- src/Layer.cpp | 77 +++ src/Layers.cpp | 673 ++++++++++++++++++++ src/Source.cpp | 48 ++ src/Sources.cpp | 166 +++++ src/Style.cpp | 254 ++++++++ 13 files changed, 2293 insertions(+), 14 deletions(-) create mode 100644 include/Layer.h create mode 100644 include/Layers.h create mode 100644 include/Source.h create mode 100644 include/Sources.h create mode 100644 src/Layer.cpp create mode 100644 src/Layers.cpp create mode 100644 src/Source.cpp create mode 100644 src/Sources.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 48f4beb..5eac5b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -93,6 +93,10 @@ target_sources( ${PROJECT_SOURCE_DIR}/src/ShaderRegistry.cpp ${PROJECT_SOURCE_DIR}/src/Size.cpp ${PROJECT_SOURCE_DIR}/src/Style.cpp + ${PROJECT_SOURCE_DIR}/src/Layer.cpp + ${PROJECT_SOURCE_DIR}/src/Layers.cpp + ${PROJECT_SOURCE_DIR}/src/Source.cpp + ${PROJECT_SOURCE_DIR}/src/Sources.cpp ${PROJECT_SOURCE_DIR}/src/TileCoordinate.cpp ${PROJECT_SOURCE_DIR}/src/TransformState.cpp ${PROJECT_SOURCE_DIR}/src/UnassociatedImage.cpp diff --git a/Examples/Console/Example.Console/Program.cs b/Examples/Console/Example.Console/Program.cs index ab5c104..cfe3b93 100644 --- a/Examples/Console/Example.Console/Program.cs +++ b/Examples/Console/Example.Console/Program.cs @@ -18,6 +18,12 @@ static void Main(string[] args) using var map = new Map(frontend, new MapObserver(), new MapOptions().WithSize(size).WithMapMode(MapMode.Static)); map.Style.LoadURL("https://raw.githubusercontent.com/maplibre/demotiles/gh-pages/style.json"); + // ---------------------------------------------------------------- + // Demonstrate the programmatic style API — add a GeoJSON source + // with a circle layer on top of the base style. + // ---------------------------------------------------------------- + DemoProgrammaticStyleApi(map.Style); + byte[]? imageData = null; map.RenderStill(new CameraOptions().WithZoom(0), MapDebugOptions.NoDebug, ex => @@ -54,5 +60,51 @@ static void Main(string[] args) } } } + + /// + /// Demonstrates the programmatic layer and source API: + /// - adds an inline GeoJSON source with a few cities + /// - adds a circle layer that renders those points + /// - inspects existing layers and prints a summary + /// + static void DemoProgrammaticStyleApi(Style style) + { + // Add an inline GeoJSON source containing a handful of cities. + var citiesSource = style.AddGeoJsonSource("demo-cities"); + citiesSource.SetData(""" + { + "type": "FeatureCollection", + "features": [ + { "type": "Feature", "geometry": { "type": "Point", "coordinates": [-87.63, 41.85] }, "properties": { "name": "Chicago" } }, + { "type": "Feature", "geometry": { "type": "Point", "coordinates": [-73.97, 40.66] }, "properties": { "name": "New York" } }, + { "type": "Feature", "geometry": { "type": "Point", "coordinates": [-118.25, 34.05] }, "properties": { "name": "Los Angeles" } }, + { "type": "Feature", "geometry": { "type": "Point", "coordinates": [-0.13, 51.50] }, "properties": { "name": "London" } }, + { "type": "Feature", "geometry": { "type": "Point", "coordinates": [2.35, 48.85] }, "properties": { "name": "Paris" } }, + { "type": "Feature", "geometry": { "type": "Point", "coordinates": [139.69, 35.69] }, "properties": { "name": "Tokyo" } } + ] + } + """); + + // Add a circle layer that renders the city points. + var circleLayer = style.AddCircleLayer("demo-city-circles", "demo-cities"); + circleLayer.Color = "#ff4444"; + circleLayer.Radius = 10f; + circleLayer.Opacity = 0.9f; + circleLayer.StrokeColor = "#ffffff"; + circleLayer.StrokeWidth = 2f; + + // Print a summary of all layers in paint order. + System.Console.WriteLine("--- Layers in style ---"); + foreach (var layer in style.GetLayers()) + { + System.Console.WriteLine($" [{layer.Type,-18}] {layer.Id}"); + } + + // Show that we can retrieve a typed layer back. + if (style.GetLayer("demo-city-circles") is CircleLayer cl) + { + System.Console.WriteLine($"\ndemo-city-circles: color={cl.Color} radius={cl.Radius} visible={cl.Visible}"); + } + } } } \ No newline at end of file diff --git a/README.md b/README.md index e42456f..f5315d5 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,158 @@ You can check other examples at the [Examples](https://github.com/tdcosta100/Map **Attention:** the examples reference `MaplibreNative.NET.dll` at `build\Release`. Build MaplibreNative.NET with `Release` configuration before building the examples (see *Building the sources* below). +## Programmatic style API + +After loading a style, you can add or modify sources and layers at runtime using the typed style API. + +### Adding sources + +```cs +// GeoJSON source from a URL +var geojsonSource = map.Style.AddGeoJsonSourceFromUrl("my-geojson", "https://example.com/data.geojson"); + +// GeoJSON source with inline data +var inlineSource = map.Style.AddGeoJsonSource("my-inline"); +inlineSource.SetData(""" + { + "type": "FeatureCollection", + "features": [{ + "type": "Feature", + "geometry": { "type": "Point", "coordinates": [0, 0] } + }] + } + """); + +// Vector tile source +var vectorSource = map.Style.AddVectorSource("my-vector", "https://example.com/tiles.json"); + +// Raster tile source (512 px tiles) +var rasterSource = map.Style.AddRasterSource("my-raster", "https://example.com/raster.json", 512); + +// Raster-DEM elevation source +var demSource = map.Style.AddRasterDemSource("my-dem", "https://example.com/dem.json", 512); + +// Image source (four geographic corners: TL, TR, BR, BL) +var imageSource = map.Style.AddImageSource("my-image", "https://example.com/overlay.png", +[ + new LatLng(46.0, -76.0), // top-left + new LatLng(46.0, -72.0), // top-right + new LatLng(44.0, -72.0), // bottom-right + new LatLng(44.0, -76.0), // bottom-left +]); +``` + +### Adding layers + +Each `AddXxxLayer` call returns a typed wrapper whose properties you can set immediately: + +```cs +// Fill layer (polygon fill) +var fillLayer = map.Style.AddFillLayer("my-fill", "my-vector"); +fillLayer.SourceLayer = "water"; +fillLayer.Color = "#4488ff"; +fillLayer.Opacity = 0.6f; + +// Line layer +var lineLayer = map.Style.AddLineLayer("my-line", "my-vector"); +lineLayer.SourceLayer = "roads"; +lineLayer.Color = "#ff6600"; +lineLayer.Width = 3f; + +// Circle layer +var circleLayer = map.Style.AddCircleLayer("my-circles", "my-geojson"); +circleLayer.Color = "#ff0000"; +circleLayer.Radius = 8f; +circleLayer.Opacity = 0.9f; +circleLayer.StrokeColor = "#ffffff"; +circleLayer.StrokeWidth = 1.5f; + +// Symbol layer +var symbolLayer = map.Style.AddSymbolLayer("my-labels", "my-vector"); +symbolLayer.SourceLayer = "places"; +symbolLayer.TextSize = 14f; +symbolLayer.TextColor = "#333333"; +symbolLayer.TextHaloColor = "#ffffff"; +symbolLayer.TextHaloWidth = 1.5f; + +// Raster layer +var rasterLayer = map.Style.AddRasterLayer("my-raster-layer", "my-raster"); +rasterLayer.Opacity = 0.8f; +rasterLayer.Saturation = -0.4f; + +// Background layer +var bgLayer = map.Style.AddBackgroundLayer("my-bg"); +bgLayer.Color = "#e8e0d8"; +bgLayer.Opacity = 1f; + +// Heatmap layer +var heatmap = map.Style.AddHeatmapLayer("my-heatmap", "my-geojson"); +heatmap.Radius = 20f; +heatmap.Intensity = 1.2f; +heatmap.Opacity = 0.85f; + +// Hillshade layer (requires a raster-DEM source) +var hillshade = map.Style.AddHillshadeLayer("my-hillshade", "my-dem"); +hillshade.Exaggeration = 0.6f; +hillshade.IlluminationDirection = 315f; + +// Fill extrusion layer (3-D buildings) +var extrusion = map.Style.AddFillExtrusionLayer("my-buildings", "my-vector"); +extrusion.SourceLayer = "buildings"; +extrusion.Color = "#aaaaaa"; +extrusion.Height = 50f; +extrusion.Base = 0f; +extrusion.VerticalGradient = true; + +// Color relief layer (requires a raster-DEM source — maplibre-native PR #3965) +var colorRelief = map.Style.AddColorReliefLayer("my-relief", "my-dem"); +colorRelief.Opacity = 0.8f; +``` + +### Inspecting existing layers and sources + +```cs +// Get a strongly-typed layer back from the style +if (map.Style.GetLayer("my-fill") is FillLayer fill) +{ + System.Console.WriteLine($"Fill color: {fill.Color}"); +} + +// Iterate all layers +foreach (var layer in map.Style.GetLayers()) +{ + System.Console.WriteLine($"{layer.Type,-18} {layer.Id} visible={layer.Visible}"); +} + +// Get a typed source +if (map.Style.GetSource("my-geojson") is GeoJSONSource gjs) +{ + gjs.SetData(newGeoJsonString); +} +``` + +### Base layer / source properties + +All layers share common properties inherited from `Layer`: + +| Property | Type | Description | +|---|---|---| +| `Id` | `string` | Read-only identifier | +| `SourceId` | `string` | Source the layer reads from | +| `SourceLayer` | `string` | Vector tile layer name | +| `Visible` | `bool` | `true` = rendered, `false` = hidden | +| `MinZoom` / `MaxZoom` | `float` | Zoom range for rendering | +| `Type` | `string` | Type string (e.g. `"fill"`, `"line"`) | + +All sources share properties from `Source`: + +| Property | Type | Description | +|---|---|---| +| `Id` | `string` | Read-only identifier | +| `Type` | `string` | e.g. `"geojson"`, `"vector"`, `"raster"` | +| `Attribution` | `string` | Attribution text (if provided) | +| `IsVolatile` | `bool` | Whether tiles may be evicted from cache | + ## Building the sources ### Prerequisites diff --git a/include/Layer.h b/include/Layer.h new file mode 100644 index 0000000..489c6b3 --- /dev/null +++ b/include/Layer.h @@ -0,0 +1,73 @@ +#pragma once + +namespace mbgl { +namespace style { +class Layer; +} // namespace style +} // namespace mbgl + +namespace DOTNET_NAMESPACE +{ + /// + /// Non-owning wrapper for an mbgl::style::Layer. + /// Lifetime is tied to the Style that owns the native layer — do not hold + /// references after the Style is disposed. + /// + public ref class Layer + { + internal: + mbgl::style::Layer* _layer; // raw, non-owning + Layer(mbgl::style::Layer* layer); + + public: + /// Unique identifier for the layer. + property System::String^ Id { System::String^ get(); } + + /// + /// Source identifier this layer reads from. + /// Empty string for background layers. + /// + property System::String^ SourceId + { + System::String^ get(); + System::Void set(System::String^); + } + + /// + /// Source-layer name used when drawing from a vector tile source. + /// + property System::String^ SourceLayer + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Whether this layer is rendered (true = visible, false = none). + property bool Visible + { + bool get(); + System::Void set(bool); + } + + /// Minimum zoom level at which this layer is rendered. + property float MinZoom + { + float get(); + System::Void set(float); + } + + /// Maximum zoom level at which this layer is rendered. + property float MaxZoom + { + float get(); + System::Void set(float); + } + + /// + /// Layer type string as reported by the native library + /// (e.g. "fill", "line", "circle", "symbol", "raster", "background", + /// "heatmap", "hillshade", "fill-extrusion"). + /// + property System::String^ Type { System::String^ get(); } + }; +} diff --git a/include/Layers.h b/include/Layers.h new file mode 100644 index 0000000..42e0855 --- /dev/null +++ b/include/Layers.h @@ -0,0 +1,527 @@ +#pragma once +#include "Layer.h" + +// Forward-declare native layer types +namespace mbgl { +namespace style { +class FillLayer; +class LineLayer; +class CircleLayer; +class SymbolLayer; +class RasterLayer; +class BackgroundLayer; +class HeatmapLayer; +class HillshadeLayer; +class FillExtrusionLayer; +class ColorReliefLayer; +} // namespace style +} // namespace mbgl + +namespace DOTNET_NAMESPACE +{ + // ========================================================================= + // FillLayer + // ========================================================================= + + /// Wraps an mbgl::style::FillLayer. + public ref class FillLayer : public Layer + { + internal: + FillLayer(mbgl::style::FillLayer* layer); + + private: + mbgl::style::FillLayer* Impl(); + + public: + /// + /// Fill color as a CSS hex string (e.g. "#rrggbb"). + /// Returns the current constant value, or empty string for expressions. + /// + property System::String^ Color + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Fill opacity (0.0 – 1.0). + property float Opacity + { + float get(); + System::Void set(float); + } + + /// + /// Outline color as a CSS hex string. + /// Defaults to the fill color if not set. + /// + property System::String^ OutlineColor + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Whether to anti-alias the fill edges. + property bool Antialias + { + bool get(); + System::Void set(bool); + } + }; + + // ========================================================================= + // LineLayer + // ========================================================================= + + /// Wraps an mbgl::style::LineLayer. + public ref class LineLayer : public Layer + { + internal: + LineLayer(mbgl::style::LineLayer* layer); + + private: + mbgl::style::LineLayer* Impl(); + + public: + /// Stroke color as a CSS hex string. + property System::String^ Color + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Stroke opacity (0.0 – 1.0). + property float Opacity + { + float get(); + System::Void set(float); + } + + /// Stroke width in pixels. + property float Width + { + float get(); + System::Void set(float); + } + + /// Line blur applied in pixels. + property float Blur + { + float get(); + System::Void set(float); + } + + /// Gap width in pixels (draws a gap between two parallel lines). + property float GapWidth + { + float get(); + System::Void set(float); + } + + /// Offset in pixels (positive = right of direction, negative = left). + property float Offset + { + float get(); + System::Void set(float); + } + }; + + // ========================================================================= + // CircleLayer + // ========================================================================= + + /// Wraps an mbgl::style::CircleLayer. + public ref class CircleLayer : public Layer + { + internal: + CircleLayer(mbgl::style::CircleLayer* layer); + + private: + mbgl::style::CircleLayer* Impl(); + + public: + /// Circle fill color as a CSS hex string. + property System::String^ Color + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Circle opacity (0.0 – 1.0). + property float Opacity + { + float get(); + System::Void set(float); + } + + /// Circle radius in pixels. + property float Radius + { + float get(); + System::Void set(float); + } + + /// Stroke width in pixels. + property float StrokeWidth + { + float get(); + System::Void set(float); + } + + /// Stroke color as a CSS hex string. + property System::String^ StrokeColor + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Stroke opacity (0.0 – 1.0). + property float StrokeOpacity + { + float get(); + System::Void set(float); + } + + /// Blur applied to the circle (pixels). + property float Blur + { + float get(); + System::Void set(float); + } + }; + + // ========================================================================= + // SymbolLayer + // ========================================================================= + + /// Wraps an mbgl::style::SymbolLayer. + public ref class SymbolLayer : public Layer + { + internal: + SymbolLayer(mbgl::style::SymbolLayer* layer); + + private: + mbgl::style::SymbolLayer* Impl(); + + public: + /// Text color as a CSS hex string. + property System::String^ TextColor + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Text opacity (0.0 – 1.0). + property float TextOpacity + { + float get(); + System::Void set(float); + } + + /// Font size in pixels. + property float TextSize + { + float get(); + System::Void set(float); + } + + /// Halo color around text glyphs as a CSS hex string. + property System::String^ TextHaloColor + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Halo width in pixels. + property float TextHaloWidth + { + float get(); + System::Void set(float); + } + + /// Icon opacity (0.0 – 1.0). + property float IconOpacity + { + float get(); + System::Void set(float); + } + + /// Icon color (used for SDF icons) as a CSS hex string. + property System::String^ IconColor + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Icon halo color as a CSS hex string. + property System::String^ IconHaloColor + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Icon halo width in pixels. + property float IconHaloWidth + { + float get(); + System::Void set(float); + } + }; + + // ========================================================================= + // RasterLayer + // ========================================================================= + + /// Wraps an mbgl::style::RasterLayer. + public ref class RasterLayer : public Layer + { + internal: + RasterLayer(mbgl::style::RasterLayer* layer); + + private: + mbgl::style::RasterLayer* Impl(); + + public: + /// Raster opacity (0.0 – 1.0). + property float Opacity + { + float get(); + System::Void set(float); + } + + /// Minimum brightness (0.0 – 1.0). + property float BrightnessMin + { + float get(); + System::Void set(float); + } + + /// Maximum brightness (0.0 – 1.0). + property float BrightnessMax + { + float get(); + System::Void set(float); + } + + /// Contrast adjustment (-1.0 – 1.0). + property float Contrast + { + float get(); + System::Void set(float); + } + + /// Hue rotation in degrees (0 – 360). + property float HueRotate + { + float get(); + System::Void set(float); + } + + /// Saturation adjustment (-1.0 – 1.0). + property float Saturation + { + float get(); + System::Void set(float); + } + + /// Fade duration when a new tile is added (milliseconds). + property float FadeDuration + { + float get(); + System::Void set(float); + } + }; + + // ========================================================================= + // BackgroundLayer + // ========================================================================= + + /// Wraps an mbgl::style::BackgroundLayer (no source). + public ref class BackgroundLayer : public Layer + { + internal: + BackgroundLayer(mbgl::style::BackgroundLayer* layer); + + private: + mbgl::style::BackgroundLayer* Impl(); + + public: + /// Background fill color as a CSS hex string. + property System::String^ Color + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Background opacity (0.0 – 1.0). + property float Opacity + { + float get(); + System::Void set(float); + } + }; + + // ========================================================================= + // HeatmapLayer + // ========================================================================= + + /// Wraps an mbgl::style::HeatmapLayer. + public ref class HeatmapLayer : public Layer + { + internal: + HeatmapLayer(mbgl::style::HeatmapLayer* layer); + + private: + mbgl::style::HeatmapLayer* Impl(); + + public: + /// Global opacity of the heatmap (0.0 – 1.0). + property float Opacity + { + float get(); + System::Void set(float); + } + + /// Radius of influence of one heatmap point in pixels. + property float Radius + { + float get(); + System::Void set(float); + } + + /// Intensity multiplier that controls the rate of increase. + property float Intensity + { + float get(); + System::Void set(float); + } + + /// Contribution of each individual point to the heatmap. + property float Weight + { + float get(); + System::Void set(float); + } + }; + + // ========================================================================= + // HillshadeLayer + // ========================================================================= + + /// Wraps an mbgl::style::HillshadeLayer. + public ref class HillshadeLayer : public Layer + { + internal: + HillshadeLayer(mbgl::style::HillshadeLayer* layer); + + private: + mbgl::style::HillshadeLayer* Impl(); + + public: + /// Intensity of the hillshade (0.0 – 1.0). + property float Exaggeration + { + float get(); + System::Void set(float); + } + + /// Direction of the light source as an azimuth (0 – 360 degrees). + property float IlluminationDirection + { + float get(); + System::Void set(float); + } + + /// Shadow color as a CSS hex string. + property System::String^ ShadowColor + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Highlight color as a CSS hex string. + property System::String^ HighlightColor + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Accent color for vert faces as a CSS hex string. + property System::String^ AccentColor + { + System::String^ get(); + System::Void set(System::String^); + } + }; + + // ========================================================================= + // FillExtrusionLayer + // ========================================================================= + + /// Wraps an mbgl::style::FillExtrusionLayer. + public ref class FillExtrusionLayer : public Layer + { + internal: + FillExtrusionLayer(mbgl::style::FillExtrusionLayer* layer); + + private: + mbgl::style::FillExtrusionLayer* Impl(); + + public: + /// Extrusion color as a CSS hex string. + property System::String^ Color + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Extrusion opacity (0.0 – 1.0). + property float Opacity + { + float get(); + System::Void set(float); + } + + /// Height of the extrusion in meters. + property float Height + { + float get(); + System::Void set(float); + } + + /// Base of the extrusion in meters (for raising the bottom). + property float Base + { + float get(); + System::Void set(float); + } + + /// Whether to apply a gradient effect along the sides of the extrusion. + property bool VerticalGradient + { + bool get(); + System::Void set(bool); + } + }; + + // ========================================================================= + // ColorReliefLayer + // ========================================================================= + + /// Wraps an mbgl::style::ColorReliefLayer. + public ref class ColorReliefLayer : public Layer + { + internal: + ColorReliefLayer(mbgl::style::ColorReliefLayer* layer); + + private: + mbgl::style::ColorReliefLayer* Impl(); + + public: + /// Opacity of the color-relief layer (0.0 – 1.0). + property float Opacity + { + float get(); + System::Void set(float); + } + }; +} diff --git a/include/Source.h b/include/Source.h new file mode 100644 index 0000000..1ec82b5 --- /dev/null +++ b/include/Source.h @@ -0,0 +1,44 @@ +#pragma once + +namespace mbgl { +namespace style { +class Source; +} // namespace style +} // namespace mbgl + +namespace DOTNET_NAMESPACE +{ + /// + /// Non-owning wrapper for an mbgl::style::Source. + /// Lifetime is tied to the Style that owns the native source — do not hold + /// references after the Style is disposed. + /// + public ref class Source + { + internal: + mbgl::style::Source* _source; // raw, non-owning + Source(mbgl::style::Source* source); + + public: + /// Unique identifier for the source. + property System::String^ Id { System::String^ get(); } + + /// + /// Source type string as reported by the native library + /// (e.g. "geojson", "vector", "raster", "raster-dem", "image"). + /// + property System::String^ Type { System::String^ get(); } + + /// Optional attribution text for this source. + property System::String^ Attribution { System::String^ get(); } + + /// + /// Whether the source's data is volatile (can be evicted from the cache). + /// + property bool IsVolatile + { + bool get(); + System::Void set(bool); + } + }; +} diff --git a/include/Sources.h b/include/Sources.h new file mode 100644 index 0000000..8cde043 --- /dev/null +++ b/include/Sources.h @@ -0,0 +1,146 @@ +#pragma once +#include "Source.h" +#include "LatLng.h" + +// Forward-declare native source types +namespace mbgl { +namespace style { +class GeoJSONSource; +class VectorSource; +class RasterSource; +class RasterDEMSource; +class ImageSource; +} // namespace style +} // namespace mbgl + +namespace DOTNET_NAMESPACE +{ + // ========================================================================= + // GeoJSONSource + // ========================================================================= + + /// Wraps an mbgl::style::GeoJSONSource. + public ref class GeoJSONSource : public Source + { + internal: + GeoJSONSource(mbgl::style::GeoJSONSource* source); + + private: + mbgl::style::GeoJSONSource* Impl(); + + public: + /// + /// Update the source to load data from the specified URL. + /// The URL may be a remote https:// URL or a local file:// URL. + /// + /// URL pointing to a GeoJSON document. + System::Void SetUrl(System::String^ url); + + /// + /// Update the source with inline GeoJSON data provided as a string. + /// + /// A valid GeoJSON string. + System::Void SetData(System::String^ geoJson); + }; + + // ========================================================================= + // VectorSource + // ========================================================================= + + /// Wraps an mbgl::style::VectorSource. + public ref class VectorSource : public Source + { + internal: + VectorSource(mbgl::style::VectorSource* source); + + private: + mbgl::style::VectorSource* Impl(); + + public: + /// + /// The TileJSON or Mapbox-style URL for the vector tile set, + /// or empty string if the source was constructed from an inline Tileset. + /// + property System::String^ Url { System::String^ get(); } + }; + + // ========================================================================= + // RasterSource + // ========================================================================= + + /// Wraps an mbgl::style::RasterSource. + public ref class RasterSource : public Source + { + internal: + RasterSource(mbgl::style::RasterSource* source); + + private: + mbgl::style::RasterSource* Impl(); + + public: + /// + /// The TileJSON or Mapbox-style URL for the raster tile set, + /// or empty string if the source was constructed from an inline Tileset. + /// + property System::String^ Url { System::String^ get(); } + + /// Tile size in pixels (default 512). + property System::UInt16 TileSize { System::UInt16 get(); } + }; + + // ========================================================================= + // RasterDEMSource + // ========================================================================= + + /// Wraps an mbgl::style::RasterDEMSource (elevation / terrain). + public ref class RasterDEMSource : public Source + { + internal: + RasterDEMSource(mbgl::style::RasterDEMSource* source); + + private: + mbgl::style::RasterDEMSource* Impl(); + + public: + /// + /// The TileJSON URL for the raster-DEM tile set, + /// or empty string if the source was constructed from an inline Tileset. + /// + property System::String^ Url { System::String^ get(); } + + /// Tile size in pixels (default 512). + property System::UInt16 TileSize { System::UInt16 get(); } + }; + + // ========================================================================= + // ImageSource + // ========================================================================= + + /// Wraps an mbgl::style::ImageSource. + public ref class ImageSource : public Source + { + internal: + ImageSource(mbgl::style::ImageSource* source); + + private: + mbgl::style::ImageSource* Impl(); + + public: + /// URL of the image to display. + property System::String^ Url + { + System::String^ get(); + System::Void set(System::String^); + } + + /// + /// The four geographic corners of the image, in order: + /// top-left, top-right, bottom-right, bottom-left. + /// + property array^ Coordinates + { + array^ get(); + System::Void set(array^); + } + }; +} diff --git a/include/Style.h b/include/Style.h index 55d5b05..e32d548 100644 --- a/include/Style.h +++ b/include/Style.h @@ -1,5 +1,10 @@ #pragma once #include "NativeWrapper.h" +#include "Layer.h" +#include "Layers.h" +#include "Source.h" +#include "Sources.h" +#include "LatLng.h" namespace mbgl { @@ -27,31 +32,89 @@ namespace DOTNET_NAMESPACE System::String^ GetURL(); System::String^ GetName(); - // GeoJSON sources + // ------------------------------------------------------------------------- + // Layer retrieval + // ------------------------------------------------------------------------- + + /// + /// Returns the layer with the given id, cast to its concrete type, or + /// nullptr if no layer with that id exists. + /// + Layer^ GetLayer(System::String^ layerId); + + /// Returns all layers in paint order (bottom to top). + System::Collections::Generic::List^ GetLayers(); + + // ------------------------------------------------------------------------- + // Source retrieval + // ------------------------------------------------------------------------- + + /// + /// Returns the source with the given id, cast to its concrete type, or + /// nullptr if no source with that id exists. + /// + Source^ GetSource(System::String^ sourceId); + + /// Returns all sources. + System::Collections::Generic::List^ GetSources(); + + // ------------------------------------------------------------------------- + // Add layers + // ------------------------------------------------------------------------- + + FillLayer^ AddFillLayer(System::String^ layerId, System::String^ sourceId); + LineLayer^ AddLineLayer(System::String^ layerId, System::String^ sourceId); + CircleLayer^ AddCircleLayer(System::String^ layerId, System::String^ sourceId); + SymbolLayer^ AddSymbolLayer(System::String^ layerId, System::String^ sourceId); + RasterLayer^ AddRasterLayer(System::String^ layerId, System::String^ sourceId); + BackgroundLayer^ AddBackgroundLayer(System::String^ layerId); + HeatmapLayer^ AddHeatmapLayer(System::String^ layerId, System::String^ sourceId); + HillshadeLayer^ AddHillshadeLayer(System::String^ layerId, System::String^ sourceId); + FillExtrusionLayer^ AddFillExtrusionLayer(System::String^ layerId, System::String^ sourceId); + ColorReliefLayer^ AddColorReliefLayer(System::String^ layerId, System::String^ sourceId); + + /// Remove a layer by id. Returns true if removed. + System::Boolean RemoveLayer(System::String^ layerId); + /// Returns true if a layer with the given id exists. + System::Boolean HasLayer(System::String^ layerId); + + // ------------------------------------------------------------------------- + // Add sources + // ------------------------------------------------------------------------- + + GeoJSONSource^ AddGeoJsonSource(System::String^ sourceId); + /// Add a GeoJSON source that immediately loads from a URL. + GeoJSONSource^ AddGeoJsonSourceFromUrl(System::String^ sourceId, System::String^ url); + VectorSource^ AddVectorSource(System::String^ sourceId, System::String^ url); + RasterSource^ AddRasterSource(System::String^ sourceId, System::String^ url, System::UInt16 tileSize); + RasterDEMSource^ AddRasterDemSource(System::String^ sourceId, System::String^ url, System::UInt16 tileSize); + ImageSource^ AddImageSource(System::String^ sourceId, System::String^ url, array^ coordinates); + + /// Remove a source by id. Returns true if removed. + System::Boolean RemoveSource(System::String^ sourceId); + /// Returns true if a source with the given id exists. + System::Boolean HasSource(System::String^ sourceId); + + // ------------------------------------------------------------------------- + // Legacy helpers (kept for backward compatibility) + // ------------------------------------------------------------------------- + /// Add a GeoJSON source that fetches its data from a URL. + [System::Obsolete("Use AddGeoJsonSourceFromUrl instead.")] System::Void AddGeoJsonSource(System::String^ sourceId, System::String^ url); - /// Update the URL of an existing GeoJSON source (no-op if source not found or wrong type). + /// Update the URL of an existing GeoJSON source. System::Void SetGeoJsonSourceUrl(System::String^ sourceId, System::String^ url); /// Set the data of an existing GeoJSON source from an inline GeoJSON string. System::Void SetGeoJsonSourceData(System::String^ sourceId, System::String^ geojsonString); - /// Remove a source by id. Returns true if removed. - System::Boolean RemoveSource(System::String^ sourceId); - /// Returns true if a source with the given id exists. - System::Boolean HasSource(System::String^ sourceId); - // Circle layers /// - /// Add a circle layer above all existing layers. - /// color: CSS hex string e.g. "#ff0000". - /// filter: optional JSON expression string e.g. "["==", ["get","type"], "wifi"]" or empty/null. + /// Add a circle layer above all existing layers (legacy overload with inline style params). + /// color: CSS hex string e.g. "#ff0000". filterJson is reserved and currently ignored. /// + [System::Obsolete("Use AddCircleLayer and configure properties on the returned CircleLayer.")] System::Void AddCircleLayer(System::String^ layerId, System::String^ sourceId, System::String^ color, float radius, float opacity, System::String^ filterJson); - /// Remove a layer by id. Returns true if removed. - System::Boolean RemoveLayer(System::String^ layerId); - /// Returns true if a layer with the given id exists. - System::Boolean HasLayer(System::String^ layerId); internal: Style(NativePointerHolder^ nativePointerHolder); diff --git a/src/Layer.cpp b/src/Layer.cpp new file mode 100644 index 0000000..91bf6fc --- /dev/null +++ b/src/Layer.cpp @@ -0,0 +1,77 @@ +#include "pch.h" +#include "Layer.h" + +#include +#include +#include + +namespace DOTNET_NAMESPACE +{ + Layer::Layer(mbgl::style::Layer* layer) + : _layer(layer) + { + } + + System::String^ Layer::Id::get() + { + return msclr::interop::marshal_as(_layer->getID()); + } + + System::String^ Layer::SourceId::get() + { + return msclr::interop::marshal_as(_layer->getSourceID()); + } + + System::Void Layer::SourceId::set(System::String^ value) + { + _layer->setSourceID(msclr::interop::marshal_as(value)); + } + + System::String^ Layer::SourceLayer::get() + { + return msclr::interop::marshal_as(_layer->getSourceLayer()); + } + + System::Void Layer::SourceLayer::set(System::String^ value) + { + _layer->setSourceLayer(msclr::interop::marshal_as(value)); + } + + bool Layer::Visible::get() + { + return _layer->getVisibility() == mbgl::style::VisibilityType::Visible; + } + + System::Void Layer::Visible::set(bool value) + { + _layer->setVisibility(value + ? mbgl::style::VisibilityType::Visible + : mbgl::style::VisibilityType::None); + } + + float Layer::MinZoom::get() + { + return _layer->getMinZoom(); + } + + System::Void Layer::MinZoom::set(float value) + { + _layer->setMinZoom(value); + } + + float Layer::MaxZoom::get() + { + return _layer->getMaxZoom(); + } + + System::Void Layer::MaxZoom::set(float value) + { + _layer->setMaxZoom(value); + } + + System::String^ Layer::Type::get() + { + const auto* info = _layer->getTypeInfo(); + return info ? msclr::interop::marshal_as(std::string(info->type)) : System::String::Empty; + } +} diff --git a/src/Layers.cpp b/src/Layers.cpp new file mode 100644 index 0000000..f3ca738 --- /dev/null +++ b/src/Layers.cpp @@ -0,0 +1,673 @@ +#include "pch.h" +#include "Layers.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + // ------------------------------------------------------------------------- + // Helper: convert mbgl::Color -> System::String^ (CSS rgba string) + // ------------------------------------------------------------------------- + inline System::String^ ColorToString(const mbgl::Color& c) + { + return msclr::interop::marshal_as(c.stringify()); + } + + // ------------------------------------------------------------------------- + // Helper: parse a CSS color string into mbgl::Color; returns black on failure + // ------------------------------------------------------------------------- + inline mbgl::Color ParseColor(System::String^ s) + { + auto parsed = mbgl::Color::parse(msclr::interop::marshal_as(s)); + return parsed ? *parsed : mbgl::Color::black(); + } + + // ------------------------------------------------------------------------- + // Helper: extract constant float from PropertyValue; default on expression + // ------------------------------------------------------------------------- + inline float GetFloat(const mbgl::style::PropertyValue& pv, float def = 0.0f) + { + return pv.isConstant() ? pv.asConstant() : def; + } + + // ------------------------------------------------------------------------- + // Helper: extract constant bool from PropertyValue; default on expression + // ------------------------------------------------------------------------- + inline bool GetBool(const mbgl::style::PropertyValue& pv, bool def = false) + { + return pv.isConstant() ? pv.asConstant() : def; + } + + // ------------------------------------------------------------------------- + // Helper: extract constant Color from PropertyValue; default on expression + // ------------------------------------------------------------------------- + inline System::String^ GetColor(const mbgl::style::PropertyValue& pv, + System::String^ def) + { + return pv.isConstant() ? ColorToString(pv.asConstant()) : def; + } +} + +namespace DOTNET_NAMESPACE +{ + // ========================================================================= + // FillLayer + // ========================================================================= + + FillLayer::FillLayer(mbgl::style::FillLayer* layer) : Layer(layer) {} + + mbgl::style::FillLayer* FillLayer::Impl() + { + return static_cast(_layer); + } + + System::String^ FillLayer::Color::get() + { + return GetColor(Impl()->getFillColor(), "#000000"); + } + + System::Void FillLayer::Color::set(System::String^ value) + { + Impl()->setFillColor(mbgl::style::PropertyValue(ParseColor(value))); + } + + float FillLayer::Opacity::get() + { + return GetFloat(Impl()->getFillOpacity(), 1.0f); + } + + System::Void FillLayer::Opacity::set(float value) + { + Impl()->setFillOpacity(mbgl::style::PropertyValue(value)); + } + + System::String^ FillLayer::OutlineColor::get() + { + return GetColor(Impl()->getFillOutlineColor(), ""); + } + + System::Void FillLayer::OutlineColor::set(System::String^ value) + { + Impl()->setFillOutlineColor(mbgl::style::PropertyValue(ParseColor(value))); + } + + bool FillLayer::Antialias::get() + { + return GetBool(Impl()->getFillAntialias(), true); + } + + System::Void FillLayer::Antialias::set(bool value) + { + Impl()->setFillAntialias(mbgl::style::PropertyValue(value)); + } + + // ========================================================================= + // LineLayer + // ========================================================================= + + LineLayer::LineLayer(mbgl::style::LineLayer* layer) : Layer(layer) {} + + mbgl::style::LineLayer* LineLayer::Impl() + { + return static_cast(_layer); + } + + System::String^ LineLayer::Color::get() + { + return GetColor(Impl()->getLineColor(), "#000000"); + } + + System::Void LineLayer::Color::set(System::String^ value) + { + Impl()->setLineColor(mbgl::style::PropertyValue(ParseColor(value))); + } + + float LineLayer::Opacity::get() + { + return GetFloat(Impl()->getLineOpacity(), 1.0f); + } + + System::Void LineLayer::Opacity::set(float value) + { + Impl()->setLineOpacity(mbgl::style::PropertyValue(value)); + } + + float LineLayer::Width::get() + { + return GetFloat(Impl()->getLineWidth(), 1.0f); + } + + System::Void LineLayer::Width::set(float value) + { + Impl()->setLineWidth(mbgl::style::PropertyValue(value)); + } + + float LineLayer::Blur::get() + { + return GetFloat(Impl()->getLineBlur(), 0.0f); + } + + System::Void LineLayer::Blur::set(float value) + { + Impl()->setLineBlur(mbgl::style::PropertyValue(value)); + } + + float LineLayer::GapWidth::get() + { + return GetFloat(Impl()->getLineGapWidth(), 0.0f); + } + + System::Void LineLayer::GapWidth::set(float value) + { + Impl()->setLineGapWidth(mbgl::style::PropertyValue(value)); + } + + float LineLayer::Offset::get() + { + return GetFloat(Impl()->getLineOffset(), 0.0f); + } + + System::Void LineLayer::Offset::set(float value) + { + Impl()->setLineOffset(mbgl::style::PropertyValue(value)); + } + + // ========================================================================= + // CircleLayer + // ========================================================================= + + CircleLayer::CircleLayer(mbgl::style::CircleLayer* layer) : Layer(layer) {} + + mbgl::style::CircleLayer* CircleLayer::Impl() + { + return static_cast(_layer); + } + + System::String^ CircleLayer::Color::get() + { + return GetColor(Impl()->getCircleColor(), "#000000"); + } + + System::Void CircleLayer::Color::set(System::String^ value) + { + Impl()->setCircleColor(mbgl::style::PropertyValue(ParseColor(value))); + } + + float CircleLayer::Opacity::get() + { + return GetFloat(Impl()->getCircleOpacity(), 1.0f); + } + + System::Void CircleLayer::Opacity::set(float value) + { + Impl()->setCircleOpacity(mbgl::style::PropertyValue(value)); + } + + float CircleLayer::Radius::get() + { + return GetFloat(Impl()->getCircleRadius(), 5.0f); + } + + System::Void CircleLayer::Radius::set(float value) + { + Impl()->setCircleRadius(mbgl::style::PropertyValue(value)); + } + + float CircleLayer::StrokeWidth::get() + { + return GetFloat(Impl()->getCircleStrokeWidth(), 0.0f); + } + + System::Void CircleLayer::StrokeWidth::set(float value) + { + Impl()->setCircleStrokeWidth(mbgl::style::PropertyValue(value)); + } + + System::String^ CircleLayer::StrokeColor::get() + { + return GetColor(Impl()->getCircleStrokeColor(), "#000000"); + } + + System::Void CircleLayer::StrokeColor::set(System::String^ value) + { + Impl()->setCircleStrokeColor(mbgl::style::PropertyValue(ParseColor(value))); + } + + float CircleLayer::StrokeOpacity::get() + { + return GetFloat(Impl()->getCircleStrokeOpacity(), 1.0f); + } + + System::Void CircleLayer::StrokeOpacity::set(float value) + { + Impl()->setCircleStrokeOpacity(mbgl::style::PropertyValue(value)); + } + + float CircleLayer::Blur::get() + { + return GetFloat(Impl()->getCircleBlur(), 0.0f); + } + + System::Void CircleLayer::Blur::set(float value) + { + Impl()->setCircleBlur(mbgl::style::PropertyValue(value)); + } + + // ========================================================================= + // SymbolLayer + // ========================================================================= + + SymbolLayer::SymbolLayer(mbgl::style::SymbolLayer* layer) : Layer(layer) {} + + mbgl::style::SymbolLayer* SymbolLayer::Impl() + { + return static_cast(_layer); + } + + System::String^ SymbolLayer::TextColor::get() + { + return GetColor(Impl()->getTextColor(), "#000000"); + } + + System::Void SymbolLayer::TextColor::set(System::String^ value) + { + Impl()->setTextColor(mbgl::style::PropertyValue(ParseColor(value))); + } + + float SymbolLayer::TextOpacity::get() + { + return GetFloat(Impl()->getTextOpacity(), 1.0f); + } + + System::Void SymbolLayer::TextOpacity::set(float value) + { + Impl()->setTextOpacity(mbgl::style::PropertyValue(value)); + } + + float SymbolLayer::TextSize::get() + { + return GetFloat(Impl()->getTextSize(), 16.0f); + } + + System::Void SymbolLayer::TextSize::set(float value) + { + Impl()->setTextSize(mbgl::style::PropertyValue(value)); + } + + System::String^ SymbolLayer::TextHaloColor::get() + { + return GetColor(Impl()->getTextHaloColor(), "rgba(0,0,0,0)"); + } + + System::Void SymbolLayer::TextHaloColor::set(System::String^ value) + { + Impl()->setTextHaloColor(mbgl::style::PropertyValue(ParseColor(value))); + } + + float SymbolLayer::TextHaloWidth::get() + { + return GetFloat(Impl()->getTextHaloWidth(), 0.0f); + } + + System::Void SymbolLayer::TextHaloWidth::set(float value) + { + Impl()->setTextHaloWidth(mbgl::style::PropertyValue(value)); + } + + float SymbolLayer::IconOpacity::get() + { + return GetFloat(Impl()->getIconOpacity(), 1.0f); + } + + System::Void SymbolLayer::IconOpacity::set(float value) + { + Impl()->setIconOpacity(mbgl::style::PropertyValue(value)); + } + + System::String^ SymbolLayer::IconColor::get() + { + return GetColor(Impl()->getIconColor(), "#000000"); + } + + System::Void SymbolLayer::IconColor::set(System::String^ value) + { + Impl()->setIconColor(mbgl::style::PropertyValue(ParseColor(value))); + } + + System::String^ SymbolLayer::IconHaloColor::get() + { + return GetColor(Impl()->getIconHaloColor(), "rgba(0,0,0,0)"); + } + + System::Void SymbolLayer::IconHaloColor::set(System::String^ value) + { + Impl()->setIconHaloColor(mbgl::style::PropertyValue(ParseColor(value))); + } + + float SymbolLayer::IconHaloWidth::get() + { + return GetFloat(Impl()->getIconHaloWidth(), 0.0f); + } + + System::Void SymbolLayer::IconHaloWidth::set(float value) + { + Impl()->setIconHaloWidth(mbgl::style::PropertyValue(value)); + } + + // ========================================================================= + // RasterLayer + // ========================================================================= + + RasterLayer::RasterLayer(mbgl::style::RasterLayer* layer) : Layer(layer) {} + + mbgl::style::RasterLayer* RasterLayer::Impl() + { + return static_cast(_layer); + } + + float RasterLayer::Opacity::get() + { + return GetFloat(Impl()->getRasterOpacity(), 1.0f); + } + + System::Void RasterLayer::Opacity::set(float value) + { + Impl()->setRasterOpacity(mbgl::style::PropertyValue(value)); + } + + float RasterLayer::BrightnessMin::get() + { + return GetFloat(Impl()->getRasterBrightnessMin(), 0.0f); + } + + System::Void RasterLayer::BrightnessMin::set(float value) + { + Impl()->setRasterBrightnessMin(mbgl::style::PropertyValue(value)); + } + + float RasterLayer::BrightnessMax::get() + { + return GetFloat(Impl()->getRasterBrightnessMax(), 1.0f); + } + + System::Void RasterLayer::BrightnessMax::set(float value) + { + Impl()->setRasterBrightnessMax(mbgl::style::PropertyValue(value)); + } + + float RasterLayer::Contrast::get() + { + return GetFloat(Impl()->getRasterContrast(), 0.0f); + } + + System::Void RasterLayer::Contrast::set(float value) + { + Impl()->setRasterContrast(mbgl::style::PropertyValue(value)); + } + + float RasterLayer::HueRotate::get() + { + return GetFloat(Impl()->getRasterHueRotate(), 0.0f); + } + + System::Void RasterLayer::HueRotate::set(float value) + { + Impl()->setRasterHueRotate(mbgl::style::PropertyValue(value)); + } + + float RasterLayer::Saturation::get() + { + return GetFloat(Impl()->getRasterSaturation(), 0.0f); + } + + System::Void RasterLayer::Saturation::set(float value) + { + Impl()->setRasterSaturation(mbgl::style::PropertyValue(value)); + } + + float RasterLayer::FadeDuration::get() + { + return GetFloat(Impl()->getRasterFadeDuration(), 300.0f); + } + + System::Void RasterLayer::FadeDuration::set(float value) + { + Impl()->setRasterFadeDuration(mbgl::style::PropertyValue(value)); + } + + // ========================================================================= + // BackgroundLayer + // ========================================================================= + + BackgroundLayer::BackgroundLayer(mbgl::style::BackgroundLayer* layer) : Layer(layer) {} + + mbgl::style::BackgroundLayer* BackgroundLayer::Impl() + { + return static_cast(_layer); + } + + System::String^ BackgroundLayer::Color::get() + { + return GetColor(Impl()->getBackgroundColor(), "#000000"); + } + + System::Void BackgroundLayer::Color::set(System::String^ value) + { + Impl()->setBackgroundColor(mbgl::style::PropertyValue(ParseColor(value))); + } + + float BackgroundLayer::Opacity::get() + { + return GetFloat(Impl()->getBackgroundOpacity(), 1.0f); + } + + System::Void BackgroundLayer::Opacity::set(float value) + { + Impl()->setBackgroundOpacity(mbgl::style::PropertyValue(value)); + } + + // ========================================================================= + // HeatmapLayer + // ========================================================================= + + HeatmapLayer::HeatmapLayer(mbgl::style::HeatmapLayer* layer) : Layer(layer) {} + + mbgl::style::HeatmapLayer* HeatmapLayer::Impl() + { + return static_cast(_layer); + } + + float HeatmapLayer::Opacity::get() + { + return GetFloat(Impl()->getHeatmapOpacity(), 1.0f); + } + + System::Void HeatmapLayer::Opacity::set(float value) + { + Impl()->setHeatmapOpacity(mbgl::style::PropertyValue(value)); + } + + float HeatmapLayer::Radius::get() + { + return GetFloat(Impl()->getHeatmapRadius(), 30.0f); + } + + System::Void HeatmapLayer::Radius::set(float value) + { + Impl()->setHeatmapRadius(mbgl::style::PropertyValue(value)); + } + + float HeatmapLayer::Intensity::get() + { + return GetFloat(Impl()->getHeatmapIntensity(), 1.0f); + } + + System::Void HeatmapLayer::Intensity::set(float value) + { + Impl()->setHeatmapIntensity(mbgl::style::PropertyValue(value)); + } + + float HeatmapLayer::Weight::get() + { + return GetFloat(Impl()->getHeatmapWeight(), 1.0f); + } + + System::Void HeatmapLayer::Weight::set(float value) + { + Impl()->setHeatmapWeight(mbgl::style::PropertyValue(value)); + } + + // ========================================================================= + // HillshadeLayer + // ========================================================================= + + HillshadeLayer::HillshadeLayer(mbgl::style::HillshadeLayer* layer) : Layer(layer) {} + + mbgl::style::HillshadeLayer* HillshadeLayer::Impl() + { + return static_cast(_layer); + } + + float HillshadeLayer::Exaggeration::get() + { + return GetFloat(Impl()->getHillshadeExaggeration(), 0.5f); + } + + System::Void HillshadeLayer::Exaggeration::set(float value) + { + Impl()->setHillshadeExaggeration(mbgl::style::PropertyValue(value)); + } + + float HillshadeLayer::IlluminationDirection::get() + { + return GetFloat(Impl()->getHillshadeIlluminationDirection(), 335.0f); + } + + System::Void HillshadeLayer::IlluminationDirection::set(float value) + { + Impl()->setHillshadeIlluminationDirection(mbgl::style::PropertyValue(value)); + } + + System::String^ HillshadeLayer::ShadowColor::get() + { + return GetColor(Impl()->getHillshadeShadowColor(), "#000000"); + } + + System::Void HillshadeLayer::ShadowColor::set(System::String^ value) + { + Impl()->setHillshadeShadowColor(mbgl::style::PropertyValue(ParseColor(value))); + } + + System::String^ HillshadeLayer::HighlightColor::get() + { + return GetColor(Impl()->getHillshadeHighlightColor(), "#ffffff"); + } + + System::Void HillshadeLayer::HighlightColor::set(System::String^ value) + { + Impl()->setHillshadeHighlightColor(mbgl::style::PropertyValue(ParseColor(value))); + } + + System::String^ HillshadeLayer::AccentColor::get() + { + return GetColor(Impl()->getHillshadeAccentColor(), "#000000"); + } + + System::Void HillshadeLayer::AccentColor::set(System::String^ value) + { + Impl()->setHillshadeAccentColor(mbgl::style::PropertyValue(ParseColor(value))); + } + + // ========================================================================= + // FillExtrusionLayer + // ========================================================================= + + FillExtrusionLayer::FillExtrusionLayer(mbgl::style::FillExtrusionLayer* layer) : Layer(layer) {} + + mbgl::style::FillExtrusionLayer* FillExtrusionLayer::Impl() + { + return static_cast(_layer); + } + + System::String^ FillExtrusionLayer::Color::get() + { + return GetColor(Impl()->getFillExtrusionColor(), "#000000"); + } + + System::Void FillExtrusionLayer::Color::set(System::String^ value) + { + Impl()->setFillExtrusionColor(mbgl::style::PropertyValue(ParseColor(value))); + } + + float FillExtrusionLayer::Opacity::get() + { + return GetFloat(Impl()->getFillExtrusionOpacity(), 1.0f); + } + + System::Void FillExtrusionLayer::Opacity::set(float value) + { + Impl()->setFillExtrusionOpacity(mbgl::style::PropertyValue(value)); + } + + float FillExtrusionLayer::Height::get() + { + return GetFloat(Impl()->getFillExtrusionHeight(), 0.0f); + } + + System::Void FillExtrusionLayer::Height::set(float value) + { + Impl()->setFillExtrusionHeight(mbgl::style::PropertyValue(value)); + } + + float FillExtrusionLayer::Base::get() + { + return GetFloat(Impl()->getFillExtrusionBase(), 0.0f); + } + + System::Void FillExtrusionLayer::Base::set(float value) + { + Impl()->setFillExtrusionBase(mbgl::style::PropertyValue(value)); + } + + bool FillExtrusionLayer::VerticalGradient::get() + { + return GetBool(Impl()->getFillExtrusionVerticalGradient(), true); + } + + System::Void FillExtrusionLayer::VerticalGradient::set(bool value) + { + Impl()->setFillExtrusionVerticalGradient(mbgl::style::PropertyValue(value)); + } + + // ========================================================================= + // ColorReliefLayer + // ========================================================================= + + ColorReliefLayer::ColorReliefLayer(mbgl::style::ColorReliefLayer* layer) : Layer(layer) {} + + mbgl::style::ColorReliefLayer* ColorReliefLayer::Impl() + { + return static_cast(_layer); + } + + float ColorReliefLayer::Opacity::get() + { + return GetFloat(Impl()->getColorReliefOpacity(), 1.0f); + } + + System::Void ColorReliefLayer::Opacity::set(float value) + { + Impl()->setColorReliefOpacity(mbgl::style::PropertyValue(value)); + } +} diff --git a/src/Source.cpp b/src/Source.cpp new file mode 100644 index 0000000..301c4e4 --- /dev/null +++ b/src/Source.cpp @@ -0,0 +1,48 @@ +#include "pch.h" +#include "Source.h" + +#include +#include +#include + +namespace DOTNET_NAMESPACE +{ + Source::Source(mbgl::style::Source* source) + : _source(source) + { + } + + System::String^ Source::Id::get() + { + return msclr::interop::marshal_as(_source->getID()); + } + + System::String^ Source::Type::get() + { + switch (_source->getType()) + { + case mbgl::style::SourceType::Vector: return "vector"; + case mbgl::style::SourceType::Raster: return "raster"; + case mbgl::style::SourceType::RasterDEM: return "raster-dem"; + case mbgl::style::SourceType::GeoJSON: return "geojson"; + case mbgl::style::SourceType::Image: return "image"; + default: return "unknown"; + } + } + + System::String^ Source::Attribution::get() + { + auto attr = _source->getAttribution(); + return attr ? msclr::interop::marshal_as(*attr) : System::String::Empty; + } + + bool Source::IsVolatile::get() + { + return _source->isVolatile(); + } + + System::Void Source::IsVolatile::set(bool value) + { + _source->setVolatile(value); + } +} diff --git a/src/Sources.cpp b/src/Sources.cpp new file mode 100644 index 0000000..7bddbe6 --- /dev/null +++ b/src/Sources.cpp @@ -0,0 +1,166 @@ +#include "pch.h" +#include "Sources.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace DOTNET_NAMESPACE +{ + // ========================================================================= + // GeoJSONSource + // ========================================================================= + + GeoJSONSource::GeoJSONSource(mbgl::style::GeoJSONSource* source) + : Source(source) + { + } + + mbgl::style::GeoJSONSource* GeoJSONSource::Impl() + { + return static_cast(_source); + } + + System::Void GeoJSONSource::SetUrl(System::String^ url) + { + Impl()->setURL(msclr::interop::marshal_as(url)); + } + + System::Void GeoJSONSource::SetData(System::String^ geoJson) + { + std::string raw = msclr::interop::marshal_as(geoJson); + mbgl::style::conversion::Error err; + auto parsed = mbgl::style::conversion::parseGeoJSON(raw, err); + if (parsed) + { + Impl()->setGeoJSON(*parsed); + } + } + + // ========================================================================= + // VectorSource + // ========================================================================= + + VectorSource::VectorSource(mbgl::style::VectorSource* source) + : Source(source) + { + } + + mbgl::style::VectorSource* VectorSource::Impl() + { + return static_cast(_source); + } + + System::String^ VectorSource::Url::get() + { + auto opt = Impl()->getURL(); + return opt ? msclr::interop::marshal_as(*opt) : System::String::Empty; + } + + // ========================================================================= + // RasterSource + // ========================================================================= + + RasterSource::RasterSource(mbgl::style::RasterSource* source) + : Source(source) + { + } + + mbgl::style::RasterSource* RasterSource::Impl() + { + return static_cast(_source); + } + + System::String^ RasterSource::Url::get() + { + auto opt = Impl()->getURL(); + return opt ? msclr::interop::marshal_as(*opt) : System::String::Empty; + } + + System::UInt16 RasterSource::TileSize::get() + { + return Impl()->getTileSize(); + } + + // ========================================================================= + // RasterDEMSource + // ========================================================================= + + RasterDEMSource::RasterDEMSource(mbgl::style::RasterDEMSource* source) + : Source(source) + { + } + + mbgl::style::RasterDEMSource* RasterDEMSource::Impl() + { + return static_cast(_source); + } + + System::String^ RasterDEMSource::Url::get() + { + // RasterDEMSource inherits RasterSource; cast through to RasterSource for the URL + auto* rs = static_cast(_source); + auto opt = rs->getURL(); + return opt ? msclr::interop::marshal_as(*opt) : System::String::Empty; + } + + System::UInt16 RasterDEMSource::TileSize::get() + { + auto* rs = static_cast(_source); + return rs->getTileSize(); + } + + // ========================================================================= + // ImageSource + // ========================================================================= + + ImageSource::ImageSource(mbgl::style::ImageSource* source) + : Source(source) + { + } + + mbgl::style::ImageSource* ImageSource::Impl() + { + return static_cast(_source); + } + + System::String^ ImageSource::Url::get() + { + auto opt = Impl()->getURL(); + return opt ? msclr::interop::marshal_as(*opt) : System::String::Empty; + } + + System::Void ImageSource::Url::set(System::String^ value) + { + Impl()->setURL(msclr::interop::marshal_as(value)); + } + + array^ ImageSource::Coordinates::get() + { + auto coords = Impl()->getCoordinates(); + auto result = gcnew array(4); + for (int i = 0; i < 4; ++i) + { + result[i] = gcnew LatLng(coords[i].latitude(), coords[i].longitude()); + } + return result; + } + + System::Void ImageSource::Coordinates::set(array^ value) + { + if (!value || value->Length < 4) + return; + std::array coords = { + mbgl::LatLng(value[0]->Latitude, value[0]->Longitude), + mbgl::LatLng(value[1]->Latitude, value[1]->Longitude), + mbgl::LatLng(value[2]->Latitude, value[2]->Longitude), + mbgl::LatLng(value[3]->Latitude, value[3]->Longitude) + }; + Impl()->setCoordinates(coords); + } +} diff --git a/src/Style.cpp b/src/Style.cpp index 72ae88e..f417ca0 100644 --- a/src/Style.cpp +++ b/src/Style.cpp @@ -1,12 +1,28 @@ #include "Convert.h" #include "FileSource.h" #include "Style.h" +#include "Layers.h" +#include "Sources.h" #include #include #include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include #include #include +#include namespace DOTNET_NAMESPACE { @@ -130,4 +146,242 @@ namespace DOTNET_NAMESPACE std::string id = Convert::ToStdString(layerId); return NativePointer->getLayer(id) != nullptr; } + + // ------------------------------------------------------------------------- + // Helper: wrap a native Layer* as the correct managed concrete type + // ------------------------------------------------------------------------- + static Layer^ WrapLayer(mbgl::style::Layer* raw) + { + if (!raw) return nullptr; + if (auto* p = raw->as()) return gcnew FillLayer(p); + if (auto* p = raw->as()) return gcnew LineLayer(p); + if (auto* p = raw->as()) return gcnew CircleLayer(p); + if (auto* p = raw->as()) return gcnew SymbolLayer(p); + if (auto* p = raw->as()) return gcnew RasterLayer(p); + if (auto* p = raw->as()) return gcnew BackgroundLayer(p); + if (auto* p = raw->as()) return gcnew HeatmapLayer(p); + if (auto* p = raw->as()) return gcnew HillshadeLayer(p); + if (auto* p = raw->as()) return gcnew FillExtrusionLayer(p); + if (auto* p = raw->as()) return gcnew ColorReliefLayer(p); + // Unknown type — return base Layer wrapper + return gcnew Layer(raw); + } + + // ------------------------------------------------------------------------- + // Helper: wrap a native Source* as the correct managed concrete type + // ------------------------------------------------------------------------- + static Source^ WrapSource(mbgl::style::Source* raw) + { + if (!raw) return nullptr; + if (auto* p = raw->as()) return gcnew GeoJSONSource(p); + if (auto* p = raw->as()) return gcnew VectorSource(p); + if (auto* p = raw->as()) return gcnew RasterDEMSource(p); + if (auto* p = raw->as()) return gcnew RasterSource(p); + if (auto* p = raw->as()) return gcnew ImageSource(p); + return gcnew Source(raw); + } + + // ------------------------------------------------------------------------- + // Layer retrieval + // ------------------------------------------------------------------------- + + Layer^ Style::GetLayer(System::String^ layerId) + { + auto* raw = NativePointer->getLayer(Convert::ToStdString(layerId)); + return WrapLayer(raw); + } + + System::Collections::Generic::List^ Style::GetLayers() + { + auto result = gcnew System::Collections::Generic::List(); + for (auto* raw : NativePointer->getLayers()) + { + result->Add(WrapLayer(raw)); + } + return result; + } + + // ------------------------------------------------------------------------- + // Source retrieval + // ------------------------------------------------------------------------- + + Source^ Style::GetSource(System::String^ sourceId) + { + auto* raw = NativePointer->getSource(Convert::ToStdString(sourceId)); + return WrapSource(raw); + } + + System::Collections::Generic::List^ Style::GetSources() + { + auto result = gcnew System::Collections::Generic::List(); + for (auto* raw : NativePointer->getSources()) + { + result->Add(WrapSource(raw)); + } + return result; + } + + // ------------------------------------------------------------------------- + // Add layer helpers + // ------------------------------------------------------------------------- + + FillLayer^ Style::AddFillLayer(System::String^ layerId, System::String^ sourceId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer)); + return gcnew FillLayer(raw); + } + + LineLayer^ Style::AddLineLayer(System::String^ layerId, System::String^ sourceId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer)); + return gcnew LineLayer(raw); + } + + CircleLayer^ Style::AddCircleLayer(System::String^ layerId, System::String^ sourceId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer)); + return gcnew CircleLayer(raw); + } + + SymbolLayer^ Style::AddSymbolLayer(System::String^ layerId, System::String^ sourceId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer)); + return gcnew SymbolLayer(raw); + } + + RasterLayer^ Style::AddRasterLayer(System::String^ layerId, System::String^ sourceId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer)); + return gcnew RasterLayer(raw); + } + + BackgroundLayer^ Style::AddBackgroundLayer(System::String^ layerId) + { + auto layer = std::make_unique(Convert::ToStdString(layerId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer)); + return gcnew BackgroundLayer(raw); + } + + HeatmapLayer^ Style::AddHeatmapLayer(System::String^ layerId, System::String^ sourceId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer)); + return gcnew HeatmapLayer(raw); + } + + HillshadeLayer^ Style::AddHillshadeLayer(System::String^ layerId, System::String^ sourceId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer)); + return gcnew HillshadeLayer(raw); + } + + FillExtrusionLayer^ Style::AddFillExtrusionLayer(System::String^ layerId, System::String^ sourceId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer)); + return gcnew FillExtrusionLayer(raw); + } + + ColorReliefLayer^ Style::AddColorReliefLayer(System::String^ layerId, System::String^ sourceId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer)); + return gcnew ColorReliefLayer(raw); + } + + // ------------------------------------------------------------------------- + // Add source helpers + // ------------------------------------------------------------------------- + + GeoJSONSource^ Style::AddGeoJsonSource(System::String^ sourceId) + { + auto src = std::make_unique(Convert::ToStdString(sourceId)); + auto* raw = src.get(); + NativePointer->addSource(std::move(src)); + return gcnew GeoJSONSource(raw); + } + + GeoJSONSource^ Style::AddGeoJsonSourceFromUrl(System::String^ sourceId, System::String^ url) + { + auto src = std::make_unique(Convert::ToStdString(sourceId)); + src->setURL(Convert::ToStdString(url)); + auto* raw = src.get(); + NativePointer->addSource(std::move(src)); + return gcnew GeoJSONSource(raw); + } + + VectorSource^ Style::AddVectorSource(System::String^ sourceId, System::String^ url) + { + auto src = std::make_unique( + Convert::ToStdString(sourceId), + variant(Convert::ToStdString(url))); + auto* raw = src.get(); + NativePointer->addSource(std::move(src)); + return gcnew VectorSource(raw); + } + + RasterSource^ Style::AddRasterSource(System::String^ sourceId, System::String^ url, System::UInt16 tileSize) + { + auto src = std::make_unique( + Convert::ToStdString(sourceId), + variant(Convert::ToStdString(url)), + tileSize); + auto* raw = src.get(); + NativePointer->addSource(std::move(src)); + return gcnew RasterSource(raw); + } + + RasterDEMSource^ Style::AddRasterDemSource(System::String^ sourceId, System::String^ url, System::UInt16 tileSize) + { + auto src = std::make_unique( + Convert::ToStdString(sourceId), + variant(Convert::ToStdString(url)), + tileSize); + auto* raw = src.get(); + NativePointer->addSource(std::move(src)); + return gcnew RasterDEMSource(raw); + } + + ImageSource^ Style::AddImageSource(System::String^ sourceId, System::String^ url, array^ coordinates) + { + std::array coords = { + mbgl::LatLng(coordinates[0]->Latitude, coordinates[0]->Longitude), + mbgl::LatLng(coordinates[1]->Latitude, coordinates[1]->Longitude), + mbgl::LatLng(coordinates[2]->Latitude, coordinates[2]->Longitude), + mbgl::LatLng(coordinates[3]->Latitude, coordinates[3]->Longitude) + }; + auto src = std::make_unique(Convert::ToStdString(sourceId), coords); + if (url && url->Length > 0) + { + src->setURL(Convert::ToStdString(url)); + } + auto* raw = src.get(); + NativePointer->addSource(std::move(src)); + return gcnew ImageSource(raw); + } } From 7ebeeb39c1f2568d8ab9cf147505982f4ab006c6 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Fri, 1 May 2026 10:05:31 -0400 Subject: [PATCH 45/63] Add Light, StyleImage, TransitionOptions, and Style image management - New Light ref class with Anchor (Map/Viewport), Color (CSS), Intensity, and Position (spherical radial/azimuthal/polar) properties - New LightAnchor enum and LightPosition value struct - New StyleImage ref class for sprite image metadata - New TransitionOptions ref class with DurationMilliseconds, DelayMilliseconds, EnablePlacementTransitions - Style::GetLight() returns non-owning Light wrapper - Style::GetTransitionOptions() / SetTransitionOptions() - Style::GetDefaultCamera() returns CameraOptions - Style::AddImage(id, pixels, pixelRatio[, sdf]) adds sprite image - Style::GetImage(id) returns StyleImage metadata or null - Style::HasImage(id) checks existence - Style::RemoveImage(id) removes sprite - Light.cpp added to CMakeLists.txt --- CMakeLists.txt | 1 + include/Light.h | 82 ++++++++++++++++++++++++++++ include/Style.h | 67 ++++++++++++++++++++++- include/StyleImage.h | 29 ++++++++++ include/TransitionOptions.h | 39 +++++++++++++ src/Light.cpp | 98 +++++++++++++++++++++++++++++++++ src/Style.cpp | 106 ++++++++++++++++++++++++++++++++++++ 7 files changed, 419 insertions(+), 3 deletions(-) create mode 100644 include/Light.h create mode 100644 include/StyleImage.h create mode 100644 include/TransitionOptions.h create mode 100644 src/Light.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5eac5b9..b4df970 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,6 +95,7 @@ target_sources( ${PROJECT_SOURCE_DIR}/src/Style.cpp ${PROJECT_SOURCE_DIR}/src/Layer.cpp ${PROJECT_SOURCE_DIR}/src/Layers.cpp + ${PROJECT_SOURCE_DIR}/src/Light.cpp ${PROJECT_SOURCE_DIR}/src/Source.cpp ${PROJECT_SOURCE_DIR}/src/Sources.cpp ${PROJECT_SOURCE_DIR}/src/TileCoordinate.cpp diff --git a/include/Light.h b/include/Light.h new file mode 100644 index 0000000..861964e --- /dev/null +++ b/include/Light.h @@ -0,0 +1,82 @@ +#pragma once +#include "NativeWrapper.h" + +namespace mbgl { +namespace style { + class Light; +} +} + +namespace DOTNET_NAMESPACE +{ + /// + /// Specifies the anchor reference for the light source. + /// + public enum class LightAnchor : System::Byte + { + /// The light is fixed relative to the map. + Map, + /// The light rotates with the viewport. + Viewport + }; + + /// + /// Spherical position of the light source. + /// Coordinates are [radial, azimuthal, polar] where: + /// radial = distance from the center (0.0–infinity) + /// azimuthal = azimuthal angle in degrees (0–360) + /// polar = polar angle in degrees (0–90) + /// + public value class LightPosition + { + public: + float Radial; + float Azimuthal; + float Polar; + + LightPosition(float radial, float azimuthal, float polar) + : Radial(radial), Azimuthal(azimuthal), Polar(polar) {} + }; + + /// + /// Wraps an mbgl::style::Light object (non-owning pointer). + /// Obtain an instance via . + /// + public ref class Light + { + public: + /// Gets or sets the anchor type. + property LightAnchor Anchor + { + LightAnchor get(); + System::Void set(LightAnchor value); + } + + /// Gets or sets the light color as a CSS string (e.g. "#ffffff"). + property System::String^ Color + { + System::String^ get(); + System::Void set(System::String^ value); + } + + /// Gets or sets the intensity in [0, 1]. + property System::Single Intensity + { + System::Single get(); + System::Void set(System::Single value); + } + + /// Gets or sets the spherical light position. + property LightPosition Position + { + LightPosition get(); + System::Void set(LightPosition value); + } + + internal: + Light(mbgl::style::Light* light); + + private: + mbgl::style::Light* _light; + }; +} diff --git a/include/Style.h b/include/Style.h index e32d548..50714cb 100644 --- a/include/Style.h +++ b/include/Style.h @@ -5,6 +5,9 @@ #include "Source.h" #include "Sources.h" #include "LatLng.h" +#include "Light.h" +#include "StyleImage.h" +#include "TransitionOptions.h" namespace mbgl { @@ -18,7 +21,7 @@ namespace DOTNET_NAMESPACE { ref class CameraOptions; ref class FileSource; - ref class TransitionOptions; + ref class PremultipliedImage; public ref class Style : NativeWrapper { @@ -95,8 +98,66 @@ namespace DOTNET_NAMESPACE /// Returns true if a source with the given id exists. System::Boolean HasSource(System::String^ sourceId); - // ------------------------------------------------------------------------- - // Legacy helpers (kept for backward compatibility) + // ------------------------------------------------------------------------- // Light + // -------------------------------------------------------------------- + + /// + /// Returns a non-owning wrapper around the style's current light. + /// Mutating the returned object immediately affects the rendered map. + /// + Light^ GetLight(); + + // -------------------------------------------------------------------- + // Transition options + // -------------------------------------------------------------------- + + /// Gets the current global transition options. + TransitionOptions^ GetTransitionOptions(); + + /// Sets the global transition options. + System::Void SetTransitionOptions(TransitionOptions^ options); + + // -------------------------------------------------------------------- + // Default camera + // -------------------------------------------------------------------- + + /// + /// Returns the default camera position defined in the style JSON + /// (the "center", "zoom", "bearing" and "pitch" root fields). + /// Fields not set in the style will be unset (null) on the returned options. + /// + CameraOptions^ GetDefaultCamera(); + + // -------------------------------------------------------------------- + // Sprite / icon images + // -------------------------------------------------------------------- + + /// + /// Add a sprite image to the style. + /// + /// Unique sprite id. + /// Premultiplied RGBA pixel data. + /// Device pixel ratio the image was created at (usually 1 or 2). + /// True if the image should be treated as a signed distance field. + System::Void AddImage(System::String^ id, PremultipliedImage^ pixels, + System::Single pixelRatio, System::Boolean sdf); + + /// Add a standard (non-SDF) sprite image. + System::Void AddImage(System::String^ id, PremultipliedImage^ pixels, + System::Single pixelRatio); + + /// + /// Returns metadata for the image with the given id, or nullptr if not found. + /// + StyleImage^ GetImage(System::String^ id); + + /// Returns true if an image with the given id exists in the style. + System::Boolean HasImage(System::String^ id); + + /// Removes the image with the given id from the style. + System::Void RemoveImage(System::String^ id); + + // -------------------------------------------------------------------- // Legacy helpers (kept for backward compatibility) // ------------------------------------------------------------------------- /// Add a GeoJSON source that fetches its data from a URL. diff --git a/include/StyleImage.h b/include/StyleImage.h new file mode 100644 index 0000000..b04e10e --- /dev/null +++ b/include/StyleImage.h @@ -0,0 +1,29 @@ +#pragma once + +namespace DOTNET_NAMESPACE +{ + /// + /// Metadata for a style sprite image obtained from . + /// + public ref class StyleImage + { + public: + /// The image id (sprite name). + property System::String^ Id; + /// Width in pixels. + property System::UInt32 Width; + /// Height in pixels. + property System::UInt32 Height; + /// Pixel ratio (device pixel ratio the image was created at). + property System::Single PixelRatio; + /// Whether the image is a signed-distance-field icon. + property System::Boolean Sdf; + + StyleImage() {} + + internal: + StyleImage(System::String^ id, System::UInt32 width, System::UInt32 height, + System::Single pixelRatio, System::Boolean sdf) + : Id(id), Width(width), Height(height), PixelRatio(pixelRatio), Sdf(sdf) {} + }; +} diff --git a/include/TransitionOptions.h b/include/TransitionOptions.h new file mode 100644 index 0000000..e927acc --- /dev/null +++ b/include/TransitionOptions.h @@ -0,0 +1,39 @@ +#pragma once + +namespace DOTNET_NAMESPACE +{ + /// + /// Options that control the timing of transitions between style property values. + /// + public ref class TransitionOptions + { + public: + /// + /// Duration of the transition in milliseconds. + /// Null means no explicit duration (the default will apply). + /// + property System::Nullable DurationMilliseconds; + + /// + /// Delay before the transition starts, in milliseconds. + /// Null means no explicit delay. + /// + property System::Nullable DelayMilliseconds; + + /// + /// Whether placement transitions (symbol fade-in/out) are enabled. + /// Defaults to true. + /// + property System::Boolean EnablePlacementTransitions; + + TransitionOptions() + : EnablePlacementTransitions(true) {} + + TransitionOptions(System::Nullable durationMs, + System::Nullable delayMs, + System::Boolean enablePlacementTransitions) + : DurationMilliseconds(durationMs), + DelayMilliseconds(delayMs), + EnablePlacementTransitions(enablePlacementTransitions) {} + }; +} diff --git a/src/Light.cpp b/src/Light.cpp new file mode 100644 index 0000000..bde2480 --- /dev/null +++ b/src/Light.cpp @@ -0,0 +1,98 @@ +#include "Light.h" +#include "Convert.h" +#include +#include +#include +#include + +namespace DOTNET_NAMESPACE +{ + Light::Light(mbgl::style::Light* light) + : _light(light) + { + } + + // ------------------------------------------------------------------------- + // Anchor + // ------------------------------------------------------------------------- + + LightAnchor Light::Anchor::get() + { + auto pv = _light->getAnchor(); + if (pv.isConstant()) { + return pv.asConstant() == mbgl::style::LightAnchorType::Viewport + ? LightAnchor::Viewport + : LightAnchor::Map; + } + // Default + return _light->getDefaultAnchor() == mbgl::style::LightAnchorType::Viewport + ? LightAnchor::Viewport + : LightAnchor::Map; + } + + System::Void Light::Anchor::set(LightAnchor value) + { + _light->setAnchor(mbgl::style::PropertyValue( + value == LightAnchor::Viewport + ? mbgl::style::LightAnchorType::Viewport + : mbgl::style::LightAnchorType::Map)); + } + + // ------------------------------------------------------------------------- + // Color + // ------------------------------------------------------------------------- + + System::String^ Light::Color::get() + { + auto pv = _light->getColor(); + if (pv.isConstant()) { + return gcnew System::String(pv.asConstant().stringify().c_str()); + } + return gcnew System::String(_light->getDefaultColor().stringify().c_str()); + } + + System::Void Light::Color::set(System::String^ value) + { + auto parsed = mbgl::Color::parse(Convert::ToStdString(value)); + if (parsed) { + _light->setColor(mbgl::style::PropertyValue(*parsed)); + } + // Silently ignore unparseable colors (same pattern as Layers.cpp) + } + + // ------------------------------------------------------------------------- + // Intensity + // ------------------------------------------------------------------------- + + System::Single Light::Intensity::get() + { + auto pv = _light->getIntensity(); + return pv.isConstant() ? pv.asConstant() : _light->getDefaultIntensity(); + } + + System::Void Light::Intensity::set(System::Single value) + { + _light->setIntensity(mbgl::style::PropertyValue(value)); + } + + // ------------------------------------------------------------------------- + // Position + // ------------------------------------------------------------------------- + + LightPosition Light::Position::get() + { + auto pv = _light->getPosition(); + mbgl::style::Position pos = pv.isConstant() + ? pv.asConstant() + : _light->getDefaultPosition(); + auto sph = pos.getSpherical(); + return LightPosition(sph[0], sph[1], sph[2]); + } + + System::Void Light::Position::set(LightPosition value) + { + std::array sph = { value.Radial, value.Azimuthal, value.Polar }; + _light->setPosition(mbgl::style::PropertyValue( + mbgl::style::Position(sph))); + } +} diff --git a/src/Style.cpp b/src/Style.cpp index f417ca0..3d415b3 100644 --- a/src/Style.cpp +++ b/src/Style.cpp @@ -3,8 +3,15 @@ #include "Style.h" #include "Layers.h" #include "Sources.h" +#include "Light.h" +#include "PremultipliedImage.h" +#include "CameraOptions.h" #include #include +#include +#include +#include +#include #include #include #include @@ -384,4 +391,103 @@ namespace DOTNET_NAMESPACE NativePointer->addSource(std::move(src)); return gcnew ImageSource(raw); } + + // ------------------------------------------------------------------------- + // Light + // ------------------------------------------------------------------------- + + Light^ Style::GetLight() + { + return gcnew Light(NativePointer->getLight()); + } + + // ------------------------------------------------------------------------- + // Transition options + // ------------------------------------------------------------------------- + + TransitionOptions^ Style::GetTransitionOptions() + { + auto native = NativePointer->getTransitionOptions(); + auto opts = gcnew TransitionOptions(); + if (native.duration) { + opts->DurationMilliseconds = System::Nullable( + (System::Int64)std::chrono::duration_cast(*native.duration).count()); + } + if (native.delay) { + opts->DelayMilliseconds = System::Nullable( + (System::Int64)std::chrono::duration_cast(*native.delay).count()); + } + opts->EnablePlacementTransitions = native.enablePlacementTransitions; + return opts; + } + + System::Void Style::SetTransitionOptions(TransitionOptions^ options) + { + mbgl::style::TransitionOptions native; + if (options->DurationMilliseconds.HasValue) { + native.duration = std::chrono::milliseconds(options->DurationMilliseconds.Value); + } + if (options->DelayMilliseconds.HasValue) { + native.delay = std::chrono::milliseconds(options->DelayMilliseconds.Value); + } + native.enablePlacementTransitions = options->EnablePlacementTransitions; + NativePointer->setTransitionOptions(native); + } + + // ------------------------------------------------------------------------- + // Default camera + // ------------------------------------------------------------------------- + + CameraOptions^ Style::GetDefaultCamera() + { + auto cam = NativePointer->getDefaultCamera(); + return gcnew CameraOptions(gcnew NativePointerHolder(cam)); + } + + // ------------------------------------------------------------------------- + // Sprite / icon images + // ------------------------------------------------------------------------- + + System::Void Style::AddImage(System::String^ id, PremultipliedImage^ pixels, + System::Single pixelRatio, System::Boolean sdf) + { + // Clone the pixel data (style::Image takes ownership via move) + auto pixelsClone = pixels->NativePointer->clone(); + auto img = std::make_unique( + Convert::ToStdString(id), + std::move(pixelsClone), + pixelRatio, + (bool)sdf); + NativePointer->addImage(std::move(img)); + } + + System::Void Style::AddImage(System::String^ id, PremultipliedImage^ pixels, + System::Single pixelRatio) + { + AddImage(id, pixels, pixelRatio, false); + } + + StyleImage^ Style::GetImage(System::String^ id) + { + auto opt = NativePointer->getImage(Convert::ToStdString(id)); + if (!opt) return nullptr; + const auto& img = *opt; + const auto& px = img.getImage(); + return gcnew StyleImage( + gcnew System::String(img.getID().c_str()), + (System::UInt32)px.size.width, + (System::UInt32)px.size.height, + img.getPixelRatio(), + img.isSdf()); + } + + System::Boolean Style::HasImage(System::String^ id) + { + return NativePointer->getImage(Convert::ToStdString(id)).has_value(); + } + + System::Void Style::RemoveImage(System::String^ id) + { + NativePointer->removeImage(Convert::ToStdString(id)); + } } From 4d716d36cb83746a69be89de978a3b03b31867ee Mon Sep 17 00:00:00 2001 From: acalcutt Date: Fri, 1 May 2026 10:29:40 -0400 Subject: [PATCH 46/63] Add layer enums, full SymbolLayer layout/paint, missing Fill/Line/Circle properties, Layer filter, AddLayer beforeLayerId --- include/Enums.h | 96 +++++++ include/Layer.h | 14 + include/Layers.h | 194 ++++++++++++- include/Style.h | 12 + src/Layer.cpp | 118 ++++++++ src/Layers.cpp | 720 ++++++++++++++++++++++++++++++++++++++++++++++- src/Style.cpp | 93 ++++++ 7 files changed, 1232 insertions(+), 15 deletions(-) diff --git a/include/Enums.h b/include/Enums.h index 418bbdb..e733b7b 100644 --- a/include/Enums.h +++ b/include/Enums.h @@ -83,4 +83,100 @@ namespace DOTNET_NAMESPACE /// ResourceLoader }; + + // ========================================================================= + // Style layer property enums + // ========================================================================= + + public enum class LineCapType : System::Byte + { + Round, + Butt, + Square, + }; + + public enum class LineJoinType : System::Byte + { + Miter, + Bevel, + Round, + }; + + /// Controls the frame of reference for line/fill/circle translate. + public enum class TranslateAnchorType : System::Byte + { + Map, + Viewport, + }; + + /// Controls how the circle is translated relative to the camera pitch. + public enum class CirclePitchScaleType : System::Byte + { + Map, + Viewport, + }; + + public enum class SymbolPlacementType : System::Byte + { + Point, + Line, + LineCenter, + }; + + public enum class SymbolZOrderType : System::Byte + { + Auto, + ViewportY, + Source, + }; + + /// Alignment type used for icon/text pitch and rotation alignment. + public enum class AlignmentType : System::Byte + { + Map, + Viewport, + Auto, + }; + + public enum class TextJustifyType : System::Byte + { + Auto, + Center, + Left, + Right, + }; + + public enum class SymbolAnchorType : System::Byte + { + Center, + Left, + Right, + Top, + Bottom, + TopLeft, + TopRight, + BottomLeft, + BottomRight, + }; + + public enum class TextTransformType : System::Byte + { + None, + Uppercase, + Lowercase, + }; + + public enum class IconTextFitType : System::Byte + { + None, + Both, + Width, + Height, + }; + + public enum class TextWritingModeType : System::Byte + { + Horizontal, + Vertical, + }; } diff --git a/include/Layer.h b/include/Layer.h index 489c6b3..cc81583 100644 --- a/include/Layer.h +++ b/include/Layer.h @@ -69,5 +69,19 @@ namespace DOTNET_NAMESPACE /// "heatmap", "hillshade", "fill-extrusion"). /// property System::String^ Type { System::String^ get(); } + + /// + /// Gets the current filter expression as a JSON string, + /// or an empty string if no filter is set. + /// + System::String^ GetFilter(); + + /// + /// Sets a filter expression from a MapLibre filter JSON string, + /// e.g. ["==", "class", "motorway"] or a full expression array. + /// Pass null or an empty string to clear the filter. + /// Throws if the JSON cannot be parsed as a valid filter expression. + /// + System::Void SetFilter(System::String^ filterJson); }; } diff --git a/include/Layers.h b/include/Layers.h index 42e0855..7d8e03e 100644 --- a/include/Layers.h +++ b/include/Layers.h @@ -1,5 +1,6 @@ #pragma once #include "Layer.h" +#include "Enums.h" // Forward-declare native layer types namespace mbgl { @@ -66,6 +67,18 @@ namespace DOTNET_NAMESPACE bool get(); System::Void set(bool); } + + /// Sort key for feature rendering order within the layer. + property float SortKey { float get(); System::Void set(float); } + + /// Sprite image name for the fill pattern. Empty string = no pattern. + property System::String^ Pattern { System::String^ get(); System::Void set(System::String^); } + + /// X/Y translation of the fill layer in pixels as [x, y]. + property cli::array^ Translate { cli::array^ get(); System::Void set(cli::array^); } + + /// Frame of reference for Translate: Map or Viewport. + property TranslateAnchorType TranslateAnchor { TranslateAnchorType get(); System::Void set(TranslateAnchorType); } }; // ========================================================================= @@ -123,6 +136,30 @@ namespace DOTNET_NAMESPACE float get(); System::Void set(float); } + + /// Cap style at the ends of lines. + property LineCapType Cap { LineCapType get(); System::Void set(LineCapType); } + + /// Join style at corners of lines. + property LineJoinType Join { LineJoinType get(); System::Void set(LineJoinType); } + + /// Sort key for feature rendering order within the layer. + property float SortKey { float get(); System::Void set(float); } + + /// + /// Dash pattern for the line as alternating gap/dash lengths (in line widths). + /// Returns an empty array when unset (solid line). + /// + property cli::array^ Dasharray { cli::array^ get(); System::Void set(cli::array^); } + + /// Sprite image name for a repeating line pattern. Empty string = no pattern. + property System::String^ Pattern { System::String^ get(); System::Void set(System::String^); } + + /// X/Y translation of the line layer in pixels as [x, y]. + property cli::array^ Translate { cli::array^ get(); System::Void set(cli::array^); } + + /// Frame of reference for Translate: Map or Viewport. + property TranslateAnchorType TranslateAnchor { TranslateAnchorType get(); System::Void set(TranslateAnchorType); } }; // ========================================================================= @@ -187,6 +224,21 @@ namespace DOTNET_NAMESPACE float get(); System::Void set(float); } + + /// Sort key for feature rendering order within the layer. + property float SortKey { float get(); System::Void set(float); } + + /// Controls the scaling of circle radius with camera pitch. + property CirclePitchScaleType PitchScale { CirclePitchScaleType get(); System::Void set(CirclePitchScaleType); } + + /// Orientation of the circle relative to map or viewport. + property AlignmentType PitchAlignment { AlignmentType get(); System::Void set(AlignmentType); } + + /// X/Y translation of circles in pixels as [x, y]. + property cli::array^ Translate { cli::array^ get(); System::Void set(cli::array^); } + + /// Frame of reference for Translate: Map or Viewport. + property TranslateAnchorType TranslateAnchor { TranslateAnchorType get(); System::Void set(TranslateAnchorType); } }; // ========================================================================= @@ -203,6 +255,133 @@ namespace DOTNET_NAMESPACE mbgl::style::SymbolLayer* Impl(); public: + // ---- Layout: Symbol placement ---- + + /// How symbol icons and text are placed relative to its geometry. Default: Point. + property SymbolPlacementType SymbolPlacement { SymbolPlacementType get(); System::Void set(SymbolPlacementType); } + + /// Distance between two symbol anchors when placement is Line. Default: 250. + property float SymbolSpacing { float get(); System::Void set(float); } + + /// If true, symbols will not cross tile edges to avoid gaps or overlaps. + property bool SymbolAvoidEdges { bool get(); System::Void set(bool); } + + /// Sort key for overlap resolution (lower = drawn below higher values). + property float SymbolSortKey { float get(); System::Void set(float); } + + /// Determines whether overlapping symbols will be rendered in the order they appear in the data source. Default: Auto. + property SymbolZOrderType SymbolZOrder { SymbolZOrderType get(); System::Void set(SymbolZOrderType); } + + // ---- Layout: Icon ---- + + /// + /// Name of a sprite image to use for the icon. + /// Returns the first section's image id for constant Formatted values, empty string otherwise. + /// + property System::String^ IconImage { System::String^ get(); System::Void set(System::String^); } + + /// Scale factor for the icon image. Default: 1. + property float IconSize { float get(); System::Void set(float); } + + /// Part of icon to anchor to its placement position. Default: Center. + property SymbolAnchorType IconAnchor { SymbolAnchorType get(); System::Void set(SymbolAnchorType); } + + /// Rotates the icon clockwise by this angle in degrees. + property float IconRotate { float get(); System::Void set(float); } + + /// Offset distance of icon from its anchor in [x, y] ems. + property cli::array^ IconOffset { cli::array^ get(); System::Void set(cli::array^); } + + /// Extra padding around icon bounding box used to detect symbol collisions. Default: 2. + property float IconPadding { float get(); System::Void set(float); } + + /// If true, the icon may be flipped to prevent upside-down text. Default: false. + property bool IconKeepUpright { bool get(); System::Void set(bool); } + + /// If true, other symbols can be visible even if they collide with the icon. Default: false. + property bool IconAllowOverlap { bool get(); System::Void set(bool); } + + /// If true, the icon will be visible even if it collides with other symbols. Default: false. + property bool IconIgnorePlacement { bool get(); System::Void set(bool); } + + /// If true, text will be displayed without icon if icon is not available. Default: false. + property bool IconOptional { bool get(); System::Void set(bool); } + + /// In combination with SymbolPlacement, determines orientation of icon. Default: Auto. + property AlignmentType IconRotationAlignment { AlignmentType get(); System::Void set(AlignmentType); } + + /// Orientation of icon when map is pitched. Default: Auto. + property AlignmentType IconPitchAlignment { AlignmentType get(); System::Void set(AlignmentType); } + + // ---- Layout: Text ---- + + /// + /// Text content to display (plain string; for rich text use the style JSON directly). + /// + property System::String^ TextField { System::String^ get(); System::Void set(System::String^); } + + /// + /// Ordered list of font stack names. E.g. { "Open Sans Bold", "Arial Unicode MS Bold" }. + /// Returns an empty array if not set. + /// + property cli::array^ TextFont { cli::array^ get(); System::Void set(cli::array^); } + + /// Font size in pixels. Default: 16. + property float TextSize { float get(); System::Void set(float); } + + /// Maximum line width for text wrapping in ems. Default: 10. + property float TextMaxWidth { float get(); System::Void set(float); } + + /// Text leading value for multi-line text. Default: 1.2. + property float TextLineHeight { float get(); System::Void set(float); } + + /// Spacing between letters in ems. Default: 0. + property float TextLetterSpacing { float get(); System::Void set(float); } + + /// Text justification. Default: Center. + property TextJustifyType TextJustify { TextJustifyType get(); System::Void set(TextJustifyType); } + + /// Radial offset in ems from the anchor (used instead of TextOffset for line/circle placement). + property float TextRadialOffset { float get(); System::Void set(float); } + + /// Part of text to anchor to the symbol placement position. Default: Center. + property SymbolAnchorType TextAnchor { SymbolAnchorType get(); System::Void set(SymbolAnchorType); } + + /// Maximum angle between adjacent glyph segments for line placement in degrees. + property float TextMaxAngle { float get(); System::Void set(float); } + + /// Rotates the text clockwise by this angle in degrees. + property float TextRotate { float get(); System::Void set(float); } + + /// Extra padding around text bounding box. Default: 2. + property float TextPadding { float get(); System::Void set(float); } + + /// If true, the text may be flipped vertically to prevent upside-down text. Default: true. + property bool TextKeepUpright { bool get(); System::Void set(bool); } + + /// Specifies how to capitalize text. Default: None. + property TextTransformType TextTransform { TextTransformType get(); System::Void set(TextTransformType); } + + /// Offset distance of text from its anchor in [x, y] ems. + property cli::array^ TextOffset { cli::array^ get(); System::Void set(cli::array^); } + + /// If true, other symbols can be visible even if they collide with the text. + property bool TextAllowOverlap { bool get(); System::Void set(bool); } + + /// If true, the text will be visible even if it collides with other symbols. + property bool TextIgnorePlacement { bool get(); System::Void set(bool); } + + /// If true, icons will be shown without text if text is not available. + property bool TextOptional { bool get(); System::Void set(bool); } + + /// In combination with SymbolPlacement, determines orientation of text. Default: Auto. + property AlignmentType TextRotationAlignment { AlignmentType get(); System::Void set(AlignmentType); } + + /// Orientation of text when map is pitched. Default: Auto. + property AlignmentType TextPitchAlignment { AlignmentType get(); System::Void set(AlignmentType); } + + // ---- Paint: Text ---- + /// Text color as a CSS hex string. property System::String^ TextColor { @@ -217,13 +396,6 @@ namespace DOTNET_NAMESPACE System::Void set(float); } - /// Font size in pixels. - property float TextSize - { - float get(); - System::Void set(float); - } - /// Halo color around text glyphs as a CSS hex string. property System::String^ TextHaloColor { @@ -238,6 +410,11 @@ namespace DOTNET_NAMESPACE System::Void set(float); } + /// Halo blur applied to text glyphs in pixels. + property float TextHaloBlur { float get(); System::Void set(float); } + + // ---- Paint: Icon ---- + /// Icon opacity (0.0 – 1.0). property float IconOpacity { @@ -265,6 +442,9 @@ namespace DOTNET_NAMESPACE float get(); System::Void set(float); } + + /// Icon halo blur in pixels. + property float IconHaloBlur { float get(); System::Void set(float); } }; // ========================================================================= diff --git a/include/Style.h b/include/Style.h index 50714cb..c7712a6 100644 --- a/include/Style.h +++ b/include/Style.h @@ -76,6 +76,18 @@ namespace DOTNET_NAMESPACE FillExtrusionLayer^ AddFillExtrusionLayer(System::String^ layerId, System::String^ sourceId); ColorReliefLayer^ AddColorReliefLayer(System::String^ layerId, System::String^ sourceId); + /// Same as the two-arg overloads but inserts the layer before . + FillLayer^ AddFillLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); + LineLayer^ AddLineLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); + CircleLayer^ AddCircleLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); + SymbolLayer^ AddSymbolLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); + RasterLayer^ AddRasterLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); + BackgroundLayer^ AddBackgroundLayer(System::String^ layerId, System::String^ beforeLayerId); + HeatmapLayer^ AddHeatmapLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); + HillshadeLayer^ AddHillshadeLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); + FillExtrusionLayer^ AddFillExtrusionLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); + ColorReliefLayer^ AddColorReliefLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); + /// Remove a layer by id. Returns true if removed. System::Boolean RemoveLayer(System::String^ layerId); /// Returns true if a layer with the given id exists. diff --git a/src/Layer.cpp b/src/Layer.cpp index 91bf6fc..765915a 100644 --- a/src/Layer.cpp +++ b/src/Layer.cpp @@ -3,8 +3,93 @@ #include #include +#include +#include +#include +#include +#include #include +#include +#include + +namespace +{ + // ----------------------------------------------------------------------- + // Forward declaration + // ----------------------------------------------------------------------- + static void WriteValue(rapidjson::Writer& writer, + const mbgl::Value& value); + + struct ValueWriter + { + rapidjson::Writer& writer; + + void operator()(const mbgl::NullValue&) const + { + writer.Null(); + } + + void operator()(bool v) const + { + writer.Bool(v); + } + + void operator()(uint64_t v) const + { + writer.Uint64(v); + } + + void operator()(int64_t v) const + { + writer.Int64(v); + } + + void operator()(double v) const + { + writer.Double(v); + } + + void operator()(const std::string& v) const + { + writer.String(v.c_str(), static_cast(v.size())); + } + + void operator()(const mapbox::util::recursive_wrapper>& rw) const + { + writer.StartArray(); + for (const auto& item : rw.get()) + WriteValue(writer, item); + writer.EndArray(); + } + + void operator()(const mapbox::util::recursive_wrapper>& rw) const + { + writer.StartObject(); + for (const auto& kv : rw.get()) + { + writer.Key(kv.first.c_str(), static_cast(kv.first.size())); + WriteValue(writer, kv.second); + } + writer.EndObject(); + } + }; + + static void WriteValue(rapidjson::Writer& writer, + const mbgl::Value& value) + { + mbgl::Value::visit(value, ValueWriter{ writer }); + } + + static std::string ValueToJsonString(const mbgl::Value& value) + { + rapidjson::StringBuffer buf; + rapidjson::Writer writer(buf); + WriteValue(writer, value); + return buf.GetString(); + } +} + namespace DOTNET_NAMESPACE { Layer::Layer(mbgl::style::Layer* layer) @@ -74,4 +159,37 @@ namespace DOTNET_NAMESPACE const auto* info = _layer->getTypeInfo(); return info ? msclr::interop::marshal_as(std::string(info->type)) : System::String::Empty; } + + System::String^ Layer::GetFilter() + { + const mbgl::style::Filter& filter = _layer->getFilter(); + if (!filter) + return System::String::Empty; + mbgl::Value serialized = filter.serialize(); + return msclr::interop::marshal_as(ValueToJsonString(serialized)); + } + + System::Void Layer::SetFilter(System::String^ filterJson) + { + if (filterJson == nullptr || filterJson->Length == 0) + { + _layer->setFilter(mbgl::style::Filter{}); + return; + } + + std::string json = msclr::interop::marshal_as(filterJson); + mbgl::style::conversion::JSDocument doc; + doc.Parse(json.c_str()); + if (doc.HasParseError()) + throw gcnew System::ArgumentException("Invalid filter JSON: parse error"); + + mbgl::style::conversion::Error error; + auto filter = mbgl::style::conversion::convert( + mbgl::style::conversion::Convertible(&doc), error); + if (!filter) + throw gcnew System::ArgumentException( + msclr::interop::marshal_as("Invalid filter expression: " + error.message)); + + _layer->setFilter(*filter); + } } diff --git a/src/Layers.cpp b/src/Layers.cpp index f3ca738..3925e78 100644 --- a/src/Layers.cpp +++ b/src/Layers.cpp @@ -112,6 +112,63 @@ namespace DOTNET_NAMESPACE Impl()->setFillAntialias(mbgl::style::PropertyValue(value)); } + float FillLayer::SortKey::get() + { + return GetFloat(Impl()->getFillSortKey(), 0.0f); + } + + System::Void FillLayer::SortKey::set(float value) + { + Impl()->setFillSortKey(mbgl::style::PropertyValue(value)); + } + + System::String^ FillLayer::Pattern::get() + { + auto& pv = Impl()->getFillPattern(); + if (pv.isConstant()) + return gcnew System::String(pv.asConstant().id().c_str()); + return System::String::Empty; + } + + System::Void FillLayer::Pattern::set(System::String^ value) + { + auto raw = msclr::interop::marshal_as(value); + Impl()->setFillPattern(mbgl::style::PropertyValue( + mbgl::style::expression::Image(raw))); + } + + cli::array^ FillLayer::Translate::get() + { + auto& pv = Impl()->getFillTranslate(); + if (pv.isConstant()) + { + auto& arr = pv.asConstant(); + return gcnew cli::array { arr[0], arr[1] }; + } + return gcnew cli::array { 0.0f, 0.0f }; + } + + System::Void FillLayer::Translate::set(cli::array^ value) + { + if (value == nullptr || value->Length < 2) return; + std::array arr{ value[0], value[1] }; + Impl()->setFillTranslate(mbgl::style::PropertyValue>(arr)); + } + + TranslateAnchorType FillLayer::TranslateAnchor::get() + { + auto& pv = Impl()->getFillTranslateAnchor(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return TranslateAnchorType::Map; + } + + System::Void FillLayer::TranslateAnchor::set(TranslateAnchorType value) + { + Impl()->setFillTranslateAnchor(mbgl::style::PropertyValue( + static_cast(value))); + } + // ========================================================================= // LineLayer // ========================================================================= @@ -183,6 +240,114 @@ namespace DOTNET_NAMESPACE Impl()->setLineOffset(mbgl::style::PropertyValue(value)); } + LineCapType LineLayer::Cap::get() + { + auto& pv = Impl()->getLineCap(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return LineCapType::Butt; + } + + System::Void LineLayer::Cap::set(LineCapType value) + { + Impl()->setLineCap(mbgl::style::PropertyValue( + static_cast(value))); + } + + LineJoinType LineLayer::Join::get() + { + auto& pv = Impl()->getLineJoin(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return LineJoinType::Miter; + } + + System::Void LineLayer::Join::set(LineJoinType value) + { + Impl()->setLineJoin(mbgl::style::PropertyValue( + static_cast(value))); + } + + float LineLayer::SortKey::get() + { + return GetFloat(Impl()->getLineSortKey(), 0.0f); + } + + System::Void LineLayer::SortKey::set(float value) + { + Impl()->setLineSortKey(mbgl::style::PropertyValue(value)); + } + + cli::array^ LineLayer::Dasharray::get() + { + auto& pv = Impl()->getLineDasharray(); + if (pv.isConstant()) + { + const auto& vec = pv.asConstant(); + auto arr = gcnew cli::array(static_cast(vec.size())); + for (int i = 0; i < static_cast(vec.size()); ++i) + arr[i] = vec[i]; + return arr; + } + return gcnew cli::array(0); + } + + System::Void LineLayer::Dasharray::set(cli::array^ value) + { + std::vector vec; + if (value != nullptr) + for each (float v in value) + vec.push_back(v); + Impl()->setLineDasharray(mbgl::style::PropertyValue>(vec)); + } + + System::String^ LineLayer::Pattern::get() + { + auto& pv = Impl()->getLinePattern(); + if (pv.isConstant()) + return gcnew System::String(pv.asConstant().id().c_str()); + return System::String::Empty; + } + + System::Void LineLayer::Pattern::set(System::String^ value) + { + auto raw = msclr::interop::marshal_as(value); + Impl()->setLinePattern(mbgl::style::PropertyValue( + mbgl::style::expression::Image(raw))); + } + + cli::array^ LineLayer::Translate::get() + { + auto& pv = Impl()->getLineTranslate(); + if (pv.isConstant()) + { + auto& arr = pv.asConstant(); + return gcnew cli::array { arr[0], arr[1] }; + } + return gcnew cli::array { 0.0f, 0.0f }; + } + + System::Void LineLayer::Translate::set(cli::array^ value) + { + if (value == nullptr || value->Length < 2) return; + std::array arr{ value[0], value[1] }; + Impl()->setLineTranslate(mbgl::style::PropertyValue>(arr)); + } + + TranslateAnchorType LineLayer::TranslateAnchor::get() + { + auto& pv = Impl()->getLineTranslateAnchor(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return TranslateAnchorType::Map; + } + + System::Void LineLayer::TranslateAnchor::set(TranslateAnchorType value) + { + Impl()->setLineTranslateAnchor(mbgl::style::PropertyValue( + static_cast(value))); + } + // ========================================================================= // CircleLayer // ========================================================================= @@ -264,6 +429,76 @@ namespace DOTNET_NAMESPACE Impl()->setCircleBlur(mbgl::style::PropertyValue(value)); } + float CircleLayer::SortKey::get() + { + return GetFloat(Impl()->getCircleSortKey(), 0.0f); + } + + System::Void CircleLayer::SortKey::set(float value) + { + Impl()->setCircleSortKey(mbgl::style::PropertyValue(value)); + } + + CirclePitchScaleType CircleLayer::PitchScale::get() + { + auto& pv = Impl()->getCirclePitchScale(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return CirclePitchScaleType::Map; + } + + System::Void CircleLayer::PitchScale::set(CirclePitchScaleType value) + { + Impl()->setCirclePitchScale(mbgl::style::PropertyValue( + static_cast(value))); + } + + AlignmentType CircleLayer::PitchAlignment::get() + { + auto& pv = Impl()->getCirclePitchAlignment(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return AlignmentType::Viewport; + } + + System::Void CircleLayer::PitchAlignment::set(AlignmentType value) + { + Impl()->setCirclePitchAlignment(mbgl::style::PropertyValue( + static_cast(value))); + } + + cli::array^ CircleLayer::Translate::get() + { + auto& pv = Impl()->getCircleTranslate(); + if (pv.isConstant()) + { + auto& arr = pv.asConstant(); + return gcnew cli::array { arr[0], arr[1] }; + } + return gcnew cli::array { 0.0f, 0.0f }; + } + + System::Void CircleLayer::Translate::set(cli::array^ value) + { + if (value == nullptr || value->Length < 2) return; + std::array arr{ value[0], value[1] }; + Impl()->setCircleTranslate(mbgl::style::PropertyValue>(arr)); + } + + TranslateAnchorType CircleLayer::TranslateAnchor::get() + { + auto& pv = Impl()->getCircleTranslateAnchor(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return TranslateAnchorType::Map; + } + + System::Void CircleLayer::TranslateAnchor::set(TranslateAnchorType value) + { + Impl()->setCircleTranslateAnchor(mbgl::style::PropertyValue( + static_cast(value))); + } + // ========================================================================= // SymbolLayer // ========================================================================= @@ -275,24 +510,251 @@ namespace DOTNET_NAMESPACE return static_cast(_layer); } - System::String^ SymbolLayer::TextColor::get() + // ---- Layout: Symbol placement ---- + + SymbolPlacementType SymbolLayer::SymbolPlacement::get() { - return GetColor(Impl()->getTextColor(), "#000000"); + auto& pv = Impl()->getSymbolPlacement(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return SymbolPlacementType::Point; } - System::Void SymbolLayer::TextColor::set(System::String^ value) + System::Void SymbolLayer::SymbolPlacement::set(SymbolPlacementType value) { - Impl()->setTextColor(mbgl::style::PropertyValue(ParseColor(value))); + Impl()->setSymbolPlacement(mbgl::style::PropertyValue( + static_cast(value))); } - float SymbolLayer::TextOpacity::get() + float SymbolLayer::SymbolSpacing::get() { - return GetFloat(Impl()->getTextOpacity(), 1.0f); + return GetFloat(Impl()->getSymbolSpacing(), 250.0f); } - System::Void SymbolLayer::TextOpacity::set(float value) + System::Void SymbolLayer::SymbolSpacing::set(float value) { - Impl()->setTextOpacity(mbgl::style::PropertyValue(value)); + Impl()->setSymbolSpacing(mbgl::style::PropertyValue(value)); + } + + bool SymbolLayer::SymbolAvoidEdges::get() + { + return GetBool(Impl()->getSymbolAvoidEdges(), false); + } + + System::Void SymbolLayer::SymbolAvoidEdges::set(bool value) + { + Impl()->setSymbolAvoidEdges(mbgl::style::PropertyValue(value)); + } + + float SymbolLayer::SymbolSortKey::get() + { + return GetFloat(Impl()->getSymbolSortKey(), 0.0f); + } + + System::Void SymbolLayer::SymbolSortKey::set(float value) + { + Impl()->setSymbolSortKey(mbgl::style::PropertyValue(value)); + } + + SymbolZOrderType SymbolLayer::SymbolZOrder::get() + { + auto& pv = Impl()->getSymbolZOrder(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return SymbolZOrderType::Auto; + } + + System::Void SymbolLayer::SymbolZOrder::set(SymbolZOrderType value) + { + Impl()->setSymbolZOrder(mbgl::style::PropertyValue( + static_cast(value))); + } + + // ---- Layout: Icon ---- + + System::String^ SymbolLayer::IconImage::get() + { + auto& pv = Impl()->getIconImage(); + if (pv.isConstant()) + return gcnew System::String(pv.asConstant().id().c_str()); + return System::String::Empty; + } + + System::Void SymbolLayer::IconImage::set(System::String^ value) + { + auto raw = msclr::interop::marshal_as(value); + Impl()->setIconImage(mbgl::style::PropertyValue( + mbgl::style::expression::Image(raw))); + } + + float SymbolLayer::IconSize::get() + { + return GetFloat(Impl()->getIconSize(), 1.0f); + } + + System::Void SymbolLayer::IconSize::set(float value) + { + Impl()->setIconSize(mbgl::style::PropertyValue(value)); + } + + SymbolAnchorType SymbolLayer::IconAnchor::get() + { + auto& pv = Impl()->getIconAnchor(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return SymbolAnchorType::Center; + } + + System::Void SymbolLayer::IconAnchor::set(SymbolAnchorType value) + { + Impl()->setIconAnchor(mbgl::style::PropertyValue( + static_cast(value))); + } + + float SymbolLayer::IconRotate::get() + { + return GetFloat(Impl()->getIconRotate(), 0.0f); + } + + System::Void SymbolLayer::IconRotate::set(float value) + { + Impl()->setIconRotate(mbgl::style::PropertyValue(value)); + } + + cli::array^ SymbolLayer::IconOffset::get() + { + auto& pv = Impl()->getIconOffset(); + if (pv.isConstant()) + { + auto& arr = pv.asConstant(); + return gcnew cli::array { arr[0], arr[1] }; + } + return gcnew cli::array { 0.0f, 0.0f }; + } + + System::Void SymbolLayer::IconOffset::set(cli::array^ value) + { + if (value == nullptr || value->Length < 2) return; + std::array arr{ value[0], value[1] }; + Impl()->setIconOffset(mbgl::style::PropertyValue>(arr)); + } + + float SymbolLayer::IconPadding::get() + { + return GetFloat(Impl()->getIconPadding(), 2.0f); + } + + System::Void SymbolLayer::IconPadding::set(float value) + { + Impl()->setIconPadding(mbgl::style::PropertyValue(value)); + } + + bool SymbolLayer::IconKeepUpright::get() + { + return GetBool(Impl()->getIconKeepUpright(), false); + } + + System::Void SymbolLayer::IconKeepUpright::set(bool value) + { + Impl()->setIconKeepUpright(mbgl::style::PropertyValue(value)); + } + + bool SymbolLayer::IconAllowOverlap::get() + { + return GetBool(Impl()->getIconAllowOverlap(), false); + } + + System::Void SymbolLayer::IconAllowOverlap::set(bool value) + { + Impl()->setIconAllowOverlap(mbgl::style::PropertyValue(value)); + } + + bool SymbolLayer::IconIgnorePlacement::get() + { + return GetBool(Impl()->getIconIgnorePlacement(), false); + } + + System::Void SymbolLayer::IconIgnorePlacement::set(bool value) + { + Impl()->setIconIgnorePlacement(mbgl::style::PropertyValue(value)); + } + + bool SymbolLayer::IconOptional::get() + { + return GetBool(Impl()->getIconOptional(), false); + } + + System::Void SymbolLayer::IconOptional::set(bool value) + { + Impl()->setIconOptional(mbgl::style::PropertyValue(value)); + } + + AlignmentType SymbolLayer::IconRotationAlignment::get() + { + auto& pv = Impl()->getIconRotationAlignment(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return AlignmentType::Auto; + } + + System::Void SymbolLayer::IconRotationAlignment::set(AlignmentType value) + { + Impl()->setIconRotationAlignment(mbgl::style::PropertyValue( + static_cast(value))); + } + + AlignmentType SymbolLayer::IconPitchAlignment::get() + { + auto& pv = Impl()->getIconPitchAlignment(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return AlignmentType::Auto; + } + + System::Void SymbolLayer::IconPitchAlignment::set(AlignmentType value) + { + Impl()->setIconPitchAlignment(mbgl::style::PropertyValue( + static_cast(value))); + } + + // ---- Layout: Text ---- + + System::String^ SymbolLayer::TextField::get() + { + auto& pv = Impl()->getTextField(); + if (pv.isConstant()) + return gcnew System::String(pv.asConstant().toString().c_str()); + return System::String::Empty; + } + + System::Void SymbolLayer::TextField::set(System::String^ value) + { + auto raw = msclr::interop::marshal_as(value); + Impl()->setTextField(mbgl::style::PropertyValue( + mbgl::style::expression::Formatted(raw.c_str()))); + } + + cli::array^ SymbolLayer::TextFont::get() + { + auto& pv = Impl()->getTextFont(); + if (pv.isConstant()) + { + const auto& vec = pv.asConstant(); + auto arr = gcnew cli::array(static_cast(vec.size())); + for (int i = 0; i < static_cast(vec.size()); ++i) + arr[i] = gcnew System::String(vec[i].c_str()); + return arr; + } + return gcnew cli::array(0); + } + + System::Void SymbolLayer::TextFont::set(cli::array^ value) + { + std::vector vec; + if (value != nullptr) + for each (System::String^ s in value) + vec.push_back(msclr::interop::marshal_as(s)); + Impl()->setTextFont(mbgl::style::PropertyValue>(vec)); } float SymbolLayer::TextSize::get() @@ -305,6 +767,226 @@ namespace DOTNET_NAMESPACE Impl()->setTextSize(mbgl::style::PropertyValue(value)); } + float SymbolLayer::TextMaxWidth::get() + { + return GetFloat(Impl()->getTextMaxWidth(), 10.0f); + } + + System::Void SymbolLayer::TextMaxWidth::set(float value) + { + Impl()->setTextMaxWidth(mbgl::style::PropertyValue(value)); + } + + float SymbolLayer::TextLineHeight::get() + { + return GetFloat(Impl()->getTextLineHeight(), 1.2f); + } + + System::Void SymbolLayer::TextLineHeight::set(float value) + { + Impl()->setTextLineHeight(mbgl::style::PropertyValue(value)); + } + + float SymbolLayer::TextLetterSpacing::get() + { + return GetFloat(Impl()->getTextLetterSpacing(), 0.0f); + } + + System::Void SymbolLayer::TextLetterSpacing::set(float value) + { + Impl()->setTextLetterSpacing(mbgl::style::PropertyValue(value)); + } + + TextJustifyType SymbolLayer::TextJustify::get() + { + auto& pv = Impl()->getTextJustify(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return TextJustifyType::Center; + } + + System::Void SymbolLayer::TextJustify::set(TextJustifyType value) + { + Impl()->setTextJustify(mbgl::style::PropertyValue( + static_cast(value))); + } + + float SymbolLayer::TextRadialOffset::get() + { + return GetFloat(Impl()->getTextRadialOffset(), 0.0f); + } + + System::Void SymbolLayer::TextRadialOffset::set(float value) + { + Impl()->setTextRadialOffset(mbgl::style::PropertyValue(value)); + } + + SymbolAnchorType SymbolLayer::TextAnchor::get() + { + auto& pv = Impl()->getTextAnchor(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return SymbolAnchorType::Center; + } + + System::Void SymbolLayer::TextAnchor::set(SymbolAnchorType value) + { + Impl()->setTextAnchor(mbgl::style::PropertyValue( + static_cast(value))); + } + + float SymbolLayer::TextMaxAngle::get() + { + return GetFloat(Impl()->getTextMaxAngle(), 45.0f); + } + + System::Void SymbolLayer::TextMaxAngle::set(float value) + { + Impl()->setTextMaxAngle(mbgl::style::PropertyValue(value)); + } + + float SymbolLayer::TextRotate::get() + { + return GetFloat(Impl()->getTextRotate(), 0.0f); + } + + System::Void SymbolLayer::TextRotate::set(float value) + { + Impl()->setTextRotate(mbgl::style::PropertyValue(value)); + } + + float SymbolLayer::TextPadding::get() + { + return GetFloat(Impl()->getTextPadding(), 2.0f); + } + + System::Void SymbolLayer::TextPadding::set(float value) + { + Impl()->setTextPadding(mbgl::style::PropertyValue(value)); + } + + bool SymbolLayer::TextKeepUpright::get() + { + return GetBool(Impl()->getTextKeepUpright(), true); + } + + System::Void SymbolLayer::TextKeepUpright::set(bool value) + { + Impl()->setTextKeepUpright(mbgl::style::PropertyValue(value)); + } + + TextTransformType SymbolLayer::TextTransform::get() + { + auto& pv = Impl()->getTextTransform(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return TextTransformType::None; + } + + System::Void SymbolLayer::TextTransform::set(TextTransformType value) + { + Impl()->setTextTransform(mbgl::style::PropertyValue( + static_cast(value))); + } + + cli::array^ SymbolLayer::TextOffset::get() + { + auto& pv = Impl()->getTextOffset(); + if (pv.isConstant()) + { + auto& arr = pv.asConstant(); + return gcnew cli::array { arr[0], arr[1] }; + } + return gcnew cli::array { 0.0f, 0.0f }; + } + + System::Void SymbolLayer::TextOffset::set(cli::array^ value) + { + if (value == nullptr || value->Length < 2) return; + std::array arr{ value[0], value[1] }; + Impl()->setTextOffset(mbgl::style::PropertyValue>(arr)); + } + + bool SymbolLayer::TextAllowOverlap::get() + { + return GetBool(Impl()->getTextAllowOverlap(), false); + } + + System::Void SymbolLayer::TextAllowOverlap::set(bool value) + { + Impl()->setTextAllowOverlap(mbgl::style::PropertyValue(value)); + } + + bool SymbolLayer::TextIgnorePlacement::get() + { + return GetBool(Impl()->getTextIgnorePlacement(), false); + } + + System::Void SymbolLayer::TextIgnorePlacement::set(bool value) + { + Impl()->setTextIgnorePlacement(mbgl::style::PropertyValue(value)); + } + + bool SymbolLayer::TextOptional::get() + { + return GetBool(Impl()->getTextOptional(), false); + } + + System::Void SymbolLayer::TextOptional::set(bool value) + { + Impl()->setTextOptional(mbgl::style::PropertyValue(value)); + } + + AlignmentType SymbolLayer::TextRotationAlignment::get() + { + auto& pv = Impl()->getTextRotationAlignment(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return AlignmentType::Auto; + } + + System::Void SymbolLayer::TextRotationAlignment::set(AlignmentType value) + { + Impl()->setTextRotationAlignment(mbgl::style::PropertyValue( + static_cast(value))); + } + + AlignmentType SymbolLayer::TextPitchAlignment::get() + { + auto& pv = Impl()->getTextPitchAlignment(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return AlignmentType::Auto; + } + + System::Void SymbolLayer::TextPitchAlignment::set(AlignmentType value) + { + Impl()->setTextPitchAlignment(mbgl::style::PropertyValue( + static_cast(value))); + } + + // ---- Paint: Text ---- + + System::String^ SymbolLayer::TextColor::get() + { + return GetColor(Impl()->getTextColor(), "#000000"); + } + + System::Void SymbolLayer::TextColor::set(System::String^ value) + { + Impl()->setTextColor(mbgl::style::PropertyValue(ParseColor(value))); + } + + float SymbolLayer::TextOpacity::get() + { + return GetFloat(Impl()->getTextOpacity(), 1.0f); + } + + System::Void SymbolLayer::TextOpacity::set(float value) + { + Impl()->setTextOpacity(mbgl::style::PropertyValue(value)); + } + System::String^ SymbolLayer::TextHaloColor::get() { return GetColor(Impl()->getTextHaloColor(), "rgba(0,0,0,0)"); @@ -325,6 +1007,18 @@ namespace DOTNET_NAMESPACE Impl()->setTextHaloWidth(mbgl::style::PropertyValue(value)); } + float SymbolLayer::TextHaloBlur::get() + { + return GetFloat(Impl()->getTextHaloBlur(), 0.0f); + } + + System::Void SymbolLayer::TextHaloBlur::set(float value) + { + Impl()->setTextHaloBlur(mbgl::style::PropertyValue(value)); + } + + // ---- Paint: Icon ---- + float SymbolLayer::IconOpacity::get() { return GetFloat(Impl()->getIconOpacity(), 1.0f); @@ -365,6 +1059,16 @@ namespace DOTNET_NAMESPACE Impl()->setIconHaloWidth(mbgl::style::PropertyValue(value)); } + float SymbolLayer::IconHaloBlur::get() + { + return GetFloat(Impl()->getIconHaloBlur(), 0.0f); + } + + System::Void SymbolLayer::IconHaloBlur::set(float value) + { + Impl()->setIconHaloBlur(mbgl::style::PropertyValue(value)); + } + // ========================================================================= // RasterLayer // ========================================================================= diff --git a/src/Style.cpp b/src/Style.cpp index 3d415b3..bafdb02 100644 --- a/src/Style.cpp +++ b/src/Style.cpp @@ -321,6 +321,99 @@ namespace DOTNET_NAMESPACE return gcnew ColorReliefLayer(raw); } + // ------------------------------------------------------------------------- + // AddXxxLayer overloads with beforeLayerId + // ------------------------------------------------------------------------- + + FillLayer^ Style::AddFillLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer), std::optional(Convert::ToStdString(beforeLayerId))); + return gcnew FillLayer(raw); + } + + LineLayer^ Style::AddLineLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer), std::optional(Convert::ToStdString(beforeLayerId))); + return gcnew LineLayer(raw); + } + + CircleLayer^ Style::AddCircleLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer), std::optional(Convert::ToStdString(beforeLayerId))); + return gcnew CircleLayer(raw); + } + + SymbolLayer^ Style::AddSymbolLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer), std::optional(Convert::ToStdString(beforeLayerId))); + return gcnew SymbolLayer(raw); + } + + RasterLayer^ Style::AddRasterLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer), std::optional(Convert::ToStdString(beforeLayerId))); + return gcnew RasterLayer(raw); + } + + BackgroundLayer^ Style::AddBackgroundLayer(System::String^ layerId, System::String^ beforeLayerId) + { + auto layer = std::make_unique(Convert::ToStdString(layerId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer), std::optional(Convert::ToStdString(beforeLayerId))); + return gcnew BackgroundLayer(raw); + } + + HeatmapLayer^ Style::AddHeatmapLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer), std::optional(Convert::ToStdString(beforeLayerId))); + return gcnew HeatmapLayer(raw); + } + + HillshadeLayer^ Style::AddHillshadeLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer), std::optional(Convert::ToStdString(beforeLayerId))); + return gcnew HillshadeLayer(raw); + } + + FillExtrusionLayer^ Style::AddFillExtrusionLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer), std::optional(Convert::ToStdString(beforeLayerId))); + return gcnew FillExtrusionLayer(raw); + } + + ColorReliefLayer^ Style::AddColorReliefLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId) + { + auto layer = std::make_unique( + Convert::ToStdString(layerId), Convert::ToStdString(sourceId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer), std::optional(Convert::ToStdString(beforeLayerId))); + return gcnew ColorReliefLayer(raw); + } + // ------------------------------------------------------------------------- // Add source helpers // ------------------------------------------------------------------------- From 144edb251783dfcba791a5f77ecb802a4ea97dba Mon Sep 17 00:00:00 2001 From: acalcutt Date: Fri, 1 May 2026 11:16:23 -0400 Subject: [PATCH 47/63] Add remaining layer properties + ColorReliefLayer.ColorRamp - LineLayer: MiterLimit, RoundLimit - SymbolLayer paint: IconTextFit, IconTextFitPadding (array), IconTranslate, IconTranslateAnchor, TextTranslate, TextTranslateAnchor - RasterLayer: Resampling (RasterResamplingType) - BackgroundLayer: Pattern (sprite image) - HillshadeLayer: IlluminationAnchor (HillshadeIlluminationAnchorType) - FillExtrusionLayer: Pattern, Translate, TranslateAnchor - ColorReliefLayer: ColorRamp (JSON expression string via ColorRampPropertyValue) - Enums.h: RasterResamplingType, HillshadeIlluminationAnchorType - Layers.cpp: rapidjson + ValueToJsonString helpers for ColorRamp serialization --- include/Enums.h | 14 +++ include/Layers.h | 70 +++++++++++ src/Layers.cpp | 295 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 379 insertions(+) diff --git a/include/Enums.h b/include/Enums.h index e733b7b..0dbc233 100644 --- a/include/Enums.h +++ b/include/Enums.h @@ -179,4 +179,18 @@ namespace DOTNET_NAMESPACE Horizontal, Vertical, }; + + /// Resampling method for raster tiles when scaling. + public enum class RasterResamplingType : System::Byte + { + Linear, + Nearest, + }; + + /// Frame of reference for the hillshade illumination direction. + public enum class HillshadeIlluminationAnchorType : System::Byte + { + Map, + Viewport, + }; } diff --git a/include/Layers.h b/include/Layers.h index 7d8e03e..88e82da 100644 --- a/include/Layers.h +++ b/include/Layers.h @@ -143,6 +143,12 @@ namespace DOTNET_NAMESPACE /// Join style at corners of lines. property LineJoinType Join { LineJoinType get(); System::Void set(LineJoinType); } + /// Limit at which a miter join is converted to a bevel join. Default: 2. + property float MiterLimit { float get(); System::Void set(float); } + + /// Limit at which a round join is converted to a bevel join. Default: 1.05. + property float RoundLimit { float get(); System::Void set(float); } + /// Sort key for feature rendering order within the layer. property float SortKey { float get(); System::Void set(float); } @@ -445,6 +451,31 @@ namespace DOTNET_NAMESPACE /// Icon halo blur in pixels. property float IconHaloBlur { float get(); System::Void set(float); } + + /// + /// Scales the icon to fit around the associated text. + /// None = icon not scaled, Both = icon scaled to fit text in both axes, + /// Width/Height = scaled in one axis only. + /// + property IconTextFitType IconTextFit { IconTextFitType get(); System::Void set(IconTextFitType); } + + /// + /// Padding applied to each side of the icon bounding box when using IconTextFit + /// as [top, right, bottom, left] in ems. Returns {0,0,0,0} when unset. + /// + property cli::array^ IconTextFitPadding { cli::array^ get(); System::Void set(cli::array^); } + + /// X/Y translation of icons in pixels as [x, y]. + property cli::array^ IconTranslate { cli::array^ get(); System::Void set(cli::array^); } + + /// Frame of reference for IconTranslate: Map or Viewport. + property TranslateAnchorType IconTranslateAnchor { TranslateAnchorType get(); System::Void set(TranslateAnchorType); } + + /// X/Y translation of text in pixels as [x, y]. + property cli::array^ TextTranslate { cli::array^ get(); System::Void set(cli::array^); } + + /// Frame of reference for TextTranslate: Map or Viewport. + property TranslateAnchorType TextTranslateAnchor { TranslateAnchorType get(); System::Void set(TranslateAnchorType); } }; // ========================================================================= @@ -509,6 +540,9 @@ namespace DOTNET_NAMESPACE float get(); System::Void set(float); } + + /// Resampling method used when scaling raster tiles. Default: Linear. + property RasterResamplingType Resampling { RasterResamplingType get(); System::Void set(RasterResamplingType); } }; // ========================================================================= @@ -538,6 +572,9 @@ namespace DOTNET_NAMESPACE float get(); System::Void set(float); } + + /// Sprite image name for a repeating background pattern. Empty string = solid color. + property System::String^ Pattern { System::String^ get(); System::Void set(System::String^); } }; // ========================================================================= @@ -631,6 +668,17 @@ namespace DOTNET_NAMESPACE System::String^ get(); System::Void set(System::String^); } + + /// + /// Frame of reference for the illumination direction: Map keeps the + /// light direction fixed as the map rotates; Viewport keeps it fixed + /// relative to the viewport. + /// + property HillshadeIlluminationAnchorType IlluminationAnchor + { + HillshadeIlluminationAnchorType get(); + System::Void set(HillshadeIlluminationAnchorType); + } }; // ========================================================================= @@ -681,6 +729,15 @@ namespace DOTNET_NAMESPACE bool get(); System::Void set(bool); } + + /// Sprite image name for a repeating fill-extrusion pattern. Empty string = solid color. + property System::String^ Pattern { System::String^ get(); System::Void set(System::String^); } + + /// X/Y translation of the fill extrusion in pixels as [x, y]. + property cli::array^ Translate { cli::array^ get(); System::Void set(cli::array^); } + + /// Frame of reference for Translate: Map or Viewport. + property TranslateAnchorType TranslateAnchor { TranslateAnchorType get(); System::Void set(TranslateAnchorType); } }; // ========================================================================= @@ -703,5 +760,18 @@ namespace DOTNET_NAMESPACE float get(); System::Void set(float); } + + /// + /// Color ramp as a MapLibre expression JSON string that maps elevation + /// (raster-value) to a color. Example: + /// ["interpolate",["linear"],["raster-value"],0,"#000080",3000,"#ffffff"] + /// Returns an empty string when unset. + /// Throws ArgumentException on invalid expression JSON. + /// + property System::String^ ColorRamp + { + System::String^ get(); + System::Void set(System::String^); + } }; } diff --git a/src/Layers.cpp b/src/Layers.cpp index 3925e78..feb9bd3 100644 --- a/src/Layers.cpp +++ b/src/Layers.cpp @@ -11,9 +11,15 @@ #include #include #include +#include +#include #include +#include #include +#include +#include + namespace { // ------------------------------------------------------------------------- @@ -57,6 +63,56 @@ namespace { return pv.isConstant() ? ColorToString(pv.asConstant()) : def; } + + // ------------------------------------------------------------------------- + // Helpers: serialize mbgl::Value to JSON string (used for expression props) + // ------------------------------------------------------------------------- + static void WriteValue(rapidjson::Writer& writer, + const mbgl::Value& value); + + struct ValueWriter + { + rapidjson::Writer& writer; + void operator()(const mbgl::NullValue&) const { writer.Null(); } + void operator()(bool v) const { writer.Bool(v); } + void operator()(uint64_t v) const { writer.Uint64(v); } + void operator()(int64_t v) const { writer.Int64(v); } + void operator()(double v) const { writer.Double(v); } + void operator()(const std::string& v) const + { + writer.String(v.c_str(), static_cast(v.size())); + } + void operator()(const mapbox::util::recursive_wrapper>& rw) const + { + writer.StartArray(); + for (const auto& item : rw.get()) WriteValue(writer, item); + writer.EndArray(); + } + void operator()(const mapbox::util::recursive_wrapper>& rw) const + { + writer.StartObject(); + for (const auto& kv : rw.get()) + { + writer.Key(kv.first.c_str(), static_cast(kv.first.size())); + WriteValue(writer, kv.second); + } + writer.EndObject(); + } + }; + + static void WriteValue(rapidjson::Writer& writer, + const mbgl::Value& value) + { + mbgl::Value::visit(value, ValueWriter{ writer }); + } + + static std::string ValueToJsonString(const mbgl::Value& value) + { + rapidjson::StringBuffer buf; + rapidjson::Writer writer(buf); + WriteValue(writer, value); + return buf.GetString(); + } } namespace DOTNET_NAMESPACE @@ -268,6 +324,26 @@ namespace DOTNET_NAMESPACE static_cast(value))); } + float LineLayer::MiterLimit::get() + { + return GetFloat(Impl()->getLineMiterLimit(), 2.0f); + } + + System::Void LineLayer::MiterLimit::set(float value) + { + Impl()->setLineMiterLimit(mbgl::style::PropertyValue(value)); + } + + float LineLayer::RoundLimit::get() + { + return GetFloat(Impl()->getLineRoundLimit(), 1.05f); + } + + System::Void LineLayer::RoundLimit::set(float value) + { + Impl()->setLineRoundLimit(mbgl::style::PropertyValue(value)); + } + float LineLayer::SortKey::get() { return GetFloat(Impl()->getLineSortKey(), 0.0f); @@ -1069,6 +1145,102 @@ namespace DOTNET_NAMESPACE Impl()->setIconHaloBlur(mbgl::style::PropertyValue(value)); } + IconTextFitType SymbolLayer::IconTextFit::get() + { + auto& pv = Impl()->getIconTextFit(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return IconTextFitType::None; + } + + System::Void SymbolLayer::IconTextFit::set(IconTextFitType value) + { + Impl()->setIconTextFit(mbgl::style::PropertyValue( + static_cast(value))); + } + + cli::array^ SymbolLayer::IconTextFitPadding::get() + { + auto& pv = Impl()->getIconTextFitPadding(); + if (pv.isConstant()) + { + auto& arr = pv.asConstant(); + return gcnew cli::array { arr[0], arr[1], arr[2], arr[3] }; + } + return gcnew cli::array { 0.0f, 0.0f, 0.0f, 0.0f }; + } + + System::Void SymbolLayer::IconTextFitPadding::set(cli::array^ value) + { + if (value == nullptr || value->Length < 4) return; + std::array arr{ value[0], value[1], value[2], value[3] }; + Impl()->setIconTextFitPadding(mbgl::style::PropertyValue>(arr)); + } + + cli::array^ SymbolLayer::IconTranslate::get() + { + auto& pv = Impl()->getIconTranslate(); + if (pv.isConstant()) + { + auto& arr = pv.asConstant(); + return gcnew cli::array { arr[0], arr[1] }; + } + return gcnew cli::array { 0.0f, 0.0f }; + } + + System::Void SymbolLayer::IconTranslate::set(cli::array^ value) + { + if (value == nullptr || value->Length < 2) return; + std::array arr{ value[0], value[1] }; + Impl()->setIconTranslate(mbgl::style::PropertyValue>(arr)); + } + + TranslateAnchorType SymbolLayer::IconTranslateAnchor::get() + { + auto& pv = Impl()->getIconTranslateAnchor(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return TranslateAnchorType::Map; + } + + System::Void SymbolLayer::IconTranslateAnchor::set(TranslateAnchorType value) + { + Impl()->setIconTranslateAnchor(mbgl::style::PropertyValue( + static_cast(value))); + } + + cli::array^ SymbolLayer::TextTranslate::get() + { + auto& pv = Impl()->getTextTranslate(); + if (pv.isConstant()) + { + auto& arr = pv.asConstant(); + return gcnew cli::array { arr[0], arr[1] }; + } + return gcnew cli::array { 0.0f, 0.0f }; + } + + System::Void SymbolLayer::TextTranslate::set(cli::array^ value) + { + if (value == nullptr || value->Length < 2) return; + std::array arr{ value[0], value[1] }; + Impl()->setTextTranslate(mbgl::style::PropertyValue>(arr)); + } + + TranslateAnchorType SymbolLayer::TextTranslateAnchor::get() + { + auto& pv = Impl()->getTextTranslateAnchor(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return TranslateAnchorType::Map; + } + + System::Void SymbolLayer::TextTranslateAnchor::set(TranslateAnchorType value) + { + Impl()->setTextTranslateAnchor(mbgl::style::PropertyValue( + static_cast(value))); + } + // ========================================================================= // RasterLayer // ========================================================================= @@ -1150,6 +1322,20 @@ namespace DOTNET_NAMESPACE Impl()->setRasterFadeDuration(mbgl::style::PropertyValue(value)); } + RasterResamplingType RasterLayer::Resampling::get() + { + auto& pv = Impl()->getRasterResampling(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return RasterResamplingType::Linear; + } + + System::Void RasterLayer::Resampling::set(RasterResamplingType value) + { + Impl()->setRasterResampling(mbgl::style::PropertyValue( + static_cast(value))); + } + // ========================================================================= // BackgroundLayer // ========================================================================= @@ -1181,6 +1367,21 @@ namespace DOTNET_NAMESPACE Impl()->setBackgroundOpacity(mbgl::style::PropertyValue(value)); } + System::String^ BackgroundLayer::Pattern::get() + { + auto& pv = Impl()->getBackgroundPattern(); + if (pv.isConstant()) + return gcnew System::String(pv.asConstant().id().c_str()); + return System::String::Empty; + } + + System::Void BackgroundLayer::Pattern::set(System::String^ value) + { + auto raw = msclr::interop::marshal_as(value); + Impl()->setBackgroundPattern(mbgl::style::PropertyValue( + mbgl::style::expression::Image(raw))); + } + // ========================================================================= // HeatmapLayer // ========================================================================= @@ -1293,6 +1494,21 @@ namespace DOTNET_NAMESPACE Impl()->setHillshadeAccentColor(mbgl::style::PropertyValue(ParseColor(value))); } + HillshadeIlluminationAnchorType HillshadeLayer::IlluminationAnchor::get() + { + auto& pv = Impl()->getHillshadeIlluminationAnchor(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return HillshadeIlluminationAnchorType::Viewport; + } + + System::Void HillshadeLayer::IlluminationAnchor::set(HillshadeIlluminationAnchorType value) + { + Impl()->setHillshadeIlluminationAnchor( + mbgl::style::PropertyValue( + static_cast(value))); + } + // ========================================================================= // FillExtrusionLayer // ========================================================================= @@ -1354,6 +1570,53 @@ namespace DOTNET_NAMESPACE Impl()->setFillExtrusionVerticalGradient(mbgl::style::PropertyValue(value)); } + System::String^ FillExtrusionLayer::Pattern::get() + { + auto& pv = Impl()->getFillExtrusionPattern(); + if (pv.isConstant()) + return gcnew System::String(pv.asConstant().id().c_str()); + return System::String::Empty; + } + + System::Void FillExtrusionLayer::Pattern::set(System::String^ value) + { + auto raw = msclr::interop::marshal_as(value); + Impl()->setFillExtrusionPattern(mbgl::style::PropertyValue( + mbgl::style::expression::Image(raw))); + } + + cli::array^ FillExtrusionLayer::Translate::get() + { + auto& pv = Impl()->getFillExtrusionTranslate(); + if (pv.isConstant()) + { + auto& arr = pv.asConstant(); + return gcnew cli::array { arr[0], arr[1] }; + } + return gcnew cli::array { 0.0f, 0.0f }; + } + + System::Void FillExtrusionLayer::Translate::set(cli::array^ value) + { + if (value == nullptr || value->Length < 2) return; + std::array arr{ value[0], value[1] }; + Impl()->setFillExtrusionTranslate(mbgl::style::PropertyValue>(arr)); + } + + TranslateAnchorType FillExtrusionLayer::TranslateAnchor::get() + { + auto& pv = Impl()->getFillExtrusionTranslateAnchor(); + if (pv.isConstant()) + return static_cast(pv.asConstant()); + return TranslateAnchorType::Map; + } + + System::Void FillExtrusionLayer::TranslateAnchor::set(TranslateAnchorType value) + { + Impl()->setFillExtrusionTranslateAnchor(mbgl::style::PropertyValue( + static_cast(value))); + } + // ========================================================================= // ColorReliefLayer // ========================================================================= @@ -1374,4 +1637,36 @@ namespace DOTNET_NAMESPACE { Impl()->setColorReliefOpacity(mbgl::style::PropertyValue(value)); } + + System::String^ ColorReliefLayer::ColorRamp::get() + { + const auto& pv = Impl()->getColorReliefColor(); + if (!pv.isUndefined()) + { + mbgl::Value serialized = pv.getExpression().serialize(); + return msclr::interop::marshal_as(ValueToJsonString(serialized)); + } + return System::String::Empty; + } + + System::Void ColorReliefLayer::ColorRamp::set(System::String^ value) + { + if (value == nullptr || value->Length == 0) + return; + + std::string json = msclr::interop::marshal_as(value); + mbgl::style::conversion::JSDocument doc; + doc.Parse(json.c_str()); + if (doc.HasParseError()) + throw gcnew System::ArgumentException("Invalid color ramp JSON: parse error"); + + mbgl::style::conversion::Error error; + auto colorRamp = mbgl::style::conversion::convert( + mbgl::style::conversion::Convertible(&doc), error); + if (!colorRamp) + throw gcnew System::ArgumentException( + msclr::interop::marshal_as("Invalid color ramp expression: " + error.message)); + + Impl()->setColorReliefColor(*colorRamp); + } } From 8d8138d2f203cedc1e4fa8536b8eaef760c61ebe Mon Sep 17 00:00:00 2001 From: acalcutt Date: Fri, 1 May 2026 12:13:02 -0400 Subject: [PATCH 48/63] Update windows-ci.yml --- .github/workflows/windows-ci.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index 1e586e1..90f2ffd 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -2,13 +2,7 @@ name: Windows CI on: pull_request: - branches: - - main - - master push: - branches: - - main - - master jobs: build: From 7ae788193405ccbe8e03f7281e9bb9ad70d9a3ae Mon Sep 17 00:00:00 2001 From: acalcutt Date: Fri, 1 May 2026 12:23:58 -0400 Subject: [PATCH 49/63] Add Tier 1 parity: LocationIndicatorLayer, GeoJSONOptions, Source prefetch/overscale - LocationIndicatorLayer (new): BearingImage, ShadowImage, TopImage (layout); AccuracyRadius, AccuracyRadiusBorderColor, AccuracyRadiusColor, Bearing, BearingImageSize, ImageTiltDisplacement, Location (lat/lng/alt double[3]), PerspectiveCompensation, ShadowImageSize, TopImageSize (paint) - GeoJSONOptions managed ref class: MinZoom, MaxZoom, TileSize, Buffer, Tolerance, LineMetrics, Cluster, ClusterRadius, ClusterMaxZoom - Style::AddGeoJsonSource / AddGeoJsonSourceFromUrl overloads with GeoJSONOptions - Source::PrefetchZoomDelta / MaxOverscaleFactorForParentTiles (get/set, -1 = clear) --- include/Layers.h | 117 +++++++++++++++++++++++++++++++++ include/Source.h | 20 ++++++ include/Sources.h | 45 +++++++++++++ include/Style.h | 36 ++++++----- src/Layers.cpp | 161 ++++++++++++++++++++++++++++++++++++++++++++++ src/Source.cpp | 28 ++++++++ src/Style.cpp | 58 ++++++++++++++++- 7 files changed, 447 insertions(+), 18 deletions(-) diff --git a/include/Layers.h b/include/Layers.h index 88e82da..1a3daae 100644 --- a/include/Layers.h +++ b/include/Layers.h @@ -15,6 +15,7 @@ class HeatmapLayer; class HillshadeLayer; class FillExtrusionLayer; class ColorReliefLayer; +class LocationIndicatorLayer; } // namespace style } // namespace mbgl @@ -774,4 +775,120 @@ namespace DOTNET_NAMESPACE System::Void set(System::String^); } }; + + // ========================================================================= + // LocationIndicatorLayer + // ========================================================================= + + /// + /// Wraps an mbgl::style::LocationIndicatorLayer (the "blue dot" user-location + /// indicator layer). No source is required. + /// + public ref class LocationIndicatorLayer : public Layer + { + internal: + LocationIndicatorLayer(mbgl::style::LocationIndicatorLayer* layer); + + private: + mbgl::style::LocationIndicatorLayer* Impl(); + + public: + // ---- Layout properties ------------------------------------------ + + /// Name of the image to use as the bearing arrow (sprite id). + property System::String^ BearingImage + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Name of the image to use as the shadow beneath the indicator (sprite id). + property System::String^ ShadowImage + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Name of the image to use as the top of the location indicator (sprite id). + property System::String^ TopImage + { + System::String^ get(); + System::Void set(System::String^); + } + + // ---- Paint properties ------------------------------------------- + + /// Radius of the accuracy circle in metres. + property float AccuracyRadius + { + float get(); + System::Void set(float); + } + + /// Border color of the accuracy circle. + property System::String^ AccuracyRadiusBorderColor + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Fill color of the accuracy circle. + property System::String^ AccuracyRadiusColor + { + System::String^ get(); + System::Void set(System::String^); + } + + /// Bearing of the location indicator in degrees (0 = north, clockwise). + property float Bearing + { + float get(); + System::Void set(float); + } + + /// Scale factor of the BearingImage (default 1.0). + property float BearingImageSize + { + float get(); + System::Void set(float); + } + + /// Displacement of the top image along the tilt in pixels (default 0). + property float ImageTiltDisplacement + { + float get(); + System::Void set(float); + } + + /// + /// Geographic position of the indicator as [latitude, longitude, altitude]. + /// Returns an array of three doubles. + /// + property cli::array^ Location + { + cli::array^ get(); + System::Void set(cli::array^); + } + + /// Perspective compensation factor (0 = no compensation, 1 = full; default 0.85). + property float PerspectiveCompensation + { + float get(); + System::Void set(float); + } + + /// Scale factor of the ShadowImage (default 1.0). + property float ShadowImageSize + { + float get(); + System::Void set(float); + } + + /// Scale factor of the TopImage (default 1.0). + property float TopImageSize + { + float get(); + System::Void set(float); + } + }; } diff --git a/include/Source.h b/include/Source.h index 1ec82b5..3309e55 100644 --- a/include/Source.h +++ b/include/Source.h @@ -40,5 +40,25 @@ namespace DOTNET_NAMESPACE bool get(); System::Void set(bool); } + + /// + /// Number of zoom levels to prefetch when requesting tiles. + /// Pass a negative value to clear the override and use the global setting. + /// + property System::Int32 PrefetchZoomDelta + { + System::Int32 get(); + System::Void set(System::Int32); + } + + /// + /// Maximum overscale factor for parent tiles used when a tile is not yet + /// available. Pass a negative value to clear the override. + /// + property System::Int32 MaxOverscaleFactorForParentTiles + { + System::Int32 get(); + System::Void set(System::Int32); + } }; } diff --git a/include/Sources.h b/include/Sources.h index 8cde043..3fcdc82 100644 --- a/include/Sources.h +++ b/include/Sources.h @@ -2,6 +2,51 @@ #include "Source.h" #include "LatLng.h" +namespace DOTNET_NAMESPACE +{ + /// + /// Options that control tiling and clustering behaviour of a GeoJSONSource. + /// All options must be specified at construction time via + /// Style::AddGeoJsonSource(sourceId, options). + /// + public ref class GeoJSONOptions + { + public: + /// Minimum zoom at which tiles are generated (default 0). + property System::Byte MinZoom; + /// Maximum zoom at which tiles are generated (default 18). + property System::Byte MaxZoom; + /// Tile size in pixels (default 512). + property System::UInt16 TileSize; + /// Tile buffer size in pixels (default 128). + property System::UInt16 Buffer; + /// Douglas-Peucker simplification tolerance (default 0.375). + property System::Double Tolerance; + /// Whether to calculate line distance metrics (default false). + property System::Boolean LineMetrics; + + /// Whether to cluster point features (default false). + property System::Boolean Cluster; + /// Cluster radius in pixels (default 50). + property System::UInt16 ClusterRadius; + /// Maximum zoom at which clusters are generated (default 17). + property System::Byte ClusterMaxZoom; + + GeoJSONOptions() + { + MinZoom = 0; + MaxZoom = 18; + TileSize = 512; + Buffer = 128; + Tolerance = 0.375; + LineMetrics = false; + Cluster = false; + ClusterRadius = 50; + ClusterMaxZoom = 17; + } + }; +} + // Forward-declare native source types namespace mbgl { namespace style { diff --git a/include/Style.h b/include/Style.h index c7712a6..c56b224 100644 --- a/include/Style.h +++ b/include/Style.h @@ -70,23 +70,25 @@ namespace DOTNET_NAMESPACE CircleLayer^ AddCircleLayer(System::String^ layerId, System::String^ sourceId); SymbolLayer^ AddSymbolLayer(System::String^ layerId, System::String^ sourceId); RasterLayer^ AddRasterLayer(System::String^ layerId, System::String^ sourceId); - BackgroundLayer^ AddBackgroundLayer(System::String^ layerId); - HeatmapLayer^ AddHeatmapLayer(System::String^ layerId, System::String^ sourceId); - HillshadeLayer^ AddHillshadeLayer(System::String^ layerId, System::String^ sourceId); - FillExtrusionLayer^ AddFillExtrusionLayer(System::String^ layerId, System::String^ sourceId); - ColorReliefLayer^ AddColorReliefLayer(System::String^ layerId, System::String^ sourceId); + BackgroundLayer^ AddBackgroundLayer(System::String^ layerId); + HeatmapLayer^ AddHeatmapLayer(System::String^ layerId, System::String^ sourceId); + HillshadeLayer^ AddHillshadeLayer(System::String^ layerId, System::String^ sourceId); + FillExtrusionLayer^ AddFillExtrusionLayer(System::String^ layerId, System::String^ sourceId); + ColorReliefLayer^ AddColorReliefLayer(System::String^ layerId, System::String^ sourceId); + LocationIndicatorLayer^ AddLocationIndicatorLayer(System::String^ layerId); /// Same as the two-arg overloads but inserts the layer before . - FillLayer^ AddFillLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); - LineLayer^ AddLineLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); - CircleLayer^ AddCircleLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); - SymbolLayer^ AddSymbolLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); - RasterLayer^ AddRasterLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); - BackgroundLayer^ AddBackgroundLayer(System::String^ layerId, System::String^ beforeLayerId); - HeatmapLayer^ AddHeatmapLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); - HillshadeLayer^ AddHillshadeLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); - FillExtrusionLayer^ AddFillExtrusionLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); - ColorReliefLayer^ AddColorReliefLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); + FillLayer^ AddFillLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); + LineLayer^ AddLineLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); + CircleLayer^ AddCircleLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); + SymbolLayer^ AddSymbolLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); + RasterLayer^ AddRasterLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); + BackgroundLayer^ AddBackgroundLayer(System::String^ layerId, System::String^ beforeLayerId); + HeatmapLayer^ AddHeatmapLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); + HillshadeLayer^ AddHillshadeLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); + FillExtrusionLayer^ AddFillExtrusionLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); + ColorReliefLayer^ AddColorReliefLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId); + LocationIndicatorLayer^ AddLocationIndicatorLayer(System::String^ layerId, System::String^ beforeLayerId); /// Remove a layer by id. Returns true if removed. System::Boolean RemoveLayer(System::String^ layerId); @@ -98,8 +100,12 @@ namespace DOTNET_NAMESPACE // ------------------------------------------------------------------------- GeoJSONSource^ AddGeoJsonSource(System::String^ sourceId); + /// Add a GeoJSON source with explicit tile/cluster options. + GeoJSONSource^ AddGeoJsonSource(System::String^ sourceId, GeoJSONOptions^ options); /// Add a GeoJSON source that immediately loads from a URL. GeoJSONSource^ AddGeoJsonSourceFromUrl(System::String^ sourceId, System::String^ url); + /// Add a GeoJSON source from a URL with explicit tile/cluster options. + GeoJSONSource^ AddGeoJsonSourceFromUrl(System::String^ sourceId, System::String^ url, GeoJSONOptions^ options); VectorSource^ AddVectorSource(System::String^ sourceId, System::String^ url); RasterSource^ AddRasterSource(System::String^ sourceId, System::String^ url, System::UInt16 tileSize); RasterDEMSource^ AddRasterDemSource(System::String^ sourceId, System::String^ url, System::UInt16 tileSize); diff --git a/src/Layers.cpp b/src/Layers.cpp index feb9bd3..cb5aeb4 100644 --- a/src/Layers.cpp +++ b/src/Layers.cpp @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include #include #include @@ -1669,4 +1671,163 @@ namespace DOTNET_NAMESPACE Impl()->setColorReliefColor(*colorRamp); } + + // ========================================================================= + // LocationIndicatorLayer + // ========================================================================= + + LocationIndicatorLayer::LocationIndicatorLayer(mbgl::style::LocationIndicatorLayer* layer) : Layer(layer) {} + + mbgl::style::LocationIndicatorLayer* LocationIndicatorLayer::Impl() + { + return static_cast(_layer); + } + + // ---- Layout: image sprite names ---------------------------------------- + + System::String^ LocationIndicatorLayer::BearingImage::get() + { + const auto& pv = Impl()->getBearingImage(); + if (pv.isConstant()) return msclr::interop::marshal_as(pv.asConstant().id()); + return System::String::Empty; + } + System::Void LocationIndicatorLayer::BearingImage::set(System::String^ value) + { + std::string raw = msclr::interop::marshal_as(value ? value : System::String::Empty); + Impl()->setBearingImage(mbgl::style::PropertyValue( + mbgl::style::expression::Image(raw))); + } + + System::String^ LocationIndicatorLayer::ShadowImage::get() + { + const auto& pv = Impl()->getShadowImage(); + if (pv.isConstant()) return msclr::interop::marshal_as(pv.asConstant().id()); + return System::String::Empty; + } + System::Void LocationIndicatorLayer::ShadowImage::set(System::String^ value) + { + std::string raw = msclr::interop::marshal_as(value ? value : System::String::Empty); + Impl()->setShadowImage(mbgl::style::PropertyValue( + mbgl::style::expression::Image(raw))); + } + + System::String^ LocationIndicatorLayer::TopImage::get() + { + const auto& pv = Impl()->getTopImage(); + if (pv.isConstant()) return msclr::interop::marshal_as(pv.asConstant().id()); + return System::String::Empty; + } + System::Void LocationIndicatorLayer::TopImage::set(System::String^ value) + { + std::string raw = msclr::interop::marshal_as(value ? value : System::String::Empty); + Impl()->setTopImage(mbgl::style::PropertyValue( + mbgl::style::expression::Image(raw))); + } + + // ---- Paint: floats ----------------------------------------------------- + + float LocationIndicatorLayer::AccuracyRadius::get() + { + return GetFloat(Impl()->getAccuracyRadius(), 0.0f); + } + System::Void LocationIndicatorLayer::AccuracyRadius::set(float value) + { + Impl()->setAccuracyRadius(mbgl::style::PropertyValue(value)); + } + + float LocationIndicatorLayer::Bearing::get() + { + const auto& pv = Impl()->getBearing(); + if (pv.isConstant()) return (float)pv.asConstant().getAngle(); + return 0.0f; + } + System::Void LocationIndicatorLayer::Bearing::set(float value) + { + Impl()->setBearing(mbgl::style::PropertyValue( + mbgl::style::Rotation((double)value))); + } + + float LocationIndicatorLayer::BearingImageSize::get() + { + return GetFloat(Impl()->getBearingImageSize(), 1.0f); + } + System::Void LocationIndicatorLayer::BearingImageSize::set(float value) + { + Impl()->setBearingImageSize(mbgl::style::PropertyValue(value)); + } + + float LocationIndicatorLayer::ImageTiltDisplacement::get() + { + return GetFloat(Impl()->getImageTiltDisplacement(), 0.0f); + } + System::Void LocationIndicatorLayer::ImageTiltDisplacement::set(float value) + { + Impl()->setImageTiltDisplacement(mbgl::style::PropertyValue(value)); + } + + float LocationIndicatorLayer::PerspectiveCompensation::get() + { + return GetFloat(Impl()->getPerspectiveCompensation(), 0.85f); + } + System::Void LocationIndicatorLayer::PerspectiveCompensation::set(float value) + { + Impl()->setPerspectiveCompensation(mbgl::style::PropertyValue(value)); + } + + float LocationIndicatorLayer::ShadowImageSize::get() + { + return GetFloat(Impl()->getShadowImageSize(), 1.0f); + } + System::Void LocationIndicatorLayer::ShadowImageSize::set(float value) + { + Impl()->setShadowImageSize(mbgl::style::PropertyValue(value)); + } + + float LocationIndicatorLayer::TopImageSize::get() + { + return GetFloat(Impl()->getTopImageSize(), 1.0f); + } + System::Void LocationIndicatorLayer::TopImageSize::set(float value) + { + Impl()->setTopImageSize(mbgl::style::PropertyValue(value)); + } + + // ---- Paint: colors ----------------------------------------------------- + + System::String^ LocationIndicatorLayer::AccuracyRadiusBorderColor::get() + { + return ColorToString(GetColor(Impl()->getAccuracyRadiusBorderColor(), mbgl::Color(0, 0, 0, 0))); + } + System::Void LocationIndicatorLayer::AccuracyRadiusBorderColor::set(System::String^ value) + { + Impl()->setAccuracyRadiusBorderColor(mbgl::style::PropertyValue(ParseColor(value))); + } + + System::String^ LocationIndicatorLayer::AccuracyRadiusColor::get() + { + return ColorToString(GetColor(Impl()->getAccuracyRadiusColor(), mbgl::Color(0, 0, 0, 0))); + } + System::Void LocationIndicatorLayer::AccuracyRadiusColor::set(System::String^ value) + { + Impl()->setAccuracyRadiusColor(mbgl::style::PropertyValue(ParseColor(value))); + } + + // ---- Paint: location (lat/lng/alt) ------------------------------------- + + cli::array^ LocationIndicatorLayer::Location::get() + { + const auto& pv = Impl()->getLocation(); + if (pv.isConstant()) + { + const auto& arr = pv.asConstant(); + return gcnew cli::array { arr[0], arr[1], arr[2] }; + } + return gcnew cli::array { 0.0, 0.0, 0.0 }; + } + System::Void LocationIndicatorLayer::Location::set(cli::array^ value) + { + if (value == nullptr || value->Length < 3) return; + std::array arr{ value[0], value[1], value[2] }; + Impl()->setLocation(mbgl::style::PropertyValue>(arr)); + } } diff --git a/src/Source.cpp b/src/Source.cpp index 301c4e4..fb686a3 100644 --- a/src/Source.cpp +++ b/src/Source.cpp @@ -45,4 +45,32 @@ namespace DOTNET_NAMESPACE { _source->setVolatile(value); } + + System::Int32 Source::PrefetchZoomDelta::get() + { + auto val = _source->getPrefetchZoomDelta(); + return val.has_value() ? (System::Int32)*val : -1; + } + + System::Void Source::PrefetchZoomDelta::set(System::Int32 value) + { + if (value < 0) + _source->setPrefetchZoomDelta(std::nullopt); + else + _source->setPrefetchZoomDelta(std::optional((uint8_t)value)); + } + + System::Int32 Source::MaxOverscaleFactorForParentTiles::get() + { + auto val = _source->getMaxOverscaleFactorForParentTiles(); + return val.has_value() ? (System::Int32)*val : -1; + } + + System::Void Source::MaxOverscaleFactorForParentTiles::set(System::Int32 value) + { + if (value < 0) + _source->setMaxOverscaleFactorForParentTiles(std::nullopt); + else + _source->setMaxOverscaleFactorForParentTiles(std::optional((uint8_t)value)); + } } diff --git a/src/Style.cpp b/src/Style.cpp index bafdb02..ab3a416 100644 --- a/src/Style.cpp +++ b/src/Style.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -170,6 +171,7 @@ namespace DOTNET_NAMESPACE if (auto* p = raw->as()) return gcnew HillshadeLayer(p); if (auto* p = raw->as()) return gcnew FillExtrusionLayer(p); if (auto* p = raw->as()) return gcnew ColorReliefLayer(p); + if (auto* p = raw->as()) return gcnew LocationIndicatorLayer(p); // Unknown type — return base Layer wrapper return gcnew Layer(raw); } @@ -321,9 +323,13 @@ namespace DOTNET_NAMESPACE return gcnew ColorReliefLayer(raw); } - // ------------------------------------------------------------------------- - // AddXxxLayer overloads with beforeLayerId - // ------------------------------------------------------------------------- + LocationIndicatorLayer^ Style::AddLocationIndicatorLayer(System::String^ layerId) + { + auto layer = std::make_unique(Convert::ToStdString(layerId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer)); + return gcnew LocationIndicatorLayer(raw); + } FillLayer^ Style::AddFillLayer(System::String^ layerId, System::String^ sourceId, System::String^ beforeLayerId) { @@ -414,6 +420,14 @@ namespace DOTNET_NAMESPACE return gcnew ColorReliefLayer(raw); } + LocationIndicatorLayer^ Style::AddLocationIndicatorLayer(System::String^ layerId, System::String^ beforeLayerId) + { + auto layer = std::make_unique(Convert::ToStdString(layerId)); + auto* raw = layer.get(); + NativePointer->addLayer(std::move(layer), std::optional(Convert::ToStdString(beforeLayerId))); + return gcnew LocationIndicatorLayer(raw); + } + // ------------------------------------------------------------------------- // Add source helpers // ------------------------------------------------------------------------- @@ -426,6 +440,32 @@ namespace DOTNET_NAMESPACE return gcnew GeoJSONSource(raw); } + static mbgl::style::GeoJSONOptions BuildGeoJSONOptions(GeoJSONOptions^ opts) + { + mbgl::style::GeoJSONOptions o; + o.minzoom = opts->MinZoom; + o.maxzoom = opts->MaxZoom; + o.tileSize = opts->TileSize; + o.buffer = opts->Buffer; + o.tolerance = opts->Tolerance; + o.lineMetrics = opts->LineMetrics; + o.cluster = opts->Cluster; + o.clusterRadius = opts->ClusterRadius; + o.clusterMaxZoom = opts->ClusterMaxZoom; + return o; + } + + GeoJSONSource^ Style::AddGeoJsonSource(System::String^ sourceId, GeoJSONOptions^ options) + { + auto nativeOpts = mbgl::Immutable( + mbgl::makeMutable(BuildGeoJSONOptions(options))); + auto src = std::make_unique( + Convert::ToStdString(sourceId), nativeOpts); + auto* raw = src.get(); + NativePointer->addSource(std::move(src)); + return gcnew GeoJSONSource(raw); + } + GeoJSONSource^ Style::AddGeoJsonSourceFromUrl(System::String^ sourceId, System::String^ url) { auto src = std::make_unique(Convert::ToStdString(sourceId)); @@ -435,6 +475,18 @@ namespace DOTNET_NAMESPACE return gcnew GeoJSONSource(raw); } + GeoJSONSource^ Style::AddGeoJsonSourceFromUrl(System::String^ sourceId, System::String^ url, GeoJSONOptions^ options) + { + auto nativeOpts = mbgl::Immutable( + mbgl::makeMutable(BuildGeoJSONOptions(options))); + auto src = std::make_unique( + Convert::ToStdString(sourceId), nativeOpts); + src->setURL(Convert::ToStdString(url)); + auto* raw = src.get(); + NativePointer->addSource(std::move(src)); + return gcnew GeoJSONSource(raw); + } + VectorSource^ Style::AddVectorSource(System::String^ sourceId, System::String^ url) { auto src = std::make_unique( From cf870a434bfedcd2c487ef8d7f9565b3956002f2 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Fri, 1 May 2026 12:31:09 -0400 Subject: [PATCH 50/63] Add Tier 2 parity: LineLayer.Gradient, HeatmapLayer.Color, ImageSource.SetImage - LineLayer.Gradient (ColorRampPropertyValue JSON string): maps line progress (0-1) to a color; requires lineMetrics=true on the GeoJSON source - HeatmapLayer.Color (ColorRampPropertyValue JSON string): maps heatmap density (0-1) to a color (replaces the default rainbow ramp) - ImageSource.SetImage(PremultipliedImage^): update pixel data at runtime for animated image overlays --- include/Layers.h | 25 ++++++++++++++++++--- include/Sources.h | 7 ++++++ src/Layers.cpp | 57 +++++++++++++++++++++++++++++++++++++++++++++-- src/Sources.cpp | 7 ++++++ 4 files changed, 91 insertions(+), 5 deletions(-) diff --git a/include/Layers.h b/include/Layers.h index 1a3daae..37050a9 100644 --- a/include/Layers.h +++ b/include/Layers.h @@ -167,10 +167,18 @@ namespace DOTNET_NAMESPACE /// Frame of reference for Translate: Map or Viewport. property TranslateAnchorType TranslateAnchor { TranslateAnchorType get(); System::Void set(TranslateAnchorType); } - }; - // ========================================================================= - // CircleLayer + /// + /// Line gradient as a MapLibre expression JSON string (requires lineMetrics=true on the source). + /// Maps line progress (0–1) to a color. Returns empty string when unset. + /// Throws ArgumentException on invalid expression JSON. + /// + property System::String^ Gradient + { + System::String^ get(); + System::Void set(System::String^); + } + }; // ========================================================================= /// Wraps an mbgl::style::CircleLayer. @@ -619,6 +627,17 @@ namespace DOTNET_NAMESPACE float get(); System::Void set(float); } + + /// + /// Color ramp as a MapLibre expression JSON string that maps heatmap density (0–1) to a color. + /// Returns empty string when unset (uses the default rainbow ramp). + /// Throws ArgumentException on invalid expression JSON. + /// + property System::String^ Color + { + System::String^ get(); + System::Void set(System::String^); + } }; // ========================================================================= diff --git a/include/Sources.h b/include/Sources.h index 3fcdc82..1795003 100644 --- a/include/Sources.h +++ b/include/Sources.h @@ -1,6 +1,7 @@ #pragma once #include "Source.h" #include "LatLng.h" +#include "PremultipliedImage.h" namespace DOTNET_NAMESPACE { @@ -187,5 +188,11 @@ namespace DOTNET_NAMESPACE array^ get(); System::Void set(array^); } + + /// + /// Update the pixel data of the image source at runtime. + /// The image is cloned internally; the caller retains ownership of . + /// + System::Void SetImage(PremultipliedImage^ image); }; } diff --git a/src/Layers.cpp b/src/Layers.cpp index cb5aeb4..d9a2526 100644 --- a/src/Layers.cpp +++ b/src/Layers.cpp @@ -426,6 +426,34 @@ namespace DOTNET_NAMESPACE static_cast(value))); } + System::String^ LineLayer::Gradient::get() + { + const auto& pv = Impl()->getLineGradient(); + if (!pv.isUndefined()) + { + mbgl::Value serialized = pv.getExpression().serialize(); + return msclr::interop::marshal_as(ValueToJsonString(serialized)); + } + return System::String::Empty; + } + + System::Void LineLayer::Gradient::set(System::String^ value) + { + if (value == nullptr || value->Length == 0) return; + std::string json = msclr::interop::marshal_as(value); + mbgl::style::conversion::JSDocument doc; + doc.Parse(json.c_str()); + if (doc.HasParseError()) + throw gcnew System::ArgumentException("Invalid gradient JSON: parse error"); + mbgl::style::conversion::Error error; + auto gradient = mbgl::style::conversion::convert( + mbgl::style::conversion::Convertible(&doc), error); + if (!gradient) + throw gcnew System::ArgumentException( + msclr::interop::marshal_as("Invalid gradient expression: " + error.message)); + Impl()->setLineGradient(*gradient); + } + // ========================================================================= // CircleLayer // ========================================================================= @@ -1435,8 +1463,33 @@ namespace DOTNET_NAMESPACE Impl()->setHeatmapWeight(mbgl::style::PropertyValue(value)); } - // ========================================================================= - // HillshadeLayer + System::String^ HeatmapLayer::Color::get() + { + const auto& pv = Impl()->getHeatmapColor(); + if (!pv.isUndefined()) + { + mbgl::Value serialized = pv.getExpression().serialize(); + return msclr::interop::marshal_as(ValueToJsonString(serialized)); + } + return System::String::Empty; + } + + System::Void HeatmapLayer::Color::set(System::String^ value) + { + if (value == nullptr || value->Length == 0) return; + std::string json = msclr::interop::marshal_as(value); + mbgl::style::conversion::JSDocument doc; + doc.Parse(json.c_str()); + if (doc.HasParseError()) + throw gcnew System::ArgumentException("Invalid heatmap color JSON: parse error"); + mbgl::style::conversion::Error error; + auto colorRamp = mbgl::style::conversion::convert( + mbgl::style::conversion::Convertible(&doc), error); + if (!colorRamp) + throw gcnew System::ArgumentException( + msclr::interop::marshal_as("Invalid heatmap color expression: " + error.message)); + Impl()->setHeatmapColor(*colorRamp); + } // ========================================================================= HillshadeLayer::HillshadeLayer(mbgl::style::HillshadeLayer* layer) : Layer(layer) {} diff --git a/src/Sources.cpp b/src/Sources.cpp index 7bddbe6..afc5f2b 100644 --- a/src/Sources.cpp +++ b/src/Sources.cpp @@ -1,5 +1,6 @@ #include "pch.h" #include "Sources.h" +#include "PremultipliedImage.h" #include #include @@ -163,4 +164,10 @@ namespace DOTNET_NAMESPACE }; Impl()->setCoordinates(coords); } + + System::Void ImageSource::SetImage(PremultipliedImage^ image) + { + if (image == nullptr) return; + Impl()->setImage(image->NativePointer->clone()); + } } From 3d9094ae03f2aa265d0bc5bc8ce06377401ea2fc Mon Sep 17 00:00:00 2001 From: acalcutt Date: Fri, 1 May 2026 12:35:57 -0400 Subject: [PATCH 51/63] Add formatting: .editorconfig, format.ps1, normalize src/ and include/ - .editorconfig: 4-space indent, CRLF, UTF-8 for *.cpp and *.h; dependencies/** explicitly excluded - format.ps1: runnable script that converts leading tabs->4-spaces and strips trailing whitespace in src/ and include/ only; usage: powershell -ExecutionPolicy Bypass -File format.ps1 [-DryRun] - Applied to 40 files: tabs->spaces (Projection.cpp/h and others), trailing whitespace cleanup throughout --- .editorconfig | 39 +++++++++ format.ps1 | 83 +++++++++++++++++++ include/AnimationOptions.h | 8 +- include/BoundOptions.h | 4 +- include/CameraOptions.h | 4 +- include/EdgeInsets.h | 4 +- include/FileSource.h | 10 +-- include/FileSourceManager.h | 10 +-- include/FreeCameraOptions.h | 6 +- include/Image.h | 8 +- include/LatLng.h | 4 +- include/Map.h | 22 ++--- include/MapObserver.h | 4 +- include/OverscaledTileID.h | 4 +- include/PremultipliedImage.h | 4 +- include/Projection.h | 70 ++++++++-------- include/RendererBackend.h | 4 +- include/Resource.h | 6 +- include/ResourceOptions.h | 6 +- include/ResourceTransform.h | 6 +- include/TransformState.h | 4 +- include/UnwrappedTileID.h | 4 +- include/Vector.h | 4 +- src/AnimationOptions.cpp | 4 +- src/BoundOptions.cpp | 4 +- src/CameraOptions.cpp | 12 +-- src/ClientOptions.cpp | 4 +- src/FileSourceManager.cpp | 4 +- src/FreeCameraOptions.cpp | 4 +- src/HeadlessFrontend.cpp | 8 +- src/Image.cpp | 6 +- src/Map.cpp | 8 +- src/MapObserver.cpp | 4 +- src/MapOptions.cpp | 12 +-- src/OverscaledTileID.cpp | 4 +- src/Projection.cpp | 152 +++++++++++++++++------------------ src/ProjectionMode.cpp | 4 +- src/RendererObserver.cpp | 4 +- src/ResourceOptions.cpp | 10 +-- src/Response.cpp | 8 +- src/Size.cpp | 4 +- src/Value.cpp | 10 +-- 42 files changed, 353 insertions(+), 231 deletions(-) create mode 100644 .editorconfig create mode 100644 format.ps1 diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..efabb98 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,39 @@ +root = true + +# All files +[*] +charset = utf-8 +end_of_line = crlf +insert_final_newline = true +trim_trailing_whitespace = true + +# C++/CLI source and headers +[*.{cpp,h,hpp}] +indent_style = space +indent_size = 4 + +# CMake +[CMakeLists.txt] +indent_style = space +indent_size = 2 + +[*.cmake] +indent_style = space +indent_size = 2 + +# PowerShell +[*.ps1] +indent_style = space +indent_size = 4 + +# JSON / YAML +[*.{json,yml,yaml}] +indent_style = space +indent_size = 2 + +# Dependencies are managed externally — do not reformat +[dependencies/**] +indent_style = unset +indent_size = unset +trim_trailing_whitespace = false +insert_final_newline = false diff --git a/format.ps1 b/format.ps1 new file mode 100644 index 0000000..d648702 --- /dev/null +++ b/format.ps1 @@ -0,0 +1,83 @@ +<# +.SYNOPSIS + Normalizes indentation in all C++/CLI source and header files under + src/ and include/. + +.DESCRIPTION + For each .cpp and .h file found under src/ and include/: + - Converts leading tabs to 4 spaces (per indent level) + - Strips trailing whitespace from every line + - Ensures the file ends with a single newline + - Writes files only when changes are needed (avoids unnecessary + git diffs) + + The dependencies/ directory is never touched. + +.PARAMETER DryRun + Print which files would be changed without writing anything. + +.EXAMPLE + .\format.ps1 + .\format.ps1 -DryRun +#> +param( + [switch]$DryRun +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$root = $PSScriptRoot +$dirs = @( + Join-Path $root 'src' + Join-Path $root 'include' +) + +$changed = 0 +$unchanged = 0 + +foreach ($dir in $dirs) { + if (-not (Test-Path $dir)) { continue } + + Get-ChildItem -Path $dir -Recurse -Include '*.cpp', '*.h' | ForEach-Object { + $file = $_.FullName + + # Read as raw text to preserve line endings during analysis + $raw = [System.IO.File]::ReadAllText($file) + + # Normalise CRLF -> LF for processing, then restore at the end + $lines = $raw -replace "`r`n", "`n" -replace "`r", "`n" -split "`n" + + $newLines = $lines | ForEach-Object { + $line = $_ + + # Expand leading tabs: replace each tab with 4 spaces + # (handles mixed indent correctly by expanding one tab at a time) + while ($line -match "^( *)`t") { + $line = $line -replace "^( *)`t", ('$1' + ' ') + } + + # Strip trailing whitespace + $line.TrimEnd() + } + + # Ensure single trailing newline + $newContent = ($newLines -join "`r`n").TrimEnd() + "`r`n" + + if ($newContent -ne $raw) { + if ($DryRun) { + Write-Host "Would format: $($_.Name)" + } else { + [System.IO.File]::WriteAllText($file, $newContent, [System.Text.Encoding]::UTF8) + Write-Host "Formatted: $($_.Name)" + } + $changed++ + } else { + $unchanged++ + } + } +} + +$action = if ($DryRun) { 'Would change' } else { 'Changed' } +Write-Host "" +Write-Host "$action $changed file(s), $unchanged already clean." diff --git a/include/AnimationOptions.h b/include/AnimationOptions.h index d67bbeb..3bc265f 100644 --- a/include/AnimationOptions.h +++ b/include/AnimationOptions.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "NativeWrapper.h" #include #include @@ -17,7 +17,7 @@ namespace DOTNET_NAMESPACE using TransitionFrameFunction_ = TransitionFrameFunction; using TransitionFinishFunction_ = TransitionFinishFunction; - + class NativeTransitionFunctionsHelper { public: @@ -56,14 +56,14 @@ namespace DOTNET_NAMESPACE /// /// Average velocity of a flyTo() transition, measured in screenfuls per /// second, assuming a linear timing curve. - /// + /// /// A screenful is the visible span in pixels. It does not correspond /// to a fixed physical distance but rather varies by zoom level. /// property System::Nullable Velocity { System::Nullable get(); System::Void set(System::Nullable value); } /// - /// Zero-based zoom level at the peak of the flyTo() transitions flight + /// Zero-based zoom level at the peak of the flyTo() transition�s flight /// path. /// property System::Nullable MinZoom { System::Nullable get(); System::Void set(System::Nullable value); } diff --git a/include/BoundOptions.h b/include/BoundOptions.h index f34d8ad..311201b 100644 --- a/include/BoundOptions.h +++ b/include/BoundOptions.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "NativeWrapper.h" namespace mbgl @@ -39,7 +39,7 @@ namespace DOTNET_NAMESPACE /// /// BoundOptions^ WithMaxZoom(System::Double z); - + /// /// Sets the minimum pitch /// diff --git a/include/CameraOptions.h b/include/CameraOptions.h index 60848a9..2d3ff96 100644 --- a/include/CameraOptions.h +++ b/include/CameraOptions.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "NativeWrapper.h" #include "Point.h" @@ -11,7 +11,7 @@ namespace DOTNET_NAMESPACE { ref class EdgeInsets; ref class LatLng; - + /// /// Various options for describing the viewpoint of a map. All properties are /// optional. diff --git a/include/EdgeInsets.h b/include/EdgeInsets.h index cc5905a..fe24d6f 100644 --- a/include/EdgeInsets.h +++ b/include/EdgeInsets.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "NativeWrapper.h" #include "Point.h" @@ -47,7 +47,7 @@ namespace DOTNET_NAMESPACE System::Void operator+=(EdgeInsets^ o); EdgeInsets^ operator+(EdgeInsets^ o); - + static System::Boolean operator==(EdgeInsets^ a, EdgeInsets^ b); static System::Boolean operator!=(EdgeInsets^ a, EdgeInsets^ b); internal: diff --git a/include/FileSource.h b/include/FileSource.h index c7124f7..dcd15d5 100644 --- a/include/FileSource.h +++ b/include/FileSource.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "NativeWrapper.h" #include #include @@ -22,7 +22,7 @@ namespace DOTNET_NAMESPACE using ClientOptions_ = ClientOptions; using ResourceOptions_ = ResourceOptions; - + public ref class FileSource : NativeWrapper> { public: @@ -30,7 +30,7 @@ namespace DOTNET_NAMESPACE ~FileSource(); !FileSource(); - + /// /// Request a resource. The callback will be called asynchronously, in the /// same thread as the request was made. This thread must have an active @@ -73,7 +73,7 @@ namespace DOTNET_NAMESPACE /// /// Pause file request activity. - /// + /// /// If pause is called then no revalidation or network request activity /// will occur. /// @@ -82,7 +82,7 @@ namespace DOTNET_NAMESPACE /// /// Resume file request activity. - /// + /// /// Calling resume will unpause the file source and process any tasks that /// expired while the file source was paused. /// diff --git a/include/FileSourceManager.h b/include/FileSourceManager.h index 7e0bd23..c6b5351 100644 --- a/include/FileSourceManager.h +++ b/include/FileSourceManager.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "NativeWrapper.h" namespace DOTNET_NAMESPACE @@ -6,12 +6,12 @@ namespace DOTNET_NAMESPACE ref class ClientOptions; ref class FileSource; ref class ResourceOptions; - + /// /// A singleton class responsible for managing file sources. - /// + /// /// The FileSourceManager provides following functionality: - /// + /// /// - provides access to file sources of a specific type and configuration /// - caches previously created file sources of a (type, configuration) tuples /// @@ -42,4 +42,4 @@ namespace DOTNET_NAMESPACE FileSourceManager(); ~FileSourceManager(); }; -} \ No newline at end of file +} diff --git a/include/FreeCameraOptions.h b/include/FreeCameraOptions.h index 2eb61ab..19f1caa 100644 --- a/include/FreeCameraOptions.h +++ b/include/FreeCameraOptions.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "NativeWrapper.h" namespace mbgl @@ -33,7 +33,7 @@ namespace DOTNET_NAMESPACE /// /// System::Void LookAtPoint(LatLng^ location); - + /// /// Helper function for setting orientation of the camera by defining a /// focus point on the map. Up vector is required in certain scenarios where @@ -69,7 +69,7 @@ namespace DOTNET_NAMESPACE /// The default pose of the camera is such that the forward vector is /// looking up the -Z axis and the up vector is aligned with north /// orientation of the map: forward [0, 0, -1], up [0, -1, 0], right [1, 0, 0] - /// + /// /// Orientation can be set freely but certain constraints still apply /// - Pitch has an upper limit /// diff --git a/include/Image.h b/include/Image.h index 5117f2d..5c30bd1 100644 --- a/include/Image.h +++ b/include/Image.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "Convert.h" #include "NativeWrapper.h" #include "Size.h" @@ -43,11 +43,11 @@ namespace DOTNET_NAMESPACE ImageBase(Size_^ size) : NativeWrapper>(CreateImage(size)) { } - + ImageBase(Size_^ size, cli::array^ srcData) : NativeWrapper>(CreateImage(size, srcData)) { } - + virtual ~ImageBase() { } @@ -70,7 +70,7 @@ namespace DOTNET_NAMESPACE cli::array^ result = gcnew cli::array(static_cast(this->NativePointer->bytes())); System::Runtime::InteropServices::Marshal::Copy(System::IntPtr(this->NativePointer->data.get()), result, 0, result->Length); - + return result; } diff --git a/include/LatLng.h b/include/LatLng.h index 59b98c6..852106c 100644 --- a/include/LatLng.h +++ b/include/LatLng.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "NativeWrapper.h" namespace mbgl @@ -12,7 +12,7 @@ namespace DOTNET_NAMESPACE { ref class CanonicalTileID; ref class UnwrappedTileID; - + public ref class LatLng : NativeWrapper { public: diff --git a/include/Map.h b/include/Map.h index 95aa714..f683c52 100644 --- a/include/Map.h +++ b/include/Map.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "Enums.h" #include "NativeWrapper.h" #include "Point.h" @@ -35,7 +35,7 @@ namespace DOTNET_NAMESPACE using ProjectionMode_ = ProjectionMode; using Style_ = Style; using TransformState_ = TransformState; - + public enum class MapDebugOptions : System::UInt32 { NoDebug = 0, @@ -47,7 +47,7 @@ namespace DOTNET_NAMESPACE StencilClip = 1 << 6, DepthBuffer = 1 << 7, }; - + public ref class Map : NativeWrapper { public: @@ -87,43 +87,43 @@ namespace DOTNET_NAMESPACE System::Void PitchBy(System::Double pitch, AnimationOptions^ animation); System::Void RotateBy(ScreenCoordinate first, ScreenCoordinate second); System::Void RotateBy(ScreenCoordinate first, ScreenCoordinate second, AnimationOptions^ animation); - + CameraOptions^ CameraForLatLngBounds( LatLngBounds^ bounds, EdgeInsets^ padding ); - + CameraOptions^ CameraForLatLngBounds( LatLngBounds^ bounds, EdgeInsets^ padding, System::Nullable bearing ); - + CameraOptions^ CameraForLatLngBounds( LatLngBounds^ bounds, EdgeInsets^ padding, System::Nullable bearing, System::Nullable pitch ); - + CameraOptions^ CameraForLatLngs( System::Collections::Generic::IEnumerable^ latLngs, EdgeInsets^ padding ); - + CameraOptions^ CameraForLatLngs( System::Collections::Generic::IEnumerable^ latLngs, EdgeInsets^ padding, System::Nullable bearing ); - + CameraOptions^ CameraForLatLngs( System::Collections::Generic::IEnumerable^ latLngs, EdgeInsets^ padding, System::Nullable bearing, System::Nullable pitch ); - + LatLngBounds^ LatLngBoundsForCamera(CameraOptions^ camera); LatLngBounds^ LatLngBoundsForCameraUnwrapped(CameraOptions^ camera); System::Void SetNorthOrientation(NorthOrientation orientation); @@ -174,7 +174,7 @@ namespace DOTNET_NAMESPACE property System::Double TileLodPitchThreshold { System::Double get(); System::Void set(System::Double value); } property System::Double TileLodZoomShift { System::Double get(); System::Void set(System::Double value); } property TileLodMode TileLodMode_ { TileLodMode get(); System::Void set(TileLodMode value); } - + /// /// FreeCameraOptions provides more direct access to the underlying camera /// entity. For backwards compatibility the state set using this API must be diff --git a/include/MapObserver.h b/include/MapObserver.h index 2488629..6d16f9a 100644 --- a/include/MapObserver.h +++ b/include/MapObserver.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "Convert.h" #include "Enums.h" #include "NativeWrapper.h" @@ -64,7 +64,7 @@ namespace DOTNET_NAMESPACE delegate System::Void SourceChangedHandler(System::String^ id, SourceType type); delegate System::Void DidBecomeIdleHandler(); delegate System::Void StyleImageMissingHandler(System::String^ id); - + /// /// This event handler should return true if unused image can be removed, /// false otherwise. By default, unused image will be removed. diff --git a/include/OverscaledTileID.h b/include/OverscaledTileID.h index 4d5c7ef..d2e98a7 100644 --- a/include/OverscaledTileID.h +++ b/include/OverscaledTileID.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "NativeWrapper.h" namespace mbgl @@ -10,7 +10,7 @@ namespace DOTNET_NAMESPACE { ref class CanonicalTileID; ref class UnwrappedTileID; - + /// /// Has integer z/x/y coordinates /// overscaledZ describes the zoom level this tile is intented to represent, e.g. diff --git a/include/PremultipliedImage.h b/include/PremultipliedImage.h index c2fa5dc..ec1667d 100644 --- a/include/PremultipliedImage.h +++ b/include/PremultipliedImage.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "Image.h" namespace DOTNET_NAMESPACE @@ -39,7 +39,7 @@ namespace DOTNET_NAMESPACE /// move data within a single Image. /// static System::Void Copy(PremultipliedImage^ srcImg, PremultipliedImage^ dstImg, PointUInt srcPt, PointUInt dstPt, Size_^ size); - + static PremultipliedImage^ DecodeImage(System::String^ data); static cli::array^ EncodePNG(PremultipliedImage^ image); internal: diff --git a/include/Projection.h b/include/Projection.h index 0e4e362..6727586 100644 --- a/include/Projection.h +++ b/include/Projection.h @@ -1,49 +1,49 @@ -#pragma once +#pragma once #include "NativeWrapper.h" #include "LatLng.h" #include "Point.h" namespace mbgl { - class ProjectedMeters; + class ProjectedMeters; } namespace DOTNET_NAMESPACE { - ref class ProjectedMeters; - - public ref class Projection abstract sealed - { - public: - /// - /// Map pixel width at given scale. - /// - /// - /// - static System::Double WorldSize(System::Double scale); + ref class ProjectedMeters; - static System::Double GetMetersPerPixelAtLatitude(System::Double lat, System::Double zoom); - static ProjectedMeters^ ProjectedMetersForLatLng(LatLng^ latLng); - static LatLng^ LatLngForProjectedMeters(ProjectedMeters^ projectedMeters); - static PointDouble Project(LatLng^ latLng, System::Double scale); - static PointDouble Project(LatLng^ latLng, System::Int32 zoom); - static LatLng^ Unproject(PointDouble p, System::Double scale); - static LatLng^ Unproject(PointDouble p, System::Double scale, LatLng::WrapMode wrapMode); - }; + public ref class Projection abstract sealed + { + public: + /// + /// Map pixel width at given scale. + /// + /// + /// + static System::Double WorldSize(System::Double scale); - public ref class ProjectedMeters : NativeWrapper - { - public: - ProjectedMeters(System::Double northing); - ProjectedMeters(System::Double northing, System::Double easting); - ~ProjectedMeters(); + static System::Double GetMetersPerPixelAtLatitude(System::Double lat, System::Double zoom); + static ProjectedMeters^ ProjectedMetersForLatLng(LatLng^ latLng); + static LatLng^ LatLngForProjectedMeters(ProjectedMeters^ projectedMeters); + static PointDouble Project(LatLng^ latLng, System::Double scale); + static PointDouble Project(LatLng^ latLng, System::Int32 zoom); + static LatLng^ Unproject(PointDouble p, System::Double scale); + static LatLng^ Unproject(PointDouble p, System::Double scale, LatLng::WrapMode wrapMode); + }; - property System::Double Northing { System::Double get(); } - property System::Double Easting { System::Double get(); } + public ref class ProjectedMeters : NativeWrapper + { + public: + ProjectedMeters(System::Double northing); + ProjectedMeters(System::Double northing, System::Double easting); + ~ProjectedMeters(); - static System::Boolean operator==(ProjectedMeters^ a, ProjectedMeters^ b); - static System::Boolean operator!=(ProjectedMeters^ a, ProjectedMeters^ b); - internal: - ProjectedMeters(NativePointerHolder^ nativePointerHolder); - }; -} \ No newline at end of file + property System::Double Northing { System::Double get(); } + property System::Double Easting { System::Double get(); } + + static System::Boolean operator==(ProjectedMeters^ a, ProjectedMeters^ b); + static System::Boolean operator!=(ProjectedMeters^ a, ProjectedMeters^ b); + internal: + ProjectedMeters(NativePointerHolder^ nativePointerHolder); + }; +} diff --git a/include/RendererBackend.h b/include/RendererBackend.h index a423915..0c62c56 100644 --- a/include/RendererBackend.h +++ b/include/RendererBackend.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "NativeWrapper.h" namespace mbgl @@ -12,7 +12,7 @@ namespace mbgl namespace DOTNET_NAMESPACE { ref class RendererBackend; - + public interface class IRendererBackend { System::IntPtr GetNativePointer(); diff --git a/include/Resource.h b/include/Resource.h index 0141abe..23312ae 100644 --- a/include/Resource.h +++ b/include/Resource.h @@ -1,11 +1,11 @@ -#pragma once +#pragma once #include "NativeWrapper.h" #include namespace DOTNET_NAMESPACE { using FontStack = System::Collections::Generic::IEnumerable; - + public ref class Resource : NativeWrapper { public: @@ -69,7 +69,7 @@ namespace DOTNET_NAMESPACE static Resource^ Style(System::String^ url); static Resource^ Source(System::String^ url); - + static Resource^ Tile( System::String^ urlTemplate, System::Single pixelRatio, diff --git a/include/ResourceOptions.h b/include/ResourceOptions.h index 49e599e..98ff07b 100644 --- a/include/ResourceOptions.h +++ b/include/ResourceOptions.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "NativeWrapper.h" namespace mbgl @@ -29,7 +29,7 @@ namespace DOTNET_NAMESPACE /// Mapbox access token. /// ResourceOptions for chaining options together. ResourceOptions^ WithApiKey(System::String^ token); - + // TODO: implement the managed version //ResourceOptions^ WithTileServerOptions(TileServerOptions tileServerOptions); @@ -71,7 +71,7 @@ namespace DOTNET_NAMESPACE /// Gets the previously set (or default) Mapbox access token. /// property System::String^ ApiKey { System::String^ get(); } - + // TODO: implement the managed version //property TileServerOptions TileServerOptions { TileServerOptions get(); } diff --git a/include/ResourceTransform.h b/include/ResourceTransform.h index 042a135..6094bb3 100644 --- a/include/ResourceTransform.h +++ b/include/ResourceTransform.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "NativeWrapper.h" #include "Resource.h" #include @@ -10,7 +10,7 @@ namespace DOTNET_NAMESPACE { public delegate System::Void ResourceTransformFinishedCallback(System::String^ url); public delegate System::Void ResourceTransformCallback(Resource::ResourceKind kind, System::String^ url, ResourceTransformFinishedCallback^ finishedCallback); - + class ResourceTransformNativeCallbackHelper; public ref class ResourceTransform : NativeWrapper @@ -36,7 +36,7 @@ namespace DOTNET_NAMESPACE ~ResourceTransformNativeCallbackHelper(); void NativeTransformCallbackHandler(mbgl::Resource::Kind kind, const std::string& url, mbgl::ResourceTransform::FinishedCallback callback); - + mbgl::ResourceTransform::FinishedCallback FinishedCallback; private: msclr::gcroot _ResourceTransform; diff --git a/include/TransformState.h b/include/TransformState.h index abfec06..4c17c0b 100644 --- a/include/TransformState.h +++ b/include/TransformState.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "Enums.h" #include "LatLng.h" #include "NativeWrapper.h" @@ -29,7 +29,7 @@ namespace DOTNET_NAMESPACE using NorthOrientation_ = NorthOrientation; using Size_ = Size; using ViewportMode_ = ViewportMode; - + public ref class TransformStateProperties : NativeWrapper { public: diff --git a/include/UnwrappedTileID.h b/include/UnwrappedTileID.h index 9b963fb..0cf0ada 100644 --- a/include/UnwrappedTileID.h +++ b/include/UnwrappedTileID.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "NativeWrapper.h" namespace mbgl @@ -10,7 +10,7 @@ namespace DOTNET_NAMESPACE { ref class CanonicalTileID; ref class OverscaledTileID; - + /// /// Has integer z/x/y coordinates /// wrap describes tiles that are left/right of the main tile pyramid, e.g. when diff --git a/include/Vector.h b/include/Vector.h index 022f099..a431dc2 100644 --- a/include/Vector.h +++ b/include/Vector.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "NativeWrapper.h" #include #include @@ -6,7 +6,7 @@ namespace DOTNET_NAMESPACE { ref class Mat4; - + public ref class Vec3 : NativeWrapper, System::Collections::Generic::IReadOnlyList { public: diff --git a/src/AnimationOptions.cpp b/src/AnimationOptions.cpp index 4bf8792..9df4864 100644 --- a/src/AnimationOptions.cpp +++ b/src/AnimationOptions.cpp @@ -1,4 +1,4 @@ -#include "AnimationOptions.h" +#include "AnimationOptions.h" #include "Convert.h" #include "UnitBezier.h" #include @@ -49,7 +49,7 @@ namespace DOTNET_NAMESPACE { return System::Nullable(NativePointer->velocity.value()); } - + return System::Nullable(); } diff --git a/src/BoundOptions.cpp b/src/BoundOptions.cpp index aa762ac..1009ede 100644 --- a/src/BoundOptions.cpp +++ b/src/BoundOptions.cpp @@ -1,4 +1,4 @@ -#include "BoundOptions.h" +#include "BoundOptions.h" #include "LatLng.h" #include @@ -52,7 +52,7 @@ namespace DOTNET_NAMESPACE { return gcnew LatLngBounds(LatLngBounds::CreateNativePointerHolder(NativePointer->bounds.value())); } - + return nullptr; } diff --git a/src/CameraOptions.cpp b/src/CameraOptions.cpp index 0607124..dd76731 100644 --- a/src/CameraOptions.cpp +++ b/src/CameraOptions.cpp @@ -1,4 +1,4 @@ -#include "CameraOptions.h" +#include "CameraOptions.h" #include "EdgeInsets.h" #include "LatLng.h" #include "Point.h" @@ -28,7 +28,7 @@ namespace DOTNET_NAMESPACE { NativePointer->withCenter(std::nullopt); } - + return this; } @@ -48,7 +48,7 @@ namespace DOTNET_NAMESPACE { NativePointer->withPadding(std::nullopt); } - + return this; } @@ -76,7 +76,7 @@ namespace DOTNET_NAMESPACE { NativePointer->withZoom(std::nullopt); } - + return this; } @@ -166,7 +166,7 @@ namespace DOTNET_NAMESPACE { _Center = nullptr; } - + return _Center; } @@ -230,7 +230,7 @@ namespace DOTNET_NAMESPACE { return System::Nullable(NativePointer->zoom.value()); } - + return System::Nullable(); } diff --git a/src/ClientOptions.cpp b/src/ClientOptions.cpp index 9dbc8f9..15098ed 100644 --- a/src/ClientOptions.cpp +++ b/src/ClientOptions.cpp @@ -1,4 +1,4 @@ -#include "Convert.h" +#include "Convert.h" #include "ClientOptions.h" #include @@ -31,7 +31,7 @@ namespace DOTNET_NAMESPACE ClientOptions^ ClientOptions::WithVersion(System::String^ version) { NativePointer->withVersion(Convert::ToStdString(version)); - + return this; } diff --git a/src/FileSourceManager.cpp b/src/FileSourceManager.cpp index 7bb83b0..25b614a 100644 --- a/src/FileSourceManager.cpp +++ b/src/FileSourceManager.cpp @@ -1,4 +1,4 @@ -#include "ClientOptions.h" +#include "ClientOptions.h" #include "Enums.h" #include "FileSource.h" #include "FileSourceManager.h" @@ -40,4 +40,4 @@ namespace DOTNET_NAMESPACE ) ); } -} \ No newline at end of file +} diff --git a/src/FreeCameraOptions.cpp b/src/FreeCameraOptions.cpp index 1332cca..07a6563 100644 --- a/src/FreeCameraOptions.cpp +++ b/src/FreeCameraOptions.cpp @@ -1,4 +1,4 @@ -#include "FreeCameraOptions.h" +#include "FreeCameraOptions.h" #include "LatLng.h" #include "Vector.h" #include @@ -60,7 +60,7 @@ namespace DOTNET_NAMESPACE { return gcnew Vec4(Vec4::CreateNativePointerHolder(NativePointer->orientation.value())); } - + return nullptr; } diff --git a/src/HeadlessFrontend.cpp b/src/HeadlessFrontend.cpp index c38df56..83cc7ed 100644 --- a/src/HeadlessFrontend.cpp +++ b/src/HeadlessFrontend.cpp @@ -1,4 +1,4 @@ -#include "CameraOptions.h" +#include "CameraOptions.h" #include "Convert.h" #include "HeadlessFrontend.h" #include "LatLng.h" @@ -21,11 +21,11 @@ namespace DOTNET_NAMESPACE HeadlessFrontend::HeadlessFrontend() : HeadlessFrontend(gcnew Size_(512, 512)) { } - + HeadlessFrontend::HeadlessFrontend(Size_^ size) : HeadlessFrontend(size, 1.0f) { } - + HeadlessFrontend::HeadlessFrontend(Size_^ size, System::Single pixelRatio) : NativeWrapper(new mbgl::HeadlessFrontend(*size->NativePointer, pixelRatio)) { @@ -120,7 +120,7 @@ namespace DOTNET_NAMESPACE { NativePointer->setSize(*value->NativePointer); } - + Renderer_^ HeadlessFrontend::Renderer::get() { return gcnew Renderer_(Renderer_::CreateNativePointerHolder(NativePointer->getRenderer())); diff --git a/src/Image.cpp b/src/Image.cpp index 5087a52..4e44aa6 100644 --- a/src/Image.cpp +++ b/src/Image.cpp @@ -1,4 +1,4 @@ -#include "Image.h" +#include "Image.h" #include "AlphaImage.h" #include "PremultipliedImage.h" #include "UnassociatedImage.h" @@ -39,11 +39,11 @@ namespace DOTNET_NAMESPACE template AlphaImage^ BaseAlphaImage::Clone(); template System::Void BaseAlphaImage::Clear(AlphaImage^ dstImg, PointUInt pt, Size_^ size); template System::Void BaseAlphaImage::Copy(AlphaImage^ srcImg, AlphaImage^ dstImg, PointUInt srcPt, PointUInt dstPt, Size_^ size); - + template PremultipliedImage^ BasePremultipliedImage::Clone(); template System::Void BasePremultipliedImage::Clear(PremultipliedImage^ dstImg, PointUInt pt, Size_^ size); template System::Void BasePremultipliedImage::Copy(PremultipliedImage^ srcImg, PremultipliedImage^ dstImg, PointUInt srcPt, PointUInt dstPt, Size_^ size); - + template UnassociatedImage^ BaseUnassociatedImage::Clone(); template System::Void BaseUnassociatedImage::Clear(UnassociatedImage^ dstImg, PointUInt pt, Size_^ size); template System::Void BaseUnassociatedImage::Copy(UnassociatedImage^ srcImg, UnassociatedImage^ dstImg, PointUInt srcPt, PointUInt dstPt, Size_^ size); diff --git a/src/Map.cpp b/src/Map.cpp index 3e84970..c9b445c 100644 --- a/src/Map.cpp +++ b/src/Map.cpp @@ -1,4 +1,4 @@ -#include "AnimationOptions.h" +#include "AnimationOptions.h" #include "BoundOptions.h" #include "CameraOptions.h" #include "ClientOptions.h" @@ -21,7 +21,7 @@ namespace { using namespace ::DOTNET_NAMESPACE; - + void StillImageCallbackHandler(std::exception_ptr eptr, Map::StillImageCallback^ callback) { msclr::gcroot exception = nullptr; @@ -277,7 +277,7 @@ namespace DOTNET_NAMESPACE { result.emplace_back(*latLng->NativePointer); } - + return gcnew CameraOptions(CameraOptions::CreateNativePointerHolder( NativePointer->cameraForLatLngs( result, @@ -445,7 +445,7 @@ namespace DOTNET_NAMESPACE { _Style = gcnew Style_(Style_::CreateNativePointerHolder(&NativePointer->getStyle(), false)); } - + return _Style; } diff --git a/src/MapObserver.cpp b/src/MapObserver.cpp index d619b9c..fa17783 100644 --- a/src/MapObserver.cpp +++ b/src/MapObserver.cpp @@ -1,4 +1,4 @@ -#include "MapObserver.h" +#include "MapObserver.h" #include "ShaderRegistry.h" #include #include @@ -29,7 +29,7 @@ namespace DOTNET_NAMESPACE NativePointer->PostCompileShaderHandler = gcnew PostCompileShaderHandler(this, &MapObserver::onPostCompileShader); NativePointer->ShaderCompileFailedHandler = gcnew ShaderCompileFailedHandler(this, &MapObserver::onShaderCompileFailed); } - + MapObserver::~MapObserver() { } diff --git a/src/MapOptions.cpp b/src/MapOptions.cpp index af57af2..44ff463 100644 --- a/src/MapOptions.cpp +++ b/src/MapOptions.cpp @@ -1,4 +1,4 @@ -#include "MapOptions.h" +#include "MapOptions.h" #include "Size.h" #include @@ -19,14 +19,14 @@ namespace DOTNET_NAMESPACE MapOptions^ MapOptions::WithMapMode(DOTNET_NAMESPACE::MapMode mode) { NativePointer->withMapMode((mbgl::MapMode)mode); - + return this; } MapOptions^ MapOptions::WithConstrainMode(DOTNET_NAMESPACE::ConstrainMode mode) { NativePointer->withConstrainMode((mbgl::ConstrainMode)mode); - + return this; } @@ -47,21 +47,21 @@ namespace DOTNET_NAMESPACE MapOptions^ MapOptions::WithNorthOrientation(DOTNET_NAMESPACE::NorthOrientation orientation) { NativePointer->withNorthOrientation((mbgl::NorthOrientation)orientation); - + return this; } MapOptions^ MapOptions::WithSize(Size_^ size) { NativePointer->withSize(*size->NativePointer); - + return this; } MapOptions^ MapOptions::WithPixelRatio(System::Single ratio) { NativePointer->withPixelRatio(ratio); - + return this; } diff --git a/src/OverscaledTileID.cpp b/src/OverscaledTileID.cpp index 5d84255..1addabe 100644 --- a/src/OverscaledTileID.cpp +++ b/src/OverscaledTileID.cpp @@ -1,4 +1,4 @@ -#include "CanonicalTileID.h" +#include "CanonicalTileID.h" #include "OverscaledTileID.h" #include "UnwrappedTileID.h" #include @@ -9,7 +9,7 @@ namespace DOTNET_NAMESPACE : NativeWrapper(new mbgl::OverscaledTileID(overscaledZ, wrap, *canonical->NativePointer)) { } - + OverscaledTileID::OverscaledTileID(System::Byte overscaledZ, System::Int16 wrap, System::Byte z, System::UInt32 x, System::UInt32 y) : NativeWrapper(new mbgl::OverscaledTileID(overscaledZ, wrap, z, x, y)) { diff --git a/src/Projection.cpp b/src/Projection.cpp index 95aec8e..65ab7a5 100644 --- a/src/Projection.cpp +++ b/src/Projection.cpp @@ -1,82 +1,82 @@ -#include "LatLng.h" +#include "LatLng.h" #include "Projection.h" #include namespace DOTNET_NAMESPACE { - System::Double Projection::WorldSize(System::Double scale) - { - return mbgl::Projection::worldSize(scale); - } - - System::Double Projection::GetMetersPerPixelAtLatitude(System::Double lat, System::Double zoom) - { - return mbgl::Projection::getMetersPerPixelAtLatitude(lat, zoom); - } - - ProjectedMeters^ Projection::ProjectedMetersForLatLng(LatLng^ latLng) - { - return gcnew ProjectedMeters(ProjectedMeters::CreateNativePointerHolder(mbgl::Projection::projectedMetersForLatLng(*latLng->NativePointer))); - } - - LatLng^ Projection::LatLngForProjectedMeters(ProjectedMeters^ projectedMeters) - { - return gcnew LatLng(LatLng::CreateNativePointerHolder(mbgl::Projection::latLngForProjectedMeters(*projectedMeters->NativePointer))); - } - - PointDouble Projection::Project(LatLng^ latLng, System::Double scale) - { - return PointDouble(mbgl::Projection::project(*latLng->NativePointer, scale)); - } - - PointDouble Projection::Project(LatLng^ latLng, System::Int32 zoom) - { - return PointDouble(mbgl::Projection::project(*latLng->NativePointer, zoom)); - } - - LatLng^ Projection::Unproject(PointDouble p, System::Double scale) - { - return gcnew LatLng(LatLng::CreateNativePointerHolder(mbgl::Projection::unproject(mbgl::ScreenCoordinate(p.X, p.Y), scale))); - } - - LatLng^ Projection::Unproject(PointDouble p, System::Double scale, LatLng::WrapMode wrapMode) - { - return gcnew LatLng(LatLng::CreateNativePointerHolder(mbgl::Projection::unproject(mbgl::ScreenCoordinate(p.X, p.Y), scale, (mbgl::LatLng::WrapMode)wrapMode))); - } - - ProjectedMeters::ProjectedMeters(System::Double northing) : ProjectedMeters(northing, 0) - { - } - - ProjectedMeters::ProjectedMeters(System::Double northing, System::Double easting) : NativeWrapper(new mbgl::ProjectedMeters(northing, easting)) - { - } - - ProjectedMeters::ProjectedMeters(NativePointerHolder^ nativePointerHolder) : NativeWrapper(nativePointerHolder) - { - } - - ProjectedMeters::~ProjectedMeters() - { - } - - System::Double ProjectedMeters::Northing::get() - { - return NativePointer->northing(); - } - - System::Double ProjectedMeters::Easting::get() - { - return NativePointer->easting(); - } - - System::Boolean ProjectedMeters::operator==(ProjectedMeters^ a, ProjectedMeters^ b) - { - return *a->NativePointer == *b->NativePointer; - } - - System::Boolean ProjectedMeters::operator!=(ProjectedMeters^ a, ProjectedMeters^ b) - { - return *a->NativePointer != *b->NativePointer; - } + System::Double Projection::WorldSize(System::Double scale) + { + return mbgl::Projection::worldSize(scale); + } + + System::Double Projection::GetMetersPerPixelAtLatitude(System::Double lat, System::Double zoom) + { + return mbgl::Projection::getMetersPerPixelAtLatitude(lat, zoom); + } + + ProjectedMeters^ Projection::ProjectedMetersForLatLng(LatLng^ latLng) + { + return gcnew ProjectedMeters(ProjectedMeters::CreateNativePointerHolder(mbgl::Projection::projectedMetersForLatLng(*latLng->NativePointer))); + } + + LatLng^ Projection::LatLngForProjectedMeters(ProjectedMeters^ projectedMeters) + { + return gcnew LatLng(LatLng::CreateNativePointerHolder(mbgl::Projection::latLngForProjectedMeters(*projectedMeters->NativePointer))); + } + + PointDouble Projection::Project(LatLng^ latLng, System::Double scale) + { + return PointDouble(mbgl::Projection::project(*latLng->NativePointer, scale)); + } + + PointDouble Projection::Project(LatLng^ latLng, System::Int32 zoom) + { + return PointDouble(mbgl::Projection::project(*latLng->NativePointer, zoom)); + } + + LatLng^ Projection::Unproject(PointDouble p, System::Double scale) + { + return gcnew LatLng(LatLng::CreateNativePointerHolder(mbgl::Projection::unproject(mbgl::ScreenCoordinate(p.X, p.Y), scale))); + } + + LatLng^ Projection::Unproject(PointDouble p, System::Double scale, LatLng::WrapMode wrapMode) + { + return gcnew LatLng(LatLng::CreateNativePointerHolder(mbgl::Projection::unproject(mbgl::ScreenCoordinate(p.X, p.Y), scale, (mbgl::LatLng::WrapMode)wrapMode))); + } + + ProjectedMeters::ProjectedMeters(System::Double northing) : ProjectedMeters(northing, 0) + { + } + + ProjectedMeters::ProjectedMeters(System::Double northing, System::Double easting) : NativeWrapper(new mbgl::ProjectedMeters(northing, easting)) + { + } + + ProjectedMeters::ProjectedMeters(NativePointerHolder^ nativePointerHolder) : NativeWrapper(nativePointerHolder) + { + } + + ProjectedMeters::~ProjectedMeters() + { + } + + System::Double ProjectedMeters::Northing::get() + { + return NativePointer->northing(); + } + + System::Double ProjectedMeters::Easting::get() + { + return NativePointer->easting(); + } + + System::Boolean ProjectedMeters::operator==(ProjectedMeters^ a, ProjectedMeters^ b) + { + return *a->NativePointer == *b->NativePointer; + } + + System::Boolean ProjectedMeters::operator!=(ProjectedMeters^ a, ProjectedMeters^ b) + { + return *a->NativePointer != *b->NativePointer; + } } diff --git a/src/ProjectionMode.cpp b/src/ProjectionMode.cpp index 5163d54..3173f37 100644 --- a/src/ProjectionMode.cpp +++ b/src/ProjectionMode.cpp @@ -1,4 +1,4 @@ -#include "ProjectionMode.h" +#include "ProjectionMode.h" #include namespace DOTNET_NAMESPACE @@ -61,7 +61,7 @@ namespace DOTNET_NAMESPACE { return System::Nullable(NativePointer->xSkew.value()); } - + return System::Nullable(); } diff --git a/src/RendererObserver.cpp b/src/RendererObserver.cpp index 8deb582..0b83e23 100644 --- a/src/RendererObserver.cpp +++ b/src/RendererObserver.cpp @@ -1,4 +1,4 @@ -#include "Convert.h" +#include "Convert.h" #include "RendererObserver.h" #include "ShaderRegistry.h" #include @@ -154,7 +154,7 @@ namespace DOTNET_NAMESPACE StyleImageMissingCallbackHelper^ callbackHelper = gcnew StyleImageMissingCallbackHelper(this); _PendingCallbacks->TryAdd(callbackHelper, System::IntPtr(new StyleImageMissingCallback(done))); - + StyleImageMissingHandler->Invoke( Convert::ToSystemString(id), gcnew DOTNET_NAMESPACE::RendererObserver::StyleImageMissingCallback( diff --git a/src/ResourceOptions.cpp b/src/ResourceOptions.cpp index 9b351ce..7939eea 100644 --- a/src/ResourceOptions.cpp +++ b/src/ResourceOptions.cpp @@ -1,4 +1,4 @@ -#include "Convert.h" +#include "Convert.h" #include "ResourceOptions.h" #include @@ -24,7 +24,7 @@ namespace DOTNET_NAMESPACE ResourceOptions^ ResourceOptions::WithApiKey(System::String^ token) { NativePointer->withApiKey(Convert::ToStdString(token)); - + return this; } @@ -48,21 +48,21 @@ namespace DOTNET_NAMESPACE ResourceOptions^ ResourceOptions::WithAssetPath(System::String^ path) { NativePointer->withAssetPath(Convert::ToStdString(path)); - + return this; } ResourceOptions^ ResourceOptions::WithMaximumCacheSize(System::UInt64 size) { NativePointer->withMaximumCacheSize(size); - + return this; } ResourceOptions^ ResourceOptions::WithPlatformContext(System::IntPtr context) { NativePointer->withPlatformContext(context.ToPointer()); - + return this; } diff --git a/src/Response.cpp b/src/Response.cpp index 3c1abf8..0dab8b6 100644 --- a/src/Response.cpp +++ b/src/Response.cpp @@ -1,4 +1,4 @@ -#include "Convert.h" +#include "Convert.h" #include "Response.h" #include @@ -36,7 +36,7 @@ namespace DOTNET_NAMESPACE { _Error = nullptr; } - + return _Error; } @@ -90,7 +90,7 @@ namespace DOTNET_NAMESPACE { return Convert::ToSystemArray(reinterpret_cast(const_cast(NativePointer->data->data())), static_cast(NativePointer->data->size())); } - + return nullptr; } @@ -177,7 +177,7 @@ namespace DOTNET_NAMESPACE (mbgl::Response::Error::Reason)reason, message ? - Convert::ToStdString(message) + Convert::ToStdString(message) : std::string(), retryAfter.HasValue diff --git a/src/Size.cpp b/src/Size.cpp index b61ed15..f652906 100644 --- a/src/Size.cpp +++ b/src/Size.cpp @@ -1,4 +1,4 @@ -#include "Size.h" +#include "Size.h" #include namespace DOTNET_NAMESPACE @@ -59,7 +59,7 @@ namespace DOTNET_NAMESPACE { return *a->NativePointer != *b->NativePointer; } - + Size::Size(NativePointerHolder^ nativePointerHolder) : NativeWrapper(nativePointerHolder) { } diff --git a/src/Value.cpp b/src/Value.cpp index 3b97931..d69bae6 100644 --- a/src/Value.cpp +++ b/src/Value.cpp @@ -1,4 +1,4 @@ -#include "Convert.h" +#include "Convert.h" #include "Value.h" #include @@ -102,7 +102,7 @@ namespace DOTNET_NAMESPACE { return System::Nullable(*value); } - + return System::Nullable(); } @@ -112,7 +112,7 @@ namespace DOTNET_NAMESPACE { return System::Nullable(*value); } - + return System::Nullable(); } @@ -122,7 +122,7 @@ namespace DOTNET_NAMESPACE { return System::Nullable(*value); } - + return System::Nullable(); } @@ -132,7 +132,7 @@ namespace DOTNET_NAMESPACE { return System::Nullable(*value); } - + return System::Nullable(); } From eb3a86c6471181c576c512069387d09e84589d7c Mon Sep 17 00:00:00 2001 From: acalcutt Date: Fri, 1 May 2026 15:30:37 -0400 Subject: [PATCH 52/63] Fix CI build errors; add DocFX self-documentation Build fixes: - StyleImage.h, TransitionOptions.h: move property init from ctor init list to body (C3137: auto-properties cannot be initialized in init list) - Style.cpp WrapLayer: replace Layer::as() with dynamic_cast() (Layer does not have an as() method in this version of maplibre-native) - Style.cpp AddVector/Raster/RasterDEMSource: qualify variant<> as mbgl::variant<> (bare 'variant' not in scope outside mbgl namespace) - Layer.cpp, Layers.cpp, Source.cpp, Sources.cpp: remove #include 'pch.h' (pch.h does not exist; causes C1083 on clean builds) Documentation: - Add docs/ with DocFX config (docfx.json, index.md, toc.yml) - Add .github/workflows/docs.yml: builds and deploys to GitHub Pages on push to main - Add XML doc comments to previously undocumented public headers: Size.h, Value.h, Vector.h (Vec3/Vec4/Mat4/MatrixExtensions), GeoJSON.h - Update .gitignore to exclude docs/_site/, docs/api/, docs/obj/ --- .github/workflows/docs.yml | 97 +++++++++++++++++++++++++++++++++++++ .gitignore | 7 ++- docs/docfx.json | 43 ++++++++++++++++ docs/index.md | 25 ++++++++++ docs/toc.yml | 4 ++ include/GeoJSON.h | 5 ++ include/Size.h | 12 +++++ include/StyleImage.h | 8 ++- include/TransitionOptions.h | 12 +++-- include/Value.h | 27 +++++++++++ include/Vector.h | 42 ++++++++++++++++ src/Layer.cpp | 1 - src/Layers.cpp | 1 - src/Source.cpp | 1 - src/Sources.cpp | 1 - src/Style.cpp | 28 +++++------ 16 files changed, 290 insertions(+), 24 deletions(-) create mode 100644 .github/workflows/docs.yml create mode 100644 docs/docfx.json create mode 100644 docs/index.md create mode 100644 docs/toc.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..cf14d17 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,97 @@ +name: Docs + +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build-docs: + name: Build documentation + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Enable Git long paths + run: git config --global core.longpaths true + + - name: Set up MSVC developer tools + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x64 + + - name: Get vcpkg commit ID + id: vcpkg-id + shell: pwsh + run: | + $id = git -C dependencies/maplibre-native/platform/windows/vendor/vcpkg rev-parse HEAD + Add-Content -Path $env:GITHUB_OUTPUT -Value "commit_id=$id" + + - name: Cache vcpkg binary cache + uses: actions/cache@v4 + with: + path: dependencies/maplibre-native/platform/windows/vendor/vcpkg/archives + key: vcpkg-x64-windows-${{ steps.vcpkg-id.outputs.commit_id }} + + - name: Bootstrap vcpkg + run: | + & "dependencies\maplibre-native\platform\windows\vendor\vcpkg\bootstrap-vcpkg.bat" -disableMetrics + + - name: Configure CMake + shell: pwsh + env: + VCPKG_BINARY_SOURCES: "clear;files,${{ github.workspace }}\\dependencies\\maplibre-native\\platform\\windows\\vendor\\vcpkg\\archives,readwrite" + run: | + $maxAttempts = 3 + for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) { + cmake -B build -G "Visual Studio 17 2022" -A x64 ` + -DMLN_WITH_OPENGL=ON ` + -DMLN_WITH_EGL=OFF ` + -DMLN_WITH_VULKAN=OFF ` + "-DCMAKE_CXX_FLAGS=/wd4267" ` + "-DMAPLIBRE_NET_DOTNET_VERSION=net9.0" + if ($LASTEXITCODE -eq 0) { break } + if ($attempt -lt $maxAttempts) { + Remove-Item -Recurse -Force build -ErrorAction SilentlyContinue + } else { + exit $LASTEXITCODE + } + } + + - name: Build (Release) + run: cmake --build build --target mbgl-dotnet --config Release --parallel + + - name: Install docfx + run: dotnet tool install -g docfx + + - name: Build documentation + run: docfx docs/docfx.json + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs/_site + + deploy: + name: Deploy to GitHub Pages + needs: build-docs + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 72fac7e..aa0e352 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,9 @@ build/ .vs/ bin/ obj/ -*.user \ No newline at end of file +*.user + +# DocFX generated output +docs/_site/ +docs/api/ +docs/obj/ diff --git a/docs/docfx.json b/docs/docfx.json new file mode 100644 index 0000000..ffd3491 --- /dev/null +++ b/docs/docfx.json @@ -0,0 +1,43 @@ +{ + "metadata": [ + { + "src": [ + { + "src": "../", + "files": ["build/Release/MaplibreNative.NET.dll"] + } + ], + "dest": "api", + "includePrivateMembers": false, + "disableGitFeatures": false, + "disableDefaultFilter": false + } + ], + "build": { + "content": [ + { + "files": ["**/*.yml", "**/*.md"], + "src": ".", + "exclude": ["_site/**", "obj/**"] + } + ], + "resource": [ + { + "files": ["images/**"] + } + ], + "output": "_site", + "template": ["default", "modern"], + "globalMetadata": { + "_appName": "MaplibreNative.NET", + "_appTitle": "MaplibreNative.NET", + "_appFooter": "MaplibreNative.NET — .NET wrapper for maplibre-native", + "_enableSearch": true + }, + "fileMetadata": {}, + "postProcessors": [], + "keepFileLink": false, + "cleanupCacheHistory": false, + "disableGitFeatures": false + } +} diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..41aac53 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,25 @@ +# MaplibreNative.NET + +A .NET 8/9 wrapper for [maplibre-native](https://github.com/maplibre/maplibre-native), written in C++/CLI. + +## Quick Start + +Install the NuGet package and add a map to your WPF or WinForms application: + +```csharp +using MaplibreNative; + +var map = new Map(backend, rendererFrontend, observer, resourceOptions); +map.LoadStyleJSON(styleJson); +map.SetCameraOptions(new CameraOptions { Center = new LatLng(51.5, -0.09), Zoom = 13 }); +``` + +## API Reference + +See the [API Reference](xref:MaplibreNative) for full documentation of all classes and methods. + +## Requirements + +- Windows x64 or ARM64 +- .NET 8 or .NET 9 +- Visual C++ Redistributable 2022 diff --git a/docs/toc.yml b/docs/toc.yml new file mode 100644 index 0000000..db5f212 --- /dev/null +++ b/docs/toc.yml @@ -0,0 +1,4 @@ +- name: Home + href: index.md +- name: API Reference + href: api/ diff --git a/include/GeoJSON.h b/include/GeoJSON.h index 9f961fd..5218db5 100644 --- a/include/GeoJSON.h +++ b/include/GeoJSON.h @@ -4,6 +4,11 @@ namespace DOTNET_NAMESPACE { + /// + /// Wraps a parsed GeoJSON value (geometry, feature, or feature collection). + /// Instances are returned by and used when passing + /// geometry data to map sources. + /// public ref class GeoJSON : NativeWrapper { public: diff --git a/include/Size.h b/include/Size.h index fd4567f..38b9707 100644 --- a/include/Size.h +++ b/include/Size.h @@ -8,20 +8,32 @@ namespace mbgl namespace DOTNET_NAMESPACE { + /// Represents a width and height pair, in pixels. public ref class Size : NativeWrapper { public: + /// Initializes a new with zero width and height. Size(); + /// Initializes a new with the specified width and height. + /// Width in pixels. + /// Height in pixels. Size(System::UInt32 width, System::UInt32 height); ~Size(); + /// Gets or sets the width in pixels. property System::UInt32 Width { System::UInt32 get(); System::Void set(System::UInt32 value); } + /// Gets or sets the height in pixels. property System::UInt32 Height { System::UInt32 get(); System::Void set(System::UInt32 value); } + /// Gets the area (Width * Height) in square pixels. property System::UInt32 Area { System::UInt32 get(); } + /// Gets the aspect ratio (Width / Height). Returns 0 if Height is zero. property System::Single AspectRatio { System::Single get(); } + /// Gets whether either dimension is zero. property System::Boolean IsEmpty { System::Boolean get(); } + /// Returns if both dimensions are equal. static System::Boolean operator==(Size^ a, Size^ b); + /// Returns if either dimension differs. static System::Boolean operator!=(Size^ a, Size^ b); internal: Size(NativePointerHolder^ nativePointerHolder); diff --git a/include/StyleImage.h b/include/StyleImage.h index b04e10e..6c75db7 100644 --- a/include/StyleImage.h +++ b/include/StyleImage.h @@ -24,6 +24,12 @@ namespace DOTNET_NAMESPACE internal: StyleImage(System::String^ id, System::UInt32 width, System::UInt32 height, System::Single pixelRatio, System::Boolean sdf) - : Id(id), Width(width), Height(height), PixelRatio(pixelRatio), Sdf(sdf) {} + { + Id = id; + Width = width; + Height = height; + PixelRatio = pixelRatio; + Sdf = sdf; + } }; } diff --git a/include/TransitionOptions.h b/include/TransitionOptions.h index e927acc..fd7bab9 100644 --- a/include/TransitionOptions.h +++ b/include/TransitionOptions.h @@ -27,13 +27,17 @@ namespace DOTNET_NAMESPACE property System::Boolean EnablePlacementTransitions; TransitionOptions() - : EnablePlacementTransitions(true) {} + { + EnablePlacementTransitions = true; + } TransitionOptions(System::Nullable durationMs, System::Nullable delayMs, System::Boolean enablePlacementTransitions) - : DurationMilliseconds(durationMs), - DelayMilliseconds(delayMs), - EnablePlacementTransitions(enablePlacementTransitions) {} + { + DurationMilliseconds = durationMs; + DelayMilliseconds = delayMs; + EnablePlacementTransitions = enablePlacementTransitions; + } }; } diff --git a/include/Value.h b/include/Value.h index 96e6c2f..77d5f95 100644 --- a/include/Value.h +++ b/include/Value.h @@ -4,34 +4,61 @@ namespace DOTNET_NAMESPACE { + /// + /// A variant type representing a JSON-like value: null, boolean, string, signed or unsigned + /// integer, double, array, or object. Used for feature properties and style expression evaluation. + /// public ref class Value : NativeWrapper { public: + /// Initializes a null . Value(); + /// Initializes a boolean . Value(System::Boolean v); + /// Initializes a string . Value(System::String^ str); + /// Initializes a signed 8-bit integer . Value(System::SByte n); + /// Initializes a signed 16-bit integer . Value(System::Int16 n); + /// Initializes a signed 32-bit integer . Value(System::Int32 n); + /// Initializes a signed 64-bit integer . Value(System::Int64 n); + /// Initializes an unsigned 8-bit integer . Value(System::Byte n); + /// Initializes an unsigned 16-bit integer . Value(System::UInt16 n); + /// Initializes an unsigned 32-bit integer . Value(System::UInt32 n); + /// Initializes an unsigned 64-bit integer . Value(System::UInt64 n); + /// Initializes a single-precision float (stored as double). Value(System::Single n); + /// Initializes a double-precision float . Value(System::Double n); + /// Initializes an array from a sequence of values. Value(System::Collections::Generic::IEnumerable^ a); + /// Initializes an object from a string-keyed dictionary. Value(System::Collections::Generic::IDictionary^ o); ~Value(); + /// Returns the value as a signed 64-bit integer, or if the held type is not a signed integer. System::Nullable GetInt(); + /// Returns the value as an unsigned 64-bit integer, or if the held type is not an unsigned integer. System::Nullable GetUInt(); + /// Returns the value as a boolean, or if the held type is not a boolean. System::Nullable GetBool(); + /// Returns the value as a double, or if the held type is not a number. System::Nullable GetDouble(); + /// Returns the array elements, or if the held type is not an array. System::Collections::Generic::IEnumerable^ GetArray(); + /// Returns the object members, or if the held type is not an object. System::Collections::Generic::IDictionary^ GetObject(); + /// Returns the value as a string, or if the held type is not a string. System::String^ GetString(); + /// Returns when the value is non-null and non-zero. explicit operator bool(); internal: Value(NativePointerHolder^ nativePointerHolder); diff --git a/include/Vector.h b/include/Vector.h index a431dc2..b3f5899 100644 --- a/include/Vector.h +++ b/include/Vector.h @@ -7,74 +7,116 @@ namespace DOTNET_NAMESPACE { ref class Mat4; + /// A 3-component column vector of doubles. Implements for index access. public ref class Vec3 : NativeWrapper, System::Collections::Generic::IReadOnlyList { public: + /// Initializes a zero . Vec3(); ~Vec3(); virtual System::Collections::Generic::IEnumerator^ GetEnumerator(); virtual System::Collections::IEnumerator^ GetEnumeratorObject() = System::Collections::IEnumerable::GetEnumerator; + /// Gets or sets the component at the specified index (0, 1, or 2). property System::Double default[System::Int32] { virtual System::Double get(System::Int32 index); System::Void set(System::Int32 index, System::Double value); }; + /// Gets the number of components (always 3). property System::Int32 Count { virtual System::Int32 get(); } internal: Vec3(NativePointerHolder^ nativePointerHolder); }; + /// A 4-component column vector of doubles. Implements for index access. public ref class Vec4 : NativeWrapper, System::Collections::Generic::IReadOnlyList { public: + /// Initializes a zero . Vec4(); ~Vec4(); virtual System::Collections::Generic::IEnumerator^ GetEnumerator(); virtual System::Collections::IEnumerator^ GetEnumeratorObject() = System::Collections::IEnumerable::GetEnumerator; + /// Gets or sets the component at the specified index (0, 1, 2, or 3). property System::Double default[System::Int32] { virtual System::Double get(System::Int32 index); System::Void set(System::Int32 index, System::Double value); }; + /// Gets the number of components (always 4). property System::Int32 Count { virtual System::Int32 get(); } + /// Transforms in-place by matrix . static System::Void TransformMat4(Vec4^ out, Mat4^ m); + /// Transforms vector by matrix and stores the result in . static System::Void TransformMat4(Vec4^ out, Vec4^ a, Mat4^ m); internal: Vec4(NativePointerHolder^ nativePointerHolder); }; + /// + /// A 4x4 column-major matrix of doubles. Implements for flat + /// index access (16 elements, column-major order). + /// public ref class Mat4 : NativeWrapper, System::Collections::Generic::IReadOnlyList { public: + /// Initializes an identity . Mat4(); + /// Initializes a by copying . Mat4(Mat4^ source); ~Mat4(); virtual System::Collections::Generic::IEnumerator^ GetEnumerator(); virtual System::Collections::IEnumerator^ GetEnumeratorObject() = System::Collections::IEnumerable::GetEnumerator; + /// Gets or sets the element at the specified flat index (0–15, column-major). property System::Double default[System::Int32] { virtual System::Double get(System::Int32 index); System::Void set(System::Int32 index, System::Double value); }; + /// Gets the number of elements (always 16). property System::Int32 Count { virtual System::Int32 get(); } + /// Sets to the identity matrix. static System::Void Identity(Mat4^ out); + /// Inverts in-place. Returns if the matrix is singular. static System::Boolean Invert(Mat4^ out); + /// Inverts and stores the result in . Returns if the matrix is singular. static System::Boolean Invert(Mat4^ out, Mat4^ a); + /// Generates an orthographic projection matrix into . static System::Void Ortho(Mat4^ out, System::Double left, System::Double right, System::Double bottom, System::Double top, System::Double near, System::Double far); + /// Generates a perspective projection matrix into . + /// Vertical field of view in radians. + /// Aspect ratio (width / height). static System::Void Perspective(Mat4^ out, System::Double fovy, System::Double aspect, System::Double near, System::Double far); + /// Copies matrix into . static System::Void Copy(Mat4^ out, Mat4^ a); + /// Translates in-place by (x, y, z). static System::Void Translate(Mat4^ out, System::Double x, System::Double y, System::Double z); + /// Translates matrix by (x, y, z) and stores the result in . static System::Void Translate(Mat4^ out, Mat4^ a, System::Double x, System::Double y, System::Double z); + /// Rotates in-place around the X axis by radians. static System::Void RotateX(Mat4^ out, System::Double rad); + /// Rotates matrix around the X axis and stores the result in . static System::Void RotateX(Mat4^ out, Mat4^ a, System::Double rad); + /// Rotates in-place around the Y axis by radians. static System::Void RotateY(Mat4^ out, System::Double rad); + /// Rotates matrix around the Y axis and stores the result in . static System::Void RotateY(Mat4^ out, Mat4^ a, System::Double rad); + /// Rotates in-place around the Z axis by radians. static System::Void RotateZ(Mat4^ out, System::Double rad); + /// Rotates matrix around the Z axis and stores the result in . static System::Void RotateZ(Mat4^ out, Mat4^ a, System::Double rad); + /// Scales in-place by (x, y, z). static System::Void Scale(Mat4^ out, System::Double x, System::Double y, System::Double z); + /// Scales matrix by (x, y, z) and stores the result in . static System::Void Scale(Mat4^ out, Mat4^ a, System::Double x, System::Double y, System::Double z); + /// Post-multiplies in-place by . static System::Void Multiply(Mat4^ out, Mat4^ a); + /// Multiplies by and stores the result in . static System::Void Multiply(Mat4^ out, Mat4^ a, Mat4^ b); internal: Mat4(NativePointerHolder^ nativePointerHolder); }; + /// + /// Extension methods for and that expose the same matrix + /// operations as instance-style calls (e.g. mat.Multiply(other)). + /// [System::Runtime::CompilerServices::ExtensionAttribute] public ref class MatrixExtensions abstract sealed { diff --git a/src/Layer.cpp b/src/Layer.cpp index 765915a..4d28fea 100644 --- a/src/Layer.cpp +++ b/src/Layer.cpp @@ -1,4 +1,3 @@ -#include "pch.h" #include "Layer.h" #include diff --git a/src/Layers.cpp b/src/Layers.cpp index d9a2526..5db579b 100644 --- a/src/Layers.cpp +++ b/src/Layers.cpp @@ -1,4 +1,3 @@ -#include "pch.h" #include "Layers.h" #include diff --git a/src/Source.cpp b/src/Source.cpp index fb686a3..a1778ad 100644 --- a/src/Source.cpp +++ b/src/Source.cpp @@ -1,4 +1,3 @@ -#include "pch.h" #include "Source.h" #include diff --git a/src/Sources.cpp b/src/Sources.cpp index afc5f2b..f459540 100644 --- a/src/Sources.cpp +++ b/src/Sources.cpp @@ -1,4 +1,3 @@ -#include "pch.h" #include "Sources.h" #include "PremultipliedImage.h" diff --git a/src/Style.cpp b/src/Style.cpp index ab3a416..a507b3e 100644 --- a/src/Style.cpp +++ b/src/Style.cpp @@ -161,17 +161,17 @@ namespace DOTNET_NAMESPACE static Layer^ WrapLayer(mbgl::style::Layer* raw) { if (!raw) return nullptr; - if (auto* p = raw->as()) return gcnew FillLayer(p); - if (auto* p = raw->as()) return gcnew LineLayer(p); - if (auto* p = raw->as()) return gcnew CircleLayer(p); - if (auto* p = raw->as()) return gcnew SymbolLayer(p); - if (auto* p = raw->as()) return gcnew RasterLayer(p); - if (auto* p = raw->as()) return gcnew BackgroundLayer(p); - if (auto* p = raw->as()) return gcnew HeatmapLayer(p); - if (auto* p = raw->as()) return gcnew HillshadeLayer(p); - if (auto* p = raw->as()) return gcnew FillExtrusionLayer(p); - if (auto* p = raw->as()) return gcnew ColorReliefLayer(p); - if (auto* p = raw->as()) return gcnew LocationIndicatorLayer(p); + if (auto* p = dynamic_cast(raw)) return gcnew FillLayer(p); + if (auto* p = dynamic_cast(raw)) return gcnew LineLayer(p); + if (auto* p = dynamic_cast(raw)) return gcnew CircleLayer(p); + if (auto* p = dynamic_cast(raw)) return gcnew SymbolLayer(p); + if (auto* p = dynamic_cast(raw)) return gcnew RasterLayer(p); + if (auto* p = dynamic_cast(raw)) return gcnew BackgroundLayer(p); + if (auto* p = dynamic_cast(raw)) return gcnew HeatmapLayer(p); + if (auto* p = dynamic_cast(raw)) return gcnew HillshadeLayer(p); + if (auto* p = dynamic_cast(raw)) return gcnew FillExtrusionLayer(p); + if (auto* p = dynamic_cast(raw)) return gcnew ColorReliefLayer(p); + if (auto* p = dynamic_cast(raw)) return gcnew LocationIndicatorLayer(p); // Unknown type — return base Layer wrapper return gcnew Layer(raw); } @@ -491,7 +491,7 @@ namespace DOTNET_NAMESPACE { auto src = std::make_unique( Convert::ToStdString(sourceId), - variant(Convert::ToStdString(url))); + mbgl::variant(Convert::ToStdString(url))); auto* raw = src.get(); NativePointer->addSource(std::move(src)); return gcnew VectorSource(raw); @@ -501,7 +501,7 @@ namespace DOTNET_NAMESPACE { auto src = std::make_unique( Convert::ToStdString(sourceId), - variant(Convert::ToStdString(url)), + mbgl::variant(Convert::ToStdString(url)), tileSize); auto* raw = src.get(); NativePointer->addSource(std::move(src)); @@ -512,7 +512,7 @@ namespace DOTNET_NAMESPACE { auto src = std::make_unique( Convert::ToStdString(sourceId), - variant(Convert::ToStdString(url)), + mbgl::variant(Convert::ToStdString(url)), tileSize); auto* raw = src.get(); NativePointer->addSource(std::move(src)); From 2651b621242dec193776de7f1f37ee45cf43f234 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Fri, 1 May 2026 16:19:41 -0400 Subject: [PATCH 53/63] Fix CI errors: NativePointerHolder C3137, JSDocument namespace, marshal_as - NativePointerHolder.h: fix delegating ctor (NativePointer -> NativePointerHolder) - Layer.cpp: mbgl::style::conversion::JSDocument -> mbgl::JSDocument; replace marshal_as error throw with gcnew System::String(msg.c_str()) - Layers.cpp: same JSDocument fix in LineLayer::Gradient, HeatmapLayer::Color, ColorReliefLayer::ColorRamp setters; replace marshal_as error throws - Layers.cpp: fix LocationIndicatorLayer image setters - split ternary into named local variable before marshal_as call - Layers.cpp: fix AccuracyRadius*Color getters - remove undefined ColorToString wrapper, pass string literal default to GetColor instead of mbgl::Color --- include/NativePointerHolder.h | 2 +- src/Layer.cpp | 4 ++-- src/Layers.cpp | 25 ++++++++++++++----------- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/include/NativePointerHolder.h b/include/NativePointerHolder.h index 61c3641..e3594da 100644 --- a/include/NativePointerHolder.h +++ b/include/NativePointerHolder.h @@ -32,7 +32,7 @@ namespace DOTNET_NAMESPACE { } - NativePointerHolder(T* nativePointer) : NativePointer(nativePointer, true) + NativePointerHolder(T* nativePointer) : NativePointerHolder(nativePointer, true) { } diff --git a/src/Layer.cpp b/src/Layer.cpp index 4d28fea..510e65f 100644 --- a/src/Layer.cpp +++ b/src/Layer.cpp @@ -177,7 +177,7 @@ namespace DOTNET_NAMESPACE } std::string json = msclr::interop::marshal_as(filterJson); - mbgl::style::conversion::JSDocument doc; + mbgl::JSDocument doc; doc.Parse(json.c_str()); if (doc.HasParseError()) throw gcnew System::ArgumentException("Invalid filter JSON: parse error"); @@ -187,7 +187,7 @@ namespace DOTNET_NAMESPACE mbgl::style::conversion::Convertible(&doc), error); if (!filter) throw gcnew System::ArgumentException( - msclr::interop::marshal_as("Invalid filter expression: " + error.message)); + gcnew System::String(("Invalid filter expression: " + error.message).c_str())); _layer->setFilter(*filter); } diff --git a/src/Layers.cpp b/src/Layers.cpp index 5db579b..d0f6fd6 100644 --- a/src/Layers.cpp +++ b/src/Layers.cpp @@ -440,7 +440,7 @@ namespace DOTNET_NAMESPACE { if (value == nullptr || value->Length == 0) return; std::string json = msclr::interop::marshal_as(value); - mbgl::style::conversion::JSDocument doc; + mbgl::JSDocument doc; doc.Parse(json.c_str()); if (doc.HasParseError()) throw gcnew System::ArgumentException("Invalid gradient JSON: parse error"); @@ -449,7 +449,7 @@ namespace DOTNET_NAMESPACE mbgl::style::conversion::Convertible(&doc), error); if (!gradient) throw gcnew System::ArgumentException( - msclr::interop::marshal_as("Invalid gradient expression: " + error.message)); + gcnew System::String(("Invalid gradient expression: " + error.message).c_str())); Impl()->setLineGradient(*gradient); } @@ -1477,7 +1477,7 @@ namespace DOTNET_NAMESPACE { if (value == nullptr || value->Length == 0) return; std::string json = msclr::interop::marshal_as(value); - mbgl::style::conversion::JSDocument doc; + mbgl::JSDocument doc; doc.Parse(json.c_str()); if (doc.HasParseError()) throw gcnew System::ArgumentException("Invalid heatmap color JSON: parse error"); @@ -1486,7 +1486,7 @@ namespace DOTNET_NAMESPACE mbgl::style::conversion::Convertible(&doc), error); if (!colorRamp) throw gcnew System::ArgumentException( - msclr::interop::marshal_as("Invalid heatmap color expression: " + error.message)); + gcnew System::String(("Invalid heatmap color expression: " + error.message).c_str())); Impl()->setHeatmapColor(*colorRamp); } // ========================================================================= @@ -1709,7 +1709,7 @@ namespace DOTNET_NAMESPACE return; std::string json = msclr::interop::marshal_as(value); - mbgl::style::conversion::JSDocument doc; + mbgl::JSDocument doc; doc.Parse(json.c_str()); if (doc.HasParseError()) throw gcnew System::ArgumentException("Invalid color ramp JSON: parse error"); @@ -1719,7 +1719,7 @@ namespace DOTNET_NAMESPACE mbgl::style::conversion::Convertible(&doc), error); if (!colorRamp) throw gcnew System::ArgumentException( - msclr::interop::marshal_as("Invalid color ramp expression: " + error.message)); + gcnew System::String(("Invalid color ramp expression: " + error.message).c_str())); Impl()->setColorReliefColor(*colorRamp); } @@ -1745,7 +1745,8 @@ namespace DOTNET_NAMESPACE } System::Void LocationIndicatorLayer::BearingImage::set(System::String^ value) { - std::string raw = msclr::interop::marshal_as(value ? value : System::String::Empty); + System::String^ safeValue = value != nullptr ? value : System::String::Empty; + std::string raw = msclr::interop::marshal_as(safeValue); Impl()->setBearingImage(mbgl::style::PropertyValue( mbgl::style::expression::Image(raw))); } @@ -1758,7 +1759,8 @@ namespace DOTNET_NAMESPACE } System::Void LocationIndicatorLayer::ShadowImage::set(System::String^ value) { - std::string raw = msclr::interop::marshal_as(value ? value : System::String::Empty); + System::String^ safeValue = value != nullptr ? value : System::String::Empty; + std::string raw = msclr::interop::marshal_as(safeValue); Impl()->setShadowImage(mbgl::style::PropertyValue( mbgl::style::expression::Image(raw))); } @@ -1771,7 +1773,8 @@ namespace DOTNET_NAMESPACE } System::Void LocationIndicatorLayer::TopImage::set(System::String^ value) { - std::string raw = msclr::interop::marshal_as(value ? value : System::String::Empty); + System::String^ safeValue = value != nullptr ? value : System::String::Empty; + std::string raw = msclr::interop::marshal_as(safeValue); Impl()->setTopImage(mbgl::style::PropertyValue( mbgl::style::expression::Image(raw))); } @@ -1848,7 +1851,7 @@ namespace DOTNET_NAMESPACE System::String^ LocationIndicatorLayer::AccuracyRadiusBorderColor::get() { - return ColorToString(GetColor(Impl()->getAccuracyRadiusBorderColor(), mbgl::Color(0, 0, 0, 0))); + return GetColor(Impl()->getAccuracyRadiusBorderColor(), "rgba(0,0,0,0)"); } System::Void LocationIndicatorLayer::AccuracyRadiusBorderColor::set(System::String^ value) { @@ -1857,7 +1860,7 @@ namespace DOTNET_NAMESPACE System::String^ LocationIndicatorLayer::AccuracyRadiusColor::get() { - return ColorToString(GetColor(Impl()->getAccuracyRadiusColor(), mbgl::Color(0, 0, 0, 0))); + return GetColor(Impl()->getAccuracyRadiusColor(), "rgba(0,0,0,0)"); } System::Void LocationIndicatorLayer::AccuracyRadiusColor::set(System::String^ value) { From 88c56266871755d96af66af47316aeb9173763b5 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Fri, 1 May 2026 21:59:41 -0400 Subject: [PATCH 54/63] Fix CI errors: Convertible JSDocument*, IconPadding, HillshadeLayer types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Layer.cpp + Layers.cpp: pass const JSValue& reference to Convertible instead of JSDocument* — ConversionTraits is specialized for const JSValue*, not JSDocument* (even though JSDocument inherits JSValue) - Layers.cpp: add #include and GetColorAgnostic<> template helper for version-agnostic color extraction from PropertyValue or PropertyValue> - Layers.cpp SymbolLayer::IconPadding: use generic lambda + if constexpr to handle both PropertyValue (older) and PropertyValue (newer) maplibre-native builds - Layers.cpp HillshadeLayer::IlluminationDirection: generic lambda handles both PropertyValue and PropertyValue> - Layers.cpp HillshadeLayer::ShadowColor/HighlightColor: use GetColorAgnostic for getter; generic lambda for setter handles both Color and vector --- src/Layer.cpp | 3 +- src/Layers.cpp | 82 +++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 73 insertions(+), 12 deletions(-) diff --git a/src/Layer.cpp b/src/Layer.cpp index 510e65f..415914b 100644 --- a/src/Layer.cpp +++ b/src/Layer.cpp @@ -183,8 +183,9 @@ namespace DOTNET_NAMESPACE throw gcnew System::ArgumentException("Invalid filter JSON: parse error"); mbgl::style::conversion::Error error; + const mbgl::JSValue& jsRoot = doc; auto filter = mbgl::style::conversion::convert( - mbgl::style::conversion::Convertible(&doc), error); + mbgl::style::conversion::Convertible(&jsRoot), error); if (!filter) throw gcnew System::ArgumentException( gcnew System::String(("Invalid filter expression: " + error.message).c_str())); diff --git a/src/Layers.cpp b/src/Layers.cpp index d0f6fd6..6cc8301 100644 --- a/src/Layers.cpp +++ b/src/Layers.cpp @@ -20,6 +20,7 @@ #include #include +#include namespace { @@ -65,6 +66,24 @@ namespace return pv.isConstant() ? ColorToString(pv.asConstant()) : def; } + // ------------------------------------------------------------------------- + // Version-agnostic color getter: handles both PropertyValue and + // PropertyValue> (newer maplibre-native builds) + // ------------------------------------------------------------------------- + template + inline System::String^ GetColorAgnostic(const PropVal& pv, System::String^ def) + { + using T = std::decay_t; + if constexpr (std::is_same_v) + return GetColor(pv, def); + else // std::vector + { + if (pv.isConstant() && !pv.asConstant().empty()) + return ColorToString(pv.asConstant()[0]); + return def; + } + } + // ------------------------------------------------------------------------- // Helpers: serialize mbgl::Value to JSON string (used for expression props) // ------------------------------------------------------------------------- @@ -445,8 +464,9 @@ namespace DOTNET_NAMESPACE if (doc.HasParseError()) throw gcnew System::ArgumentException("Invalid gradient JSON: parse error"); mbgl::style::conversion::Error error; + const mbgl::JSValue& jsRoot = doc; auto gradient = mbgl::style::conversion::convert( - mbgl::style::conversion::Convertible(&doc), error); + mbgl::style::conversion::Convertible(&jsRoot), error); if (!gradient) throw gcnew System::ArgumentException( gcnew System::String(("Invalid gradient expression: " + error.message).c_str())); @@ -746,12 +766,24 @@ namespace DOTNET_NAMESPACE float SymbolLayer::IconPadding::get() { - return GetFloat(Impl()->getIconPadding(), 2.0f); + return [](const auto& pv) -> float { + using T = std::decay_t; + if constexpr (std::is_same_v) + return GetFloat(pv, 2.0f); + else // mbgl::Padding + return pv.isConstant() ? pv.asConstant().top : 2.0f; + }(Impl()->getIconPadding()); } System::Void SymbolLayer::IconPadding::set(float value) { - Impl()->setIconPadding(mbgl::style::PropertyValue(value)); + [value](auto* impl) { + using T = std::decay_tgetIconPadding().asConstant())>; + if constexpr (std::is_same_v) + impl->setIconPadding(mbgl::style::PropertyValue(value)); + else // mbgl::Padding — uniform padding on all sides + impl->setIconPadding(mbgl::style::PropertyValue(T{value, value, value, value})); + }(Impl()); } bool SymbolLayer::IconKeepUpright::get() @@ -1482,8 +1514,9 @@ namespace DOTNET_NAMESPACE if (doc.HasParseError()) throw gcnew System::ArgumentException("Invalid heatmap color JSON: parse error"); mbgl::style::conversion::Error error; + const mbgl::JSValue& jsRoot = doc; auto colorRamp = mbgl::style::conversion::convert( - mbgl::style::conversion::Convertible(&doc), error); + mbgl::style::conversion::Convertible(&jsRoot), error); if (!colorRamp) throw gcnew System::ArgumentException( gcnew System::String(("Invalid heatmap color expression: " + error.message).c_str())); @@ -1510,32 +1543,58 @@ namespace DOTNET_NAMESPACE float HillshadeLayer::IlluminationDirection::get() { - return GetFloat(Impl()->getHillshadeIlluminationDirection(), 335.0f); + return [](const auto& pv) -> float { + using T = std::decay_t; + if constexpr (std::is_same_v) + return GetFloat(pv, 335.0f); + else // std::vector + return (pv.isConstant() && !pv.asConstant().empty()) ? pv.asConstant()[0] : 335.0f; + }(Impl()->getHillshadeIlluminationDirection()); } System::Void HillshadeLayer::IlluminationDirection::set(float value) { - Impl()->setHillshadeIlluminationDirection(mbgl::style::PropertyValue(value)); + [value](auto* impl) { + using T = std::decay_tgetHillshadeIlluminationDirection().asConstant())>; + if constexpr (std::is_same_v) + impl->setHillshadeIlluminationDirection(mbgl::style::PropertyValue(value)); + else // std::vector + impl->setHillshadeIlluminationDirection(mbgl::style::PropertyValue({value})); + }(Impl()); } System::String^ HillshadeLayer::ShadowColor::get() { - return GetColor(Impl()->getHillshadeShadowColor(), "#000000"); + return GetColorAgnostic(Impl()->getHillshadeShadowColor(), "#000000"); } System::Void HillshadeLayer::ShadowColor::set(System::String^ value) { - Impl()->setHillshadeShadowColor(mbgl::style::PropertyValue(ParseColor(value))); + mbgl::Color c = ParseColor(value); + [c](auto* impl) { + using T = std::decay_tgetHillshadeShadowColor().asConstant())>; + if constexpr (std::is_same_v) + impl->setHillshadeShadowColor(mbgl::style::PropertyValue(c)); + else // std::vector + impl->setHillshadeShadowColor(mbgl::style::PropertyValue({c})); + }(Impl()); } System::String^ HillshadeLayer::HighlightColor::get() { - return GetColor(Impl()->getHillshadeHighlightColor(), "#ffffff"); + return GetColorAgnostic(Impl()->getHillshadeHighlightColor(), "#ffffff"); } System::Void HillshadeLayer::HighlightColor::set(System::String^ value) { - Impl()->setHillshadeHighlightColor(mbgl::style::PropertyValue(ParseColor(value))); + mbgl::Color c = ParseColor(value); + [c](auto* impl) { + using T = std::decay_tgetHillshadeHighlightColor().asConstant())>; + if constexpr (std::is_same_v) + impl->setHillshadeHighlightColor(mbgl::style::PropertyValue(c)); + else // std::vector + impl->setHillshadeHighlightColor(mbgl::style::PropertyValue({c})); + }(Impl()); } System::String^ HillshadeLayer::AccentColor::get() @@ -1715,8 +1774,9 @@ namespace DOTNET_NAMESPACE throw gcnew System::ArgumentException("Invalid color ramp JSON: parse error"); mbgl::style::conversion::Error error; + const mbgl::JSValue& jsRoot = doc; auto colorRamp = mbgl::style::conversion::convert( - mbgl::style::conversion::Convertible(&doc), error); + mbgl::style::conversion::Convertible(&jsRoot), error); if (!colorRamp) throw gcnew System::ArgumentException( gcnew System::String(("Invalid color ramp expression: " + error.message).c_str())); From e940e07defc9739457f7914ac073048d7a44c448 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Fri, 1 May 2026 22:37:55 -0400 Subject: [PATCH 55/63] Fix C3923: move generic lambdas out of managed member functions C++/CLI error C3923 fires when a generic lambda (which creates a local template struct) appears inside a managed class member function. The fix is to lift each lambda into a free template function in the anonymous namespace and call that from the managed member function. Affected properties: - SymbolLayer::IconPadding (get + set) - HillshadeLayer::IlluminationDirection (get + set) - HillshadeLayer::ShadowColor (set) - HillshadeLayer::HighlightColor (set) --- src/Layers.cpp | 119 +++++++++++++++++++++++++++++++------------------ 1 file changed, 75 insertions(+), 44 deletions(-) diff --git a/src/Layers.cpp b/src/Layers.cpp index 6cc8301..04c686a 100644 --- a/src/Layers.cpp +++ b/src/Layers.cpp @@ -84,6 +84,75 @@ namespace } } + // ------------------------------------------------------------------------- + // Version-agnostic getters/setters extracted from managed member functions + // (C++/CLI forbids generic lambdas — local template types — in managed fns) + // ------------------------------------------------------------------------- + + // SymbolLayer::IconPadding — float (old) or mbgl::Padding (new) + template + inline float GetIconPadding(const PropVal& pv) + { + using T = std::decay_t; + if constexpr (std::is_same_v) + return GetFloat(pv, 2.0f); + else // mbgl::Padding + return pv.isConstant() ? pv.asConstant().top : 2.0f; + } + + template + inline void SetIconPadding(LayerImpl* impl, float value) + { + using T = std::decay_tgetIconPadding().asConstant())>; + if constexpr (std::is_same_v) + impl->setIconPadding(mbgl::style::PropertyValue(value)); + else // mbgl::Padding — uniform padding on all sides + impl->setIconPadding(mbgl::style::PropertyValue(T{value, value, value, value})); + } + + // HillshadeLayer::IlluminationDirection — float (old) or vector (new) + template + inline float GetIlluminationDirection(const PropVal& pv) + { + using T = std::decay_t; + if constexpr (std::is_same_v) + return GetFloat(pv, 335.0f); + else // std::vector + return (pv.isConstant() && !pv.asConstant().empty()) ? pv.asConstant()[0] : 335.0f; + } + + template + inline void SetIlluminationDirection(LayerImpl* impl, float value) + { + using T = std::decay_tgetHillshadeIlluminationDirection().asConstant())>; + if constexpr (std::is_same_v) + impl->setHillshadeIlluminationDirection(mbgl::style::PropertyValue(value)); + else // std::vector + impl->setHillshadeIlluminationDirection(mbgl::style::PropertyValue({value})); + } + + // HillshadeLayer::ShadowColor — Color (old) or vector (new) + template + inline void SetHillshadeShadowColor(LayerImpl* impl, mbgl::Color c) + { + using T = std::decay_tgetHillshadeShadowColor().asConstant())>; + if constexpr (std::is_same_v) + impl->setHillshadeShadowColor(mbgl::style::PropertyValue(c)); + else // std::vector + impl->setHillshadeShadowColor(mbgl::style::PropertyValue({c})); + } + + // HillshadeLayer::HighlightColor — Color (old) or vector (new) + template + inline void SetHillshadeHighlightColor(LayerImpl* impl, mbgl::Color c) + { + using T = std::decay_tgetHillshadeHighlightColor().asConstant())>; + if constexpr (std::is_same_v) + impl->setHillshadeHighlightColor(mbgl::style::PropertyValue(c)); + else // std::vector + impl->setHillshadeHighlightColor(mbgl::style::PropertyValue({c})); + } + // ------------------------------------------------------------------------- // Helpers: serialize mbgl::Value to JSON string (used for expression props) // ------------------------------------------------------------------------- @@ -766,24 +835,12 @@ namespace DOTNET_NAMESPACE float SymbolLayer::IconPadding::get() { - return [](const auto& pv) -> float { - using T = std::decay_t; - if constexpr (std::is_same_v) - return GetFloat(pv, 2.0f); - else // mbgl::Padding - return pv.isConstant() ? pv.asConstant().top : 2.0f; - }(Impl()->getIconPadding()); + return GetIconPadding(Impl()->getIconPadding()); } System::Void SymbolLayer::IconPadding::set(float value) { - [value](auto* impl) { - using T = std::decay_tgetIconPadding().asConstant())>; - if constexpr (std::is_same_v) - impl->setIconPadding(mbgl::style::PropertyValue(value)); - else // mbgl::Padding — uniform padding on all sides - impl->setIconPadding(mbgl::style::PropertyValue(T{value, value, value, value})); - }(Impl()); + SetIconPadding(Impl(), value); } bool SymbolLayer::IconKeepUpright::get() @@ -1543,24 +1600,12 @@ namespace DOTNET_NAMESPACE float HillshadeLayer::IlluminationDirection::get() { - return [](const auto& pv) -> float { - using T = std::decay_t; - if constexpr (std::is_same_v) - return GetFloat(pv, 335.0f); - else // std::vector - return (pv.isConstant() && !pv.asConstant().empty()) ? pv.asConstant()[0] : 335.0f; - }(Impl()->getHillshadeIlluminationDirection()); + return GetIlluminationDirection(Impl()->getHillshadeIlluminationDirection()); } System::Void HillshadeLayer::IlluminationDirection::set(float value) { - [value](auto* impl) { - using T = std::decay_tgetHillshadeIlluminationDirection().asConstant())>; - if constexpr (std::is_same_v) - impl->setHillshadeIlluminationDirection(mbgl::style::PropertyValue(value)); - else // std::vector - impl->setHillshadeIlluminationDirection(mbgl::style::PropertyValue({value})); - }(Impl()); + SetIlluminationDirection(Impl(), value); } System::String^ HillshadeLayer::ShadowColor::get() @@ -1570,14 +1615,7 @@ namespace DOTNET_NAMESPACE System::Void HillshadeLayer::ShadowColor::set(System::String^ value) { - mbgl::Color c = ParseColor(value); - [c](auto* impl) { - using T = std::decay_tgetHillshadeShadowColor().asConstant())>; - if constexpr (std::is_same_v) - impl->setHillshadeShadowColor(mbgl::style::PropertyValue(c)); - else // std::vector - impl->setHillshadeShadowColor(mbgl::style::PropertyValue({c})); - }(Impl()); + SetHillshadeShadowColor(Impl(), ParseColor(value)); } System::String^ HillshadeLayer::HighlightColor::get() @@ -1587,14 +1625,7 @@ namespace DOTNET_NAMESPACE System::Void HillshadeLayer::HighlightColor::set(System::String^ value) { - mbgl::Color c = ParseColor(value); - [c](auto* impl) { - using T = std::decay_tgetHillshadeHighlightColor().asConstant())>; - if constexpr (std::is_same_v) - impl->setHillshadeHighlightColor(mbgl::style::PropertyValue(c)); - else // std::vector - impl->setHillshadeHighlightColor(mbgl::style::PropertyValue({c})); - }(Impl()); + SetHillshadeHighlightColor(Impl(), ParseColor(value)); } System::String^ HillshadeLayer::AccentColor::get() From cc14b8a7241b4891e1a72aa1f834bf39fed80a0d Mon Sep 17 00:00:00 2001 From: acalcutt Date: Sat, 2 May 2026 10:55:32 -0400 Subject: [PATCH 56/63] Sync MaplibreNative.NET.WPF from VistumblerCS (location indicator + follow/bearing) DelegateMapObserver: - Add optional onStyleLoaded callback parameter so MaplibreMapHost can react to style loads/reloads without subclassing MapObserver. MaplibreMapHost: - FollowLocation (bool): re-centres map on GPS fix; first fix -> CenterOn (zoom 14), subsequent fixes -> PanTo (zoom-preserving). - ShowBearing (bool): when false, forces bearing=0 so indicator points north. - PanTo(lat,lon): zoom-preserving JumpTo helper. - UpdateLocationIndicator(lat,lon,bearing,accuracyMeters): creates and updates a LocationIndicatorLayer (blue accuracy circle) via reflection. - ClearLocationIndicator(): removes the layer. - OnMapStyleLoaded(): replays pending position after style reload. --- MaplibreNative.NET.WPF/DelegateMapObserver.cs | 14 +- MaplibreNative.NET.WPF/MaplibreMapHost.cs | 147 +++++++++++++++++- 2 files changed, 157 insertions(+), 4 deletions(-) diff --git a/MaplibreNative.NET.WPF/DelegateMapObserver.cs b/MaplibreNative.NET.WPF/DelegateMapObserver.cs index 6a4560c..b39590a 100644 --- a/MaplibreNative.NET.WPF/DelegateMapObserver.cs +++ b/MaplibreNative.NET.WPF/DelegateMapObserver.cs @@ -5,12 +5,19 @@ namespace MaplibreNative.WPF; /// /// Simple MapObserver that forwards events to a delegate, useful for logging. +/// An optional callback is invoked whenever +/// the style finishes loading (including after style URL changes). /// public class DelegateMapObserver : MapObserver { private readonly Action _log; + private readonly Action? _onStyleLoaded; - public DelegateMapObserver(Action log) { _log = log; } + public DelegateMapObserver(Action log, Action? onStyleLoaded = null) + { + _log = log; + _onStyleLoaded = onStyleLoaded; + } protected override void onDidFailLoadingMap(MapLoadError type, string description) => _log("Fail", $"[{type}] {description}"); @@ -22,7 +29,10 @@ protected override void onWillStartLoadingMap() => _log("Will", "WillStartLoading"); protected override void onDidFinishLoadingStyle() - => _log("Style", "DidFinishLoadingStyle"); + { + _log("Style", "DidFinishLoadingStyle"); + _onStyleLoaded?.Invoke(); + } protected override void onDidFinishRenderingFrame(RenderFrameStatus status) => _log("Frame", $"mode={status.mode} needsRepaint={status.needsRepaint}"); diff --git a/MaplibreNative.NET.WPF/MaplibreMapHost.cs b/MaplibreNative.NET.WPF/MaplibreMapHost.cs index d021867..67268b1 100644 --- a/MaplibreNative.NET.WPF/MaplibreMapHost.cs +++ b/MaplibreNative.NET.WPF/MaplibreMapHost.cs @@ -103,6 +103,16 @@ public void ResetNorth() _renderNeedsUpdate = true; } + /// Pan to a location without changing the current zoom level. + private void PanTo(double lat, double lon) + { + if (_map == null) return; + var cam = new CameraOptions(); + cam.Center = new LatLng(lat, lon); + _map.JumpTo(cam); + _renderNeedsUpdate = true; + } + // ── GeoJSON layer management ────────────────────────────────────────────── // These methods call APIs added to Style in the C++/CLI wrapper. They use // reflection so the WPF library keeps compiling against older DLL builds @@ -183,6 +193,106 @@ public void SetWifiGeoJsonLayerData(string sourceId, string geoJson) _renderNeedsUpdate = true; } + // ── Location indicator API ──────────────────────────────────────────────── + + /// + /// Show (or update) the user-location "blue dot" indicator at the given position. + /// Creates the LocationIndicatorLayer automatically after the style is ready. + /// Safe to call before the style is loaded — the position is stored and applied + /// once onDidFinishLoadingStyle fires. + /// + public void UpdateLocationIndicator(double lat, double lon, float bearing, float accuracyMeters) + { + if (_map == null) return; + var style = _map.Style; + + // Lazily resolve the Style.AddLocationIndicatorLayer method + if (_miAddLocInd == null) + _miAddLocInd = style.GetType().GetMethod("AddLocationIndicatorLayer", [typeof(string)]); + if (_miAddLocInd == null) + { + Log("UpdateLocationIndicator: API not available in this DLL build"); + return; + } + + // Remember latest position so it can be replayed after a style reload + bool isFirstFix = !_pendingLocInd.HasValue; + _pendingLocInd = new LocIndParams(lat, lon, bearing, Math.Max(5f, accuracyMeters)); + + // Follow mode: zoom to 14 on the first fix, then just pan (preserves user zoom) + if (FollowLocation) + { + if (isFirstFix) CenterOn(lat, lon); + else PanTo(lat, lon); + } + + if (!_styleReady) return; // OnMapStyleLoaded will call us again + + EnsureStyleApiResolved(style); + + if (_locIndObj == null) + { + // Remove a stale layer in case style was reloaded without us noticing + if (_miHasLayer != null && (bool)_miHasLayer.Invoke(style, [LocIndLayerId])!) + _miRemoveLayer?.Invoke(style, [LocIndLayerId]); + + _locIndObj = _miAddLocInd.Invoke(style, [LocIndLayerId]); + if (_locIndObj == null) + { + Log("UpdateLocationIndicator: AddLocationIndicatorLayer returned null"); + return; + } + + // Resolve property infos from the concrete type (done once across all instances) + if (_piLocLoc == null) + { + var t = _locIndObj.GetType(); + _piLocLoc = t.GetProperty("Location"); + _piLocBearing = t.GetProperty("Bearing"); + _piLocAccuracy = t.GetProperty("AccuracyRadius"); + _piLocAccuracyColor = t.GetProperty("AccuracyRadiusColor"); + _piLocAccuracyBorderColor = t.GetProperty("AccuracyRadiusBorderColor"); + } + + // Apply fixed style: semi-transparent blue fill + solid blue border + _piLocAccuracyColor?.SetValue(_locIndObj, "rgba(30,136,229,0.3)"); + _piLocAccuracyBorderColor?.SetValue(_locIndObj, "rgba(30,136,229,0.85)"); + } + + _piLocLoc?.SetValue(_locIndObj, new double[] { lat, lon, 0.0 }); + _piLocBearing?.SetValue(_locIndObj, ShowBearing ? bearing : 0f); + _piLocAccuracy?.SetValue(_locIndObj, _pendingLocInd.Value.AccuracyM); + _renderNeedsUpdate = true; + } + + /// Remove the location indicator layer from the map. + public void ClearLocationIndicator() + { + _pendingLocInd = null; + _locIndObj = null; + if (_map == null || !_styleReady) return; + var style = _map.Style; + EnsureStyleApiResolved(style); + if (_miHasLayer != null && (bool)_miHasLayer.Invoke(style, [LocIndLayerId])!) + _miRemoveLayer?.Invoke(style, [LocIndLayerId]); + _renderNeedsUpdate = true; + } + + // Called by DelegateMapObserver on the observer thread — dispatch to UI thread + private void OnMapStyleLoaded() + { + Dispatcher.BeginInvoke(() => + { + _styleReady = true; + _locIndObj = null; // invalidated by style reload + if (_pendingLocInd.HasValue) + { + var p = _pendingLocInd.Value; + UpdateLocationIndicator(p.Lat, p.Lon, p.Bearing, p.AccuracyM); + } + }); + } + /// Remove a GeoJSON wifi layer set (3 security-type circle layers + source). public void RemoveWifiGeoJsonLayer(string sourceId) { @@ -333,6 +443,28 @@ private struct PIXELFORMATDESCRIPTOR private float _dpi = 1.0f; private int _renderTickCount; + // ── Location indicator state ────────────────────────────────────────────── + + /// When true, each GPS fix also re-centres the map (zoom-preserving after the first fix). + public bool FollowLocation { get; set; } = true; + + /// When false, the bearing arrow is suppressed — the indicator always points north. + public bool ShowBearing { get; set; } = true; + + private const string LocIndLayerId = "vistumbler_location"; + private object? _locIndObj; // LocationIndicatorLayer instance (null until style loads) + private bool _styleReady; // true after first/each onDidFinishLoadingStyle + private record struct LocIndParams(double Lat, double Lon, float Bearing, float AccuracyM); + private LocIndParams? _pendingLocInd; + + // Reflection cache — resolved from the returned LocationIndicatorLayer type on first use + private static System.Reflection.MethodInfo? _miAddLocInd; + private static System.Reflection.PropertyInfo? _piLocLoc; + private static System.Reflection.PropertyInfo? _piLocBearing; + private static System.Reflection.PropertyInfo? _piLocAccuracy; + private static System.Reflection.PropertyInfo? _piLocAccuracyColor; + private static System.Reflection.PropertyInfo? _piLocAccuracyBorderColor; + // ── Input state ─────────────────────────────────────────────────────────── private bool _isDragging; @@ -367,7 +499,10 @@ private void OnIsVisibleChanged(object sender, DependencyPropertyChangedEventArg { bool visible = (bool)e.NewValue; if (visible) + { + _renderNeedsUpdate = true; // ensure first frame renders even if size didn't change Dispatcher.BeginInvoke(DispatcherPriority.Loaded, (Action)TryInitialize); + } else _renderTimer?.Stop(); @@ -410,6 +545,8 @@ protected override void DestroyWindowCore(HandleRef hwnd) if (_navPopup != null) { _navPopup.IsOpen = false; _navPopup = null; } + _styleReady = false; + _locIndObj = null; _map?.Dispose(); _map = null; _frontend?.Dispose(); _frontend = null; _runLoop?.Dispose(); _runLoop = null; @@ -422,6 +559,11 @@ protected override void DestroyWindowCore(HandleRef hwnd) protected override void OnRenderSizeChanged(SizeChangedInfo info) { base.OnRenderSizeChanged(info); + + // Guard: WPF collapses the element to 0×0 when Visibility=Collapsed. + // Passing 0-size to MapLibre native crashes the OpenGL renderer. + if (info.NewSize.Width < 1 || info.NewSize.Height < 1) return; + float dpi = GetDpiScale(); int wP = Math.Max(1, (int)(info.NewSize.Width * dpi)); int hP = Math.Max(1, (int)(info.NewSize.Height * dpi)); @@ -740,8 +882,9 @@ private void InitMaplibre() .WithCachePath(System.IO.Path.Combine(System.IO.Path.GetTempPath(), "maplibre_cache.db")) .WithAssetPath(AppDomain.CurrentDomain.BaseDirectory); - var obs = new MaplibreNative.WPF.DelegateMapObserver((type, msg) => - Log($"[MapObserver:{type}] {msg}")); + var obs = new MaplibreNative.WPF.DelegateMapObserver( + (type, msg) => Log($"[MapObserver:{type}] {msg}"), + onStyleLoaded: OnMapStyleLoaded); _map = new Map(_frontend, obs, mapOptions, resOptions); Log("Map created"); From fa1f4129f436cf2865f1757375f3bc49c507774b Mon Sep 17 00:00:00 2001 From: acalcutt Date: Sat, 2 May 2026 11:26:00 -0400 Subject: [PATCH 57/63] docs: document MaplibreNative.NET.WPF in README --- README.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/README.md b/README.md index f5315d5..4483503 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,62 @@ You can check other examples at the [Examples](https://github.com/tdcosta100/Map **Attention:** the examples reference `MaplibreNative.NET.dll` at `build\Release`. Build MaplibreNative.NET with `Release` configuration before building the examples (see *Building the sources* below). +## WPF map control (`MaplibreNative.NET.WPF`) + +The `MaplibreNative.NET.WPF/` folder is an optional C# helper library that makes it easy to embed a live, interactive MapLibre map inside a **WPF window**. + +### Why it's a separate project + +`MaplibreNative.NET.dll` is a C++/CLI assembly compiled for `x64` only. A WPF project that references it directly must also be `x64` and have `true`. Isolating those constraints in their own `.csproj` keeps the consuming app free of those settings and makes the control reusable across multiple WPF apps. + +### What's in it + +| File | Purpose | +|---|---| +| `MaplibreMapHost.cs` | WPF `HwndHost` that creates a native Win32 child window, initialises a MapLibre OpenGL context inside it, and drives the render loop via a `DispatcherTimer`. Exposes a public API for camera control, GeoJSON layers, and the location indicator. | +| `DelegateMapObserver.cs` | Thin `MapObserver` subclass that forwards observer events to `Action` callbacks so you don't need to subclass `MapObserver` yourself. | + +### Key `MaplibreMapHost` API + +| Member | Description | +|---|---| +| `StyleUrl` | Dependency property — set the map style URL from XAML or code-behind. | +| `ShowNavigationControls` | Show/hide the built-in zoom-in, zoom-out, and reset-north overlay buttons. | +| `FollowLocation` | When `true`, each GPS fix re-centres the map (zoom-14 on first fix, zoom-preserving thereafter). | +| `ShowBearing` | When `true`, the location indicator arrow follows the GPS track heading; when `false` it always points north. | +| `CenterOn(lat, lon, zoom)` | Jump to a position at a given zoom level. | +| `ZoomIn()` / `ZoomOut()` | Zoom by one level. | +| `ResetNorth()` | Snap bearing back to 0°. | +| `SetWifiGeoJsonLayerData(sourceId, geoJson)` | Push or update a GeoJSON source of wifi AP circles (three security-type circle sub-layers). | +| `SetWifiGeoJsonLayer(sourceId, url)` | Fetch and display a GeoJSON layer from a URL. | +| `RemoveWifiGeoJsonLayer(sourceId)` | Remove the source and all associated layers. | +| `UpdateLocationIndicator(lat, lon, bearing, accuracyMeters)` | Show or move the blue "user location" dot. Deferred safely until the style is ready. | +| `ClearLocationIndicator()` | Remove the location dot. | + +All style-related API calls (`AddLocationIndicatorLayer`, etc.) are resolved at runtime via reflection so that the WPF library continues to compile against older DLL builds that may not yet have those methods. + +### XAML usage + +```xml + + + + +``` + +### Referencing the project + +The `.csproj` looks for `MaplibreNative.NET.dll` at `../build/Release/MaplibreNative.NET.dll` (the local CMake build output). If you vendor a pre-built DLL, override `_MaplibreNativeDll` in a `Directory.Build.props` in your repo: + +```xml + + <_MaplibreNativeDll>$(MSBuildThisFileDirectory)..\lib\x64\MaplibreNative.NET.dll + +``` + ## Programmatic style API After loading a style, you can add or modify sources and layers at runtime using the typed style API. From ea0584a9542188be31da7b0b6cadbdbe8b08baeb Mon Sep 17 00:00:00 2001 From: acalcutt Date: Sat, 2 May 2026 11:32:57 -0400 Subject: [PATCH 58/63] ci: enable git long paths before checkout in docs workflow --- .github/workflows/docs.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index cf14d17..560758d 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -20,13 +20,13 @@ jobs: runs-on: windows-latest steps: + - name: Enable Git long paths + run: git config --global core.longpaths true + - uses: actions/checkout@v4 with: submodules: recursive - - name: Enable Git long paths - run: git config --global core.longpaths true - - name: Set up MSVC developer tools uses: ilammy/msvc-dev-cmd@v1 with: From badfd81856b5fba4fa23eed0f179767f762532e7 Mon Sep 17 00:00:00 2001 From: acalcutt Date: Sat, 2 May 2026 14:54:23 -0400 Subject: [PATCH 59/63] Update windows-ci.yml --- .github/workflows/windows-ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index 90f2ffd..e28acaa 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -4,6 +4,10 @@ on: pull_request: push: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: build: name: Build Windows ${{ matrix.arch }} ${{ matrix.dotnet }} From 5beb96f3e1a00c1fcb23c9789ecdd3b6d045e009 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 2 May 2026 18:05:14 -0400 Subject: [PATCH 60/63] Release v2.0.0 (#4) * Release v2.0.0 * Update CHANGELOG.md for version 2.0.0 Expanded the changelog to include new features, improvements, and bug fixes for version 2.0.0. * workflow update * Update README.md * Update CHANGELOG.md * Update workflows for .net 6 * Update windows-ci.yml * remove master branch from workflows --------- Co-authored-by: acalcutt <3792408+acalcutt@users.noreply.github.com> Co-authored-by: Andrew Calcutt --- .github/workflows/release.yml | 9 ++++++-- .github/workflows/windows-ci.yml | 11 ++++++++-- CHANGELOG.md | 37 +++++++++++++++++++++++++++++++- CMakeLists.txt | 4 ++-- README.md | 10 ++++----- 5 files changed, 59 insertions(+), 12 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8fe2116..3c920ce 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,7 +5,6 @@ on: push: branches: - main - - master permissions: contents: write @@ -60,7 +59,7 @@ jobs: strategy: fail-fast: false matrix: - dotnet: [net8.0, net9.0] + dotnet: [net6.0, net8.0, net9.0] arch: [x64, arm64] include: - arch: x64 @@ -83,6 +82,12 @@ jobs: fetch-depth: 0 submodules: recursive + - name: Install .NET 6 SDK (targeting pack for net6.0 builds) + if: matrix.dotnet == 'net6.0' + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '6.0.x' + - name: Set up MSVC developer tools uses: ilammy/msvc-dev-cmd@v1 with: diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index e28acaa..6ba1050 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -3,9 +3,10 @@ name: Windows CI on: pull_request: push: + branches: [main] concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} cancel-in-progress: true jobs: @@ -15,7 +16,7 @@ jobs: strategy: fail-fast: false matrix: - dotnet: [net8.0, net9.0] + dotnet: [net6.0, net8.0, net9.0] arch: [x64, arm64] include: - arch: x64 @@ -37,6 +38,12 @@ jobs: with: submodules: recursive + - name: Install .NET 6 SDK (targeting pack for net6.0 builds) + if: matrix.dotnet == 'net6.0' + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '6.0.x' + - name: Set up MSVC developer tools uses: ilammy/msvc-dev-cmd@v1 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 25dfd10..9220bf3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,47 @@ ### 🐞 Bug fixes - _...Add new stuff here..._ +## 2.0.0 +### ✨ Features and improvements +- Add full Layer and Source type wrapping (CircleLayer, FillLayer, LineLayer, SymbolLayer, RasterLayer, HillshadeLayer, HeatmapLayer, BackgroundLayer, FillExtrusionLayer, LocationIndicatorLayer, ColorReliefLayer) +- Add Light, StyleImage, TransitionOptions, and Style image management +- Add full SymbolLayer layout and paint properties; missing Fill, Line, and Circle properties; Layer filter support; `AddLayer` with `beforeLayerId` +- Add remaining layer properties and `ColorReliefLayer.ColorRamp` +- Add `LocationIndicatorLayer`, `GeoJSONOptions`, Source `prefetch`/`overscale` +- Add `LineLayer.Gradient`, `HeatmapLayer.Color`, `ImageSource.SetImage` +- Add `MaplibreNative.NET.WPF`: WPF HwndHost adapter with location indicator, follow-location, and bearing controls +- Add Windows CI workflow for pull requests and pushes to main +- Add DocFX documentation site with auto-deploy to GitHub Pages +- Add `.editorconfig` and `format.ps1`; normalize source formatting + +### 🐞 Bug fixes +- Fix multiple C++/CLI compilation errors (`NativePointerHolder` C3137, generic lambdas C3923, `JSDocument` namespace, `IconPadding`, `HillshadeLayer` types, `marshal_as`) +- Fix CI long-path checkout failure on Windows runners + ## 1.0.3 ### ✨ Features and improvements -- Add multiple release targets +- Support multiple .NET target versions via `-DMAPLIBRE_NET_DOTNET_VERSION` CMake option; update examples to net8.0 + +### 🐞 Bug fixes +- Fix `MaplibreNative.NET.WPF` csproj DLL reference path (removed VistumblerCS-specific path) +- Fix release workflow to correctly publish both x64 and arm64 artifacts +- Fix CI build matrix so `dotnet × arch` combinations cross-multiply correctly ## 1.0.2 ### ✨ Features and improvements +- Add GeoJSON source/layer API to `Style`: `AddGeoJsonSource`, `SetGeoJsonSourceUrl`, `SetGeoJsonSourceData`, `HasSource`, `RemoveSource`, `AddCircleLayer`, `HasLayer`, `RemoveLayer` +- Add `MaplibreNative.NET.WPF` C# class library (`MaplibreMapHost`, `DelegateMapObserver`) with reflection-based GeoJSON layer helpers and double-click zoom + +## 1.0.1 +### ✨ Features and improvements +- Add `MapLibreLogger` to forward internal `mbgl::Log` messages to C# ### 🐞 Bug fixes +- Fix indentation in `CMakeLists.txt` source list + +## 1.0.0 +### ✨ Features and improvements +- Initial release: C++/CLI .NET 8 wrapper for maplibre-native on Windows +- Add `Map`, `MapObserver`, `Style`, `HeadlessFrontend`, `MapSnapshotter`, `FileSource`, `FileSourceManager`, `Resource`, `Response`, `LatLng`, `BoundOptions`, `MapOptions`, `AnimationOptions`, `CameraOptions`, `Image`, `PremultipliedImage`, `AlphaImage`, `BackendScope`, `RunLoop`, `AsyncRequest`, `Vector` bindings +- Switch build system to Visual Studio 17 2022 generator with `/clr:netcore`; fix `VS_GLOBAL_TargetFramework` to resolve NETSDK1013 diff --git a/CMakeLists.txt b/CMakeLists.txt index b4df970..3b2bfce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.10 FATAL_ERROR) set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/dependencies/maplibre-native/platform/windows/custom-toolchain.cmake) -set(MAPLIBRE_NET_VERSION "1.0.3") +set(MAPLIBRE_NET_VERSION "2.0.0") project("MaplibreNative.NET" LANGUAGES CXX C) set(CMAKE_CXX_STANDARD 20) @@ -13,7 +13,7 @@ endif() add_library(mbgl-dotnet SHARED) -set(MAPLIBRE_NET_DOTNET_VERSION "net8.0" CACHE STRING "Target .NET version for the C++/CLI wrapper (e.g. net8.0, net9.0)") +set(MAPLIBRE_NET_DOTNET_VERSION "net8.0" CACHE STRING "Target .NET version for the C++/CLI wrapper (e.g. net6.0, net8.0, net9.0)") set(ASSEMBLY_NAME "MaplibreNative.NET") diff --git a/README.md b/README.md index 4483503..d334bc1 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ # MaplibreNative.NET -This is a .NET wrapper to the [maplibre-native](https://github.com/maplibre/maplibre-native). It is meant to be used on Windows and requires **.NET 8.0 or newer** (uses C++/CLI `/clr:netcore`). +This is a .NET wrapper to the [maplibre-native](https://github.com/maplibre/maplibre-native). It is meant to be used on Windows and requires **.NET 6.0 or newer** (uses C++/CLI `/clr:netcore`). ## Using in your project Just add a reference to the `MaplibreNative.NET.dll` (see below how to build from sources), and you can immediately use the classes in the `MaplibreNative` namespace. -This is a minimal example using the `HeadlessFrontend` renderer, in a project with .NET 8.0+ (Windows) and WPF enabled: +This is a minimal example using the `HeadlessFrontend` renderer, in a project with .NET 6.0+ (Windows) and WPF enabled: ```cs using MaplibreNative; @@ -286,7 +286,7 @@ All sources share properties from `Source`: In Visual Studio Installer, confirm that these individual components are selected: - `C++ CMake tools for Windows` - `Git for Windows` (if git is not already installed) -- **.NET 8 SDK** (or newer) — required for the C++/CLI `/clr:netcore` compilation. +- **.NET 6 SDK** (or newer) — required for the C++/CLI `/clr:netcore` compilation. - **PowerShell** (included with Windows). ### Downloading sources @@ -327,13 +327,13 @@ $env:VCPKG_BINARY_SOURCES = "clear;files,$PWD\dependencies\maplibre-native\platf "-DCMAKE_CXX_FLAGS=/wd4267" ``` -To target a specific .NET version (default is `net8.0`): +To target a specific .NET version (default is `net8.0`; supported values: `net6.0`, `net8.0`, `net9.0`): ```powershell & $cmake -B build -G "Visual Studio 17 2022" -A x64 ` -DMLN_WITH_OPENGL=ON -DMLN_WITH_EGL=OFF -DMLN_WITH_VULKAN=OFF ` "-DCMAKE_CXX_FLAGS=/wd4267" ` - "-DMAPLIBRE_NET_DOTNET_VERSION=net9.0" + "-DMAPLIBRE_NET_DOTNET_VERSION=net6.0" ``` ### Building From fd0bb686584092a99fc9da487dd87bc3d60ce2cc Mon Sep 17 00:00:00 2001 From: Andrew Calcutt Date: Thu, 7 May 2026 22:10:50 -0400 Subject: [PATCH 61/63] Add Layer property read-back, Style.GetSourceAttributions, Renderer.QueryRenderedFeatures (#5) --- include/Layer.h | 10 ++++++++++ include/Renderer.h | 16 ++++++++++++++++ include/Style.h | 5 +++++ src/Layer.cpp | 16 ++++++++++++++++ src/Renderer.cpp | 48 ++++++++++++++++++++++++++++++++++++++++++++++ src/Style.cpp | 17 ++++++++++++++++ 6 files changed, 112 insertions(+) diff --git a/include/Layer.h b/include/Layer.h index cc81583..662138b 100644 --- a/include/Layer.h +++ b/include/Layer.h @@ -83,5 +83,15 @@ namespace DOTNET_NAMESPACE /// Throws if the JSON cannot be parsed as a valid filter expression. /// System::Void SetFilter(System::String^ filterJson); + + /// + /// Returns the JSON-encoded value of a paint property, or an empty string if not set. + /// + System::String^ GetPaintProperty(System::String^ name); + + /// + /// Returns the JSON-encoded value of a layout property, or an empty string if not set. + /// + System::String^ GetLayoutProperty(System::String^ name); }; } diff --git a/include/Renderer.h b/include/Renderer.h index 6bb1c12..25f9b5f 100644 --- a/include/Renderer.h +++ b/include/Renderer.h @@ -23,6 +23,22 @@ namespace DOTNET_NAMESPACE System::Void DumpDebugLogs(); System::Void ReduceMemoryUse(); System::Void ClearData(); + + /// + /// Query rendered features at a screen point. Returns a GeoJSON FeatureCollection JSON string. + /// Pass null or an empty array for layerIds to query all layers. + /// + System::String^ QueryRenderedFeaturesAtPoint( + double x, double y, + System::Collections::Generic::IEnumerable^ layerIds); + + /// + /// Query rendered features within a screen bounding box. Returns a GeoJSON FeatureCollection JSON string. + /// Pass null or an empty array for layerIds to query all layers. + /// + System::String^ QueryRenderedFeaturesInBox( + double x1, double y1, double x2, double y2, + System::Collections::Generic::IEnumerable^ layerIds); internal: Renderer(NativePointerHolder^ nativePointerHolder); }; diff --git a/include/Style.h b/include/Style.h index c56b224..aff140d 100644 --- a/include/Style.h +++ b/include/Style.h @@ -195,6 +195,11 @@ namespace DOTNET_NAMESPACE System::String^ color, float radius, float opacity, System::String^ filterJson); + /// + /// Returns a deduplicated list of non-empty attribution strings from all loaded sources. + /// + System::Collections::Generic::List^ GetSourceAttributions(); + internal: Style(NativePointerHolder^ nativePointerHolder); }; diff --git a/src/Layer.cpp b/src/Layer.cpp index 415914b..4524780 100644 --- a/src/Layer.cpp +++ b/src/Layer.cpp @@ -192,4 +192,20 @@ namespace DOTNET_NAMESPACE _layer->setFilter(*filter); } + + System::String^ Layer::GetPaintProperty(System::String^ name) + { + auto prop = _layer->getProperty(msclr::interop::marshal_as(name)); + if (prop.getKind() == mbgl::style::StyleProperty::Kind::Undefined) + return System::String::Empty; + return msclr::interop::marshal_as(ValueToJsonString(prop.getValue())); + } + + System::String^ Layer::GetLayoutProperty(System::String^ name) + { + auto prop = _layer->getProperty(msclr::interop::marshal_as(name)); + if (prop.getKind() == mbgl::style::StyleProperty::Kind::Undefined) + return System::String::Empty; + return msclr::interop::marshal_as(ValueToJsonString(prop.getValue())); + } } diff --git a/src/Renderer.cpp b/src/Renderer.cpp index 8587990..f4fd631 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -2,6 +2,11 @@ #include "RendererObserver.h" #include "UpdateParameters.h" #include +#include +#include +#include +#include +#include namespace DOTNET_NAMESPACE { @@ -42,4 +47,47 @@ namespace DOTNET_NAMESPACE { NativePointer->clearData(); } + + static mbgl::RenderedQueryOptions BuildQueryOptions( + System::Collections::Generic::IEnumerable^ layerIds) + { + mbgl::RenderedQueryOptions opts; + if (layerIds != nullptr) + { + std::vector ids; + for each (System::String^ id in layerIds) + ids.push_back(msclr::interop::marshal_as(id)); + if (!ids.empty()) + opts.layerIDs = std::move(ids); + } + return opts; + } + + static System::String^ FeaturesToGeoJson(const std::vector& features) + { + mapbox::feature::feature_collection coll; + coll.reserve(features.size()); + for (const auto& f : features) + coll.push_back(f); + std::string json = mapbox::geojson::stringify(coll); + return msclr::interop::marshal_as(json); + } + + System::String^ Renderer::QueryRenderedFeaturesAtPoint( + double x, double y, + System::Collections::Generic::IEnumerable^ layerIds) + { + mbgl::ScreenCoordinate pt{ x, y }; + auto features = NativePointer->queryRenderedFeatures(pt, BuildQueryOptions(layerIds)); + return FeaturesToGeoJson(features); + } + + System::String^ Renderer::QueryRenderedFeaturesInBox( + double x1, double y1, double x2, double y2, + System::Collections::Generic::IEnumerable^ layerIds) + { + mbgl::ScreenBox box{ { x1, y1 }, { x2, y2 } }; + auto features = NativePointer->queryRenderedFeatures(box, BuildQueryOptions(layerIds)); + return FeaturesToGeoJson(features); + } } diff --git a/src/Style.cpp b/src/Style.cpp index a507b3e..1a98a42 100644 --- a/src/Style.cpp +++ b/src/Style.cpp @@ -635,4 +635,21 @@ namespace DOTNET_NAMESPACE { NativePointer->removeImage(Convert::ToStdString(id)); } + + System::Collections::Generic::List^ Style::GetSourceAttributions() + { + auto result = gcnew System::Collections::Generic::List(); + auto seen = gcnew System::Collections::Generic::HashSet(); + for (auto* raw : NativePointer->getSources()) + { + auto attr = raw->getAttribution(); + if (attr && !attr->empty()) + { + auto managed = Convert::ToSystemString(*attr); + if (seen->Add(managed)) + result->Add(managed); + } + } + return result; + } } From 6e1c517db7c26717c5eabfea7315eedf2c1bb772 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 22:12:23 -0400 Subject: [PATCH 62/63] Release v2.1.0 (#6) Co-authored-by: acalcutt <3792408+acalcutt@users.noreply.github.com> --- CHANGELOG.md | 7 +++++++ CMakeLists.txt | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9220bf3..18c5930 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ ### 🐞 Bug fixes - _...Add new stuff here..._ +## 2.1.0 +### ✨ Features and improvements + +### 🐞 Bug fixes + +- Add Layer property read-back, Style.GetSourceAttributions, Renderer.Q… ([#5](https://github.com/acalcutt/MaplibreNative.NET/pull/5)) (@acalcutt) + ## 2.0.0 ### ✨ Features and improvements - Add full Layer and Source type wrapping (CircleLayer, FillLayer, LineLayer, SymbolLayer, RasterLayer, HillshadeLayer, HeatmapLayer, BackgroundLayer, FillExtrusionLayer, LocationIndicatorLayer, ColorReliefLayer) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b2bfce..5112da6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.10 FATAL_ERROR) set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/dependencies/maplibre-native/platform/windows/custom-toolchain.cmake) -set(MAPLIBRE_NET_VERSION "2.0.0") +set(MAPLIBRE_NET_VERSION "2.1.0") project("MaplibreNative.NET" LANGUAGES CXX C) set(CMAKE_CXX_STANDARD 20) From f201781d08a676eaf04c4da95b9830497b473651 Mon Sep 17 00:00:00 2001 From: Andrew Calcutt Date: Thu, 7 May 2026 22:14:13 -0400 Subject: [PATCH 63/63] Clean up CHANGELOG by removing empty bug fix section Removed empty sections for bug fixes in CHANGELOG. --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18c5930..b412f7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,9 +9,6 @@ ## 2.1.0 ### ✨ Features and improvements - -### 🐞 Bug fixes - - Add Layer property read-back, Style.GetSourceAttributions, Renderer.Q… ([#5](https://github.com/acalcutt/MaplibreNative.NET/pull/5)) (@acalcutt) ## 2.0.0