Skip to content

v4.0.0

Choose a tag to compare

@github-actions github-actions released this 05 Jul 13:57
· 23 commits to main since this release

⚠️ Breaking changes

  • 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 SurfaceViewTextureView 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: PolylineLineLayer, PolygonFillLayer+LineLayer, PinSymbolLayer, Circle→circumference polygon+FillLayer.
  • Data-bound overlays: ItemsSource / ItemTemplate / ItemTemplateSelectorMapLibreMap 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 spritePin 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 removedVortice.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 WindowsWGLRenderableResource::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-ambiguousmbgl_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).