You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
WPF: MlnMapHost removed — The old HwndHost+Popup renderer has been deleted. MlnMapImage is now the only WPF map control. Migration: replace <wpf:MlnMapHost …> with <wpf:MlnMapImage …> in XAML; the public API surface (properties, events, methods) is identical, including the MapClicked event and its MlnMapClickEventArgs args.
MAUI Windows: WS_POPUP GL window renderer removed — The floating WS_POPUP renderer is gone; MapImageView (in-tree Image+WriteableBitmap) is now the only Windows path.
MAUI Windows: SwapChainMapView renamed to MapImageView — The class stopped using a swap chain when the renderer moved to Image+WriteableBitmap; the name now matches the implementation. It is created internally by the handler, so most apps are unaffected.
MapLibreMap.RotateGestureEnabled renamed to RotateGesturesEnabled — Now consistent with ScrollGesturesEnabled / TiltGesturesEnabled / ZoomGesturesEnabled (and with the README, which already documented the plural name).
✨ Features and improvements
Airspace-free map rendering — WPF (MlnMapImage) — The map is a real WPF Image element; nav/GPS/attribution controls are ordinary WPF children with correct z-order, clipping, DPI, and hit-testing. No more floating Popup windows, no HwndHost airspace, no per-tick overlay realignment. Powered by glReadPixels(GL_BGRA) into a WriteableBitmap after each frame — see docs/design/in-tree-map-surface.md.
Airspace-free map rendering — MAUI Windows (MapImageView) — The map is a real WinUI Image element; nav/GPS/attribution are real XAML children. The floating WS_POPUP window, PopupWndProc, and per-tick realignment are gone. Same glReadPixels+WriteableBitmap approach as WPF, with pixels written via IBufferByteAccess.
Android: TextureView replaces SurfaceView — TextureView is an ordinary in-tree View with no compositing hole, so MAUI content layers reliably above the map on Android. SurfaceCallback/ISurfaceHolderCallback replaced by TextureSurfaceListener/ISurfaceTextureListener.
Declarative over-the-map overlay elements — New Pin, Polyline, Polygon, and Circle overlay types modelled after Microsoft.Maui.Controls.Maps. Declared as direct children of MapLibreMap (or produced via ItemsSource/ItemTemplate); property changes sync to MapLibre style layers automatically. Each compiles to a GeoJSON source + style layer: Polyline→LineLayer, Polygon→FillLayer+LineLayer, Pin→SymbolLayer, Circle→circumference polygon+FillLayer.
Data-bound overlays: ItemsSource / ItemTemplate / ItemTemplateSelector — MapLibreMap now supports MVVM-style data binding of overlay elements, modelled after Microsoft.Maui.Controls.Maps.Map. Bind ItemsSource to a collection and supply an ItemTemplate (or ItemTemplateSelector) whose DataTemplate produces an overlay element (Pin, Polyline, Polygon, or Circle); each item's element gets the item as its BindingContext. Collections implementing INotifyCollectionChanged sync add/remove/replace/reset automatically. The WPF MlnMapImage gains an equivalent ItemsSource that binds an IEnumerable of the new MlnMapMarker model (lat/lon + optional label/colour) and renders them as a managed GeoJSON circle + label layer, with live INotifyCollectionChanged/INotifyPropertyChanged sync.
Pin upgrade: SymbolLayer + SDF sprite — Pin now renders via a SymbolLayer with an SDF sprite (mln_marker) instead of a CircleLayer. Supports icon-color tinting, configurable text labels, and all standard symbol layout/paint properties.
AddSpriteImage / RemoveSpriteImage — New methods on IMapLibreMapController (and MlnMapImage) for adding and removing named SDF or raster sprites at runtime, on all platforms.
MAUI Windows nav d-pad — The MAUI Windows navigation panel now has a full 4-way rotate/pitch/compass d-pad (▲/▼ pitch, ◀/▶ rotate, centre reset-north + live compass tick), matching the WPF renderer added in 3.3.0.
SymbolLayerProperties — New properties class covering the full symbol layer paint and layout property set.
MapSpan camera overloads — New JumpTo / EaseTo / FlyTo overloads on IMapLibreMapController that take a MapSpan, for fitting a geographic region into view.
MapLibreMap.VisibleRegion read-back — New VisibleRegion property (a MapSpan?) exposing the region currently visible on screen, refreshed whenever the camera becomes idle and raising PropertyChanged for data binding. A GetVisibleRegion() method reads it on demand. Backed by a new GetVisibleBounds() method on IMapLibreMapController (all platforms) that returns the actual visible lat/lng bounding box. The WPF MlnMapImage exposes the same VisibleRegion (a read-only DependencyProperty) and GetVisibleRegion().
Sample: ShapesPage — New sample page demonstrating Polyline, Polygon, and Circle overlay elements. MarkersPage converted to declarative Pin elements.
Vortice D3D packages removed — Vortice.Direct3D9 (WPF), Vortice.Direct3D11, and Vortice.DXGI (MAUI handlers) are no longer dependencies.
Offline regions + ambient cache (cabi 2.2.0) — New mbgl_offline_* C ABI family wrapping mbgl::DatabaseFileSource, and a new MbglOfflineManager (Task-based async API in MapLibreNative.Maui): create tile-pyramid or GeoJSON-geometry offline regions, start/pause downloads with progress + error observers (RegionProgress/RegionError events), list regions, query download status, round-trip opaque binary region metadata, delete/invalidate regions, merge (side-load) a secondary cache database, set the Mapbox tile-count limit, and ambient-cache maintenance (SetMaximumAmbientCacheSizeAsync, ClearAmbientCacheAsync, InvalidateAmbientCacheAsync, PackDatabaseAsync, ResetDatabaseAsync). Callbacks fire on MapLibre's database thread; the C# wrapper surfaces them as Tasks and events. Exercised end-to-end by the WPF sample's --autotest (download → progress → list → metadata → delete → ambient clear/pack).
Persistent tile cache by default — mbgl's default cache is :memory:, and none of the map views passed a cache path, so nothing survived an app restart and offline regions could never be served to the map. All map surfaces (MAUI Windows/Android/iOS + WPF MlnMapImage) now default to the new MbglCache.DefaultPath ({LocalApplicationData}/MapLibreNative.Maui/{processName}/cache.db), which MbglOfflineManager also uses by default — so offline regions downloaded by the manager are rendered by the map automatically, including with the network forced offline via MbglNetwork.Online = false.
MAUI sample: Offline page — New tab demonstrating the offline workflow: download the visible region (with live progress from the observer), list regions with size + metadata, delete all, and a network offline/online toggle.
GeoJSON source options / clustering — New mbgl_style_add_geojson_source_options C ABI function, MbglStyle.AddGeoJsonSourceOptions(sourceId, optionsJson), and an AddGeoJsonSource(sourceName, source, optionsJson) overload on IMapLibreMapController (all platforms) and WPF MlnMapImage. Accepts the style-spec GeoJSON source options (cluster, clusterRadius, clusterMaxZoom, clusterMinPoints, clusterProperties, maxzoom, buffer, tolerance, lineMetrics), enabling point clustering for typed (non-JSON-spec) sources.
Cluster expansion queries — New mbgl_map_query_feature_extensions C ABI function plus QueryFeatureExtensions (on MbglMap) and convenience helpers GetClusterExpansionZoom, GetClusterChildren, and GetClusterLeaves (on MbglMap, IMapLibreMapController, and MlnMapImage) for drilling into supercluster clusters (tap-to-expand, list cluster members). Exercised end-to-end by the WPF sample's --autotest harness.
Source-feature queries — New mbgl_map_query_source_features C ABI function and QuerySourceFeatures(sourceId, sourceLayerIds, filterJson) on MbglMap, IMapLibreMapController, and MlnMapImage: query all features in a source's data (with optional style-spec filter), independent of what is currently rendered.
Camera edge padding — New mbgl_map_jump_to_padded / mbgl_map_ease_to_padded / mbgl_map_fly_to_padded / mbgl_map_get_camera C ABI functions and matching padded JumpTo/EaseTo/FlyTo overloads on MbglMap, IMapLibreMapController, and MlnMapImage (plus MbglMap.GetCamera). Padding (top/left/bottom/right, screen px) centres the target in the unobscured part of the viewport — useful when panels overlap the map. NaN zoom/bearing/pitch means "keep current value" in the padded variants. MlnMapImage also gains plain full-camera JumpTo/EaseTo/FlyTo.
ScaleBy — New mbgl_map_scale_by C ABI function and ScaleBy(scale, anchorX, anchorY, durationMs) on MbglMap, IMapLibreMapController, and MlnMapImage for anchored zoom (2.0 = one zoom level in).
Offline mode toggle — New mbgl_network_status_set / mbgl_network_status_get C ABI functions and static MbglNetwork.Online property: force MapLibre offline (serve only cached resources) and back online at runtime.
API key + cache size at map creation — New mbgl_map_create2 C ABI function; MbglMap's constructor gains optional apiKey and maxCacheSizeBytes parameters (ResourceOptions::withApiKey / withMaximumCacheSize).
Transform state read-back — New mbgl_map_is_gesture_in_progress / mbgl_map_is_rotating / mbgl_map_is_scaling / mbgl_map_is_panning C ABI functions and matching MbglMap properties.
🐞 Bug fixes
Blank map on WPF and MAUI Windows — WGLRenderableResource::bind() in platform_frontend_windows.cpp unconditionally calls glBindFramebuffer(0), so the WGL_NV_DX_interop2 custom FBOs were never rendered into. Fixed by dropping D3D interop entirely and reading FBO 0 directly with glReadPixels.
WPF: overlay button clicks not registering — Hit-testing on nav/GPS Border buttons in MlnMapImage was blocked by the map Image absorbing pointer events. Fixed by routing pointer events correctly through the overlay children.
MAUI Windows: renderer crash on startup — the Windows map view could throw on the first render tick before WriteableBitmap was allocated. Added null guards around OnRendering and deferred Start() to View.Loaded.
Nav d-pad rotate: cumulative drift — Repeated ±15° bearing increments accumulated floating-point error so a full 24-step turn did not return to north. Fixed by snapping the new bearing to the nearest multiple of the increment.
MAUI Windows zoom buttons: white squares / tap stealing — +/− buttons showed as white squares on some WinUI font configurations (full-width Unicode U+FF0B/FF0D). Replaced with ASCII +/− at FontSize 18 Bold. Also fixed the map Image absorbing taps meant for overlay buttons.
ShapesPage crash on geometry update — Rebuilding a Polyline/Polygon source on ObservableCollection.CollectionChanged disposed the GeoJSON source while MapLibre still referenced it. Fixed by updating the geometry in place instead of removing and re-adding the source.
Circle overlay crash on radius change (radius-dependent) — GeographyUtils.ToCircumferencePositions computed the ring's closing vertex trigonometrically at 360°, which for many radii is not bit-identical to the 0° vertex (one-ulp difference). GeoJSON.Text's Polygon constructor requires exactly closed rings and threw ArgumentException inside the Circle.Radius setter, crashing the app — e.g. growing a 3 km circle to 4 km crashed while shrinking worked. The ring is now closed with a copy of the first position.
MAUI Windows: map dead after switching Shell tabs away and back — On a tab switch WinUI only unloads the platform view (the handler is never disconnected), and the controller destroyed the native map on Unloaded with nothing recreating it on re-entry: the map stopped rendering and the nav/GPS controls, overlay elements, and programmatic updates (e.g. changing a Circle's Radius) all went dead. The Windows controller now rebuilds the native map view when its platform view is re-loaded, restores the camera saved at teardown, and reloads the style — which re-fires StyleLoaded so declarative overlay elements re-materialise.
Style source/layer ID lists were newline-ambiguous — mbgl_style_get_source_ids / mbgl_style_get_layer_ids returned newline-joined strings, which is ambiguous because MapLibre IDs may contain any character including \n. They now return a JSON array; MbglStyle.GetSourceIds() / GetLayerIds() parse it (public C# API unchanged).