Releases: TechIdiots-LLC/MaplibreNativeMAUI
Releases · TechIdiots-LLC/MaplibreNativeMAUI
Release list
v4.5.1
🐞 Bug fixes
- Android: the 64-bit
libmln-cabi.sowas not 16 KB page aligned, so Google Play rejected apps that shipped it — Android 15 allows devices with 16 KB memory pages, and Play now blocks uploads whose native libraries are linked for 4 KB ("Your app is not compatible with 16 KB memory page sizes"). The release CI builds with NDK r27, whose linker still defaultsmax-page-sizeto 4 KB; r28's picks 16 KB on its own, which is why a local build looked fine while every publishedarm64-v8aandx86_64.so— 4.5.0 included — carried0x1000-alignedLOADsegments. The Android link now passes-Wl,-z,max-page-size=16384on the two 64-bit ABIs whatever the NDK version, and both Android native workflows fail the build if the linked.socomes out below 16 KB.armeabi-v7ais untouched — the NDK applies the flag to 64-bit ABIs only, and 32-bit Android has no 16 KB devices. Windows and Apple need nothing: the Windows build emits a PE via MSVC, where-Wl,-zhas no meaning and the loader wants 4 KB sections, and on Applemln-cabiis a static archive with no LOAD segments of its own — the app's own arm64 link already aligns to 16 KB. Reported in #32.
v4.5.0
✨ Features and improvements
GpsFollowZoomMode/GpsFollowZoom— configurable zoom when GPS Follow engages — new properties on the MAUIMapLibreMapcontrol (and matching dependency properties on the WPFMlnMapImage) controlling the camera zoom applied when the GPS control enters Follow mode (via the on-map button, or the first fix while following).KeepCurrent(default) preserves the old behaviour (only zooms to 14 when further out than 8);Fixedalways eases to theGpsFollowZoomlevel (default 16, the vistumbler-android behaviour);Accuracycomputes the zoom from the fix's reported accuracy so the accuracy circle spans about a third of the viewport — a sharp fix lands at street level (clamped to 17), a coarse cell-grade fix stays zoomed out to cover its uncertainty (clamped to 10). Later fixes never change the zoom, so a manual pinch/scroll zoom sticks until Follow is re-entered. Entering Follow via the button now also applies the entry zoom on Android/iOS (previously only Windows/WPF re-eased the camera there).
v4.4.0
✨ Features and improvements
- GPS control reworked: independent tracking and bearing buttons — on all platforms (Android, iOS/MacCatalyst, WinUI, WPF) the on-map GPS control's two buttons now have separate jobs, mirroring maplibre-gl-js's
GeolocateControland the Vistumbler Android app. The top button cycles the tracking mode Off ○ → Show ⊙ → Follow ◎ → Off (the old combinedFollowBearingstate is gone), and the bottom button — previously a plain reset-to-north — cycles the camera bearing mode Free ↺ → North-up N → GPS bearing ➤ → Free. The two combine: Follow + GPS bearing re-centres and rotates with each fix; Show + GPS bearing rotates the camera with the fix bearing without re-centring. Manually panning the map while in Follow drops the control back to Show (one click on the button re-enters Follow), and manually rotating (two-finger twist on Android, nav d-pad on any platform) drops the bearing mode back to Free; theCameraTrackingDismissedevent fires when a pan dismisses Follow. The location dot now always points in the direction of travel regardless of bearing mode.
v4.2.2
🐞 Bug fixes
- Android: fixed a native crash (SIGSEGV in
OnlineFileSource) under heavy tile traffic — when an HTTP response arrived, the provider removed the request from its pending table before posting the callback onto the RunLoop. A request destroyed in that window (routine with many vector sources loading while panning/zooming or backgrounding the app, where superseded requests are constantly cancelled) couldn't be flagged as cancelled — its destructor found nothing to mark — so the already-posted callback ran against the freedOnlineFileRequestand crashed inmbgl::util::Timer. The pending entry now stays registered until the callback actually executes, and the posted closure re-checks the cancelled flag (it runs on the same RunLoop thread as the request's destructor, making the check decisive). - Manually opened attribution stays open long enough to read — tapping the collapsed ⓘ chip now pins the banner on every platform: on Android/iOS camera motion no longer collapses it instantly (with GPS-follow active the camera eases on every fix, which swatted the banner shut the moment it opened), and on all platforms (Android, iOS/MacCatalyst, WinUI, WPF) a manually opened banner gets a longer 10 s auto-collapse instead of the standard 5 s. Automatic expansions (style load, attribution content changes) keep the existing behaviour.
v4.2.1
🐞 Bug fixes
- Attribution banner no longer re-expands on every source refresh —
onSourceChangedfires for every runtime source mutation, including an app updating a GeoJSON source on a timer, and the attribution handler rebuilt the banner and re-expanded it each time — so an app refreshing a live overlay every few seconds had the attribution popup permanently reopening. The Android, iOS/MacCatalyst, and WPF controls now track the attribution content they last applied and only rewrite/re-expand the banner when that content actually changed (a new style load still shows it once, and newly added sources still surface their attribution). The WinUI control already only refreshed while empty and is unchanged.
v4.2.0
✨ Features and improvements
- Android: two-finger pinch-zoom, rotate and tilt — the two-finger gesture handling was reworked, porting maplibre-gl-js's
two_fingers_touchmodel (MapLibreMapController.Android.cs). Each of zoom / rotate / tilt now activates independently off its own threshold (zoom: cumulative log2-zoom delta; rotate: pixels along the touch-circle circumference, scaled by the smallest finger separation seen; tilt: both fingers moving vertically in the same direction), so an ordinary pinch no longer also spins or tilts the map. The map also claims the touch stream from any ancestorScrollView/ViewPager2viarequestDisallowInterceptTouchEvent, so gestures work when the map is hosted inside a scrolling/paged container. - Sample app: map pages are one continuous scroll — the demo map pages previously split the screen into a map area and a separate fixed control panel (two disconnected surfaces). Each map page is now a single
ScrollViewwith the map at the top (bounded height) and the controls flowing directly beneath, so dragging the map pans it while dragging the controls scrolls the page.
🐞 Bug fixes
- Android: app crashed (native stack overflow) as soon as the map opened — the render callback ran
Render()+RunLoop.RunOnce()synchronously from inside mbgl'supdate()dispatch, so a burst of tile-load events during the initial style load recursed on the same native stack until the (very stack-hungry) Adreno GL driver overflowed the thread. The callback now only flags a pending frame; a separatePostOnAnimationloop drivesRender()/RunOnce(), mirroring the Windows controller. - Android: touch gestures did nothing (only the on-screen buttons worked) —
RotateGesturesEnabled/ScrollGesturesEnabled/TiltGesturesEnabled/ZoomGesturesEnabledwere declared without a default, so they defaulted tofalseand MAUI's initial property sync disabled all gesture input at startup. They now default totrue. - Android: panning was far slower than the finger, and pinch anchored to the wrong point — touch screen-coordinates (pan delta, pinch focus, tap position, rotate pivot) were divided by the display's pixel ratio before being handed to the native map, which uses the same raw device-pixel space as
SetSize. On a high-density/scaled screen a full-width drag panned only a fraction of the map. The redundant division was removed. - Android: polygon fills rendered as a checkerboard and tiles showed white seams — the EGL config requested no depth or stencil buffer, but mbgl-core's fill-layer renderer needs the stencil buffer for polygon tessellation and tile-boundary clipping (Windows already requests a depth/stencil pixel format).
EGL_DEPTH_SIZE, 24andEGL_STENCIL_SIZE, 8are now requested. - Android: map content stretched/squished (or blanked) after rotating the device —
RendererBackend::assumeViewport()only updates mbgl-core's cached viewport; it never callsglViewport(). Since the GL viewport is context state (not surface state) it never reset on resize, so the hardware viewport stayed frozen at its first-frame value and content kept rasterizing through the stale rectangle. The Android backend now issues a realglViewport()inupdateAssumedState()(and recreates the EGL surface on size changes), keeping the viewport honest across rotations and tab switches. - Android: tiles stopped refreshing when zooming in (stuck on lower-zoom content) — the C#
HttpClienttile provider never cancelled requests mbgl superseded, so they ran to completion and starved the connection pool of slots for the tiles actually needed at the new zoom. The provider protocol gained a cancel callback (mbgl_set_http_cancel_provider) so superseded fetches are aborted and their connections freed. Only affected Android (Windows uses mbgl-core's built-in native HTTP).
v4.1.3
🐞 Bug fixes
- Runtime vector-source layers now relayout via the upstream source-layer fix instead of a local workaround —
mbgl::style::Layer::setSourceLayer()(andsetSourceID()) now callobserver->onLayerChanged()upstream (maplibre/maplibre-native#4372), so a circle/symbol/etc. layer whosesource-layeris set viaSetSourceLayerafter it is added to the style correctly triggers a relayout of already-loaded tiles. Thedependencies/maplibre-nativesubmodule is bumped to include that fix, and the visibility-toggle workaround previously carried inmbgl_layer_set_source_layer(native/src/mln_cabi.cpp) has been removed. Runtime behaviour is unchanged — the workaround is simply no longer needed. See the 4.1.0-era source-layer bug fix below for the original symptom.
v4.1.2
🐞 Bug fixes
- MAUI Windows: double-clicking the nav/GPS/d-pad overlay buttons leaked through to the map — On WinUI the second click of a fast double-click is raised as
DoubleTapped(not a secondTapped), so the overlay buttons, which only handledTapped, dropped every second press and let the unhandledDoubleTappedbubble past the button — zooming/panning the map "behind" it. Fixed by also handlingDoubleTappedon the zoom (+/−) buttons, GPS buttons, and rotate/pitch d-pad arrows (running the same action and marking the event handled), and swallowingDoubleTappedon the attribution chip.
v4.1.0
v4.0.0
⚠️ Breaking changes
- WPF:
MlnMapHostremoved — The oldHwndHost+Popuprenderer has been deleted.MlnMapImageis 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 theMapClickedevent and itsMlnMapClickEventArgsargs. - MAUI Windows:
WS_POPUPGL window renderer removed — The floatingWS_POPUPrenderer is gone;MapImageView(in-treeImage+WriteableBitmap) is now the only Windows path. - MAUI Windows:
SwapChainMapViewrenamed toMapImageView— The class stopped using a swap chain when the renderer moved toImage+WriteableBitmap; the name now matches the implementation. It is created internally by the handler, so most apps are unaffected. MapLibreMap.RotateGestureEnabledrenamed toRotateGesturesEnabled— Now consistent withScrollGesturesEnabled/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 WPFImageelement; nav/GPS/attribution controls are ordinary WPF children with correct z-order, clipping, DPI, and hit-testing. No more floatingPopupwindows, noHwndHostairspace, no per-tick overlay realignment. Powered byglReadPixels(GL_BGRA)into aWriteableBitmapafter each frame — seedocs/design/in-tree-map-surface.md. - Airspace-free map rendering — MAUI Windows (
MapImageView) — The map is a real WinUIImageelement; nav/GPS/attribution are real XAML children. The floatingWS_POPUPwindow,PopupWndProc, and per-tick realignment are gone. SameglReadPixels+WriteableBitmapapproach as WPF, with pixels written viaIBufferByteAccess. - Android:
TextureViewreplacesSurfaceView—TextureViewis an ordinary in-treeViewwith no compositing hole, so MAUI content layers reliably above the map on Android.SurfaceCallback/ISurfaceHolderCallbackreplaced byTextureSurfaceListener/ISurfaceTextureListener. - Declarative over-the-map overlay elements — New
Pin,Polyline,Polygon, andCircleoverlay types modelled afterMicrosoft.Maui.Controls.Maps. Declared as direct children ofMapLibreMap(or produced viaItemsSource/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—MapLibreMapnow supports MVVM-style data binding of overlay elements, modelled afterMicrosoft.Maui.Controls.Maps.Map. BindItemsSourceto a collection and supply anItemTemplate(orItemTemplateSelector) whoseDataTemplateproduces an overlay element (Pin,Polyline,Polygon, orCircle); each item's element gets the item as itsBindingContext. Collections implementingINotifyCollectionChangedsync add/remove/replace/reset automatically. The WPFMlnMapImagegains an equivalentItemsSourcethat binds anIEnumerableof the newMlnMapMarkermodel (lat/lon + optional label/colour) and renders them as a managed GeoJSON circle + label layer, with liveINotifyCollectionChanged/INotifyPropertyChangedsync. Pinupgrade:SymbolLayer+ SDF sprite —Pinnow renders via aSymbolLayerwith an SDF sprite (mln_marker) instead of aCircleLayer. Supportsicon-colortinting, configurable text labels, and all standard symbol layout/paint properties.AddSpriteImage/RemoveSpriteImage— New methods onIMapLibreMapController(andMlnMapImage) 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.MapSpancamera overloads — NewJumpTo/EaseTo/FlyTooverloads onIMapLibreMapControllerthat take aMapSpan, for fitting a geographic region into view.MapLibreMap.VisibleRegionread-back — NewVisibleRegionproperty (aMapSpan?) exposing the region currently visible on screen, refreshed whenever the camera becomes idle and raisingPropertyChangedfor data binding. AGetVisibleRegion()method reads it on demand. Backed by a newGetVisibleBounds()method onIMapLibreMapController(all platforms) that returns the actual visible lat/lng bounding box. The WPFMlnMapImageexposes the sameVisibleRegion(a read-onlyDependencyProperty) andGetVisibleRegion().- Sample:
ShapesPage— New sample page demonstratingPolyline,Polygon, andCircleoverlay elements.MarkersPageconverted to declarativePinelements. - Vortice D3D packages removed —
Vortice.Direct3D9(WPF),Vortice.Direct3D11, andVortice.DXGI(MAUI handlers) are no longer dependencies. - Offline regions + ambient cache (cabi 2.2.0) — New
mbgl_offline_*C ABI family wrappingmbgl::DatabaseFileSource, and a newMbglOfflineManager(Task-based async API inMapLibreNative.Maui): create tile-pyramid or GeoJSON-geometry offline regions, start/pause downloads with progress + error observers (RegionProgress/RegionErrorevents), 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 asTasks 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 + WPFMlnMapImage) now default to the newMbglCache.DefaultPath({LocalApplicationData}/MapLibreNative.Maui/{processName}/cache.db), whichMbglOfflineManageralso uses by default — so offline regions downloaded by the manager are rendered by the map automatically, including with the network forced offline viaMbglNetwork.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_optionsC ABI function,MbglStyle.AddGeoJsonSourceOptions(sourceId, optionsJson), and anAddGeoJsonSource(sourceName, source, optionsJson)overload onIMapLibreMapController(all platforms) and WPFMlnMapImage. 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_extensionsC ABI function plusQueryFeatureExtensions(onMbglMap) and convenience helpersGetClusterExpansionZoom,GetClusterChildren, andGetClusterLeaves(onMbglMap,IMapLibreMapController, andMlnMapImage) for drilling into supercluster clusters (tap-to-expand, list cluster members). Exercised end-to-end by the WPF sample's--autotestharness. - Source-feature queries — New
mbgl_map_query_source_featuresC ABI function andQuerySourceFeatures(sourceId, sourceLayerIds, filterJson)onMbglMap,IMapLibreMapController, andMlnMapImage: 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_cameraC ABI functions and matching paddedJumpTo/EaseTo/FlyTooverloads onMbglMap,IMapLibreMapController, andMlnMapImage(plusMbglMap.GetCamera). Padding (top/left/bottom/right, screen px) centres the target in the unobscured part of the viewport — useful when panels overlap the map.NaNzoom/bearing/pitch means "keep current value" in the padded variants.MlnMapImagealso gains plain full-cameraJumpTo/EaseTo/FlyTo. ScaleBy— Newmbgl_map_scale_byC ABI function andScaleBy(scale, anchorX, anchorY, durationMs)onMbglMap,IMapLibreMapController, andMlnMapImagefor anchored zoom (2.0 = one zoom level in).- Offline mode toggle — New
mbgl_network_status_set/mbgl_network_status_getC ABI functions and staticMbglNetwork.Onlineproperty: force MapLibre offline (serve only cached resources) and back online at runtime. - API key + cache size at map creation — New
mbgl_map_create2C ABI function;MbglMap's constructor gains optionalapiKeyandmaxCacheSizeBytesparameters (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_panningC ABI functions and matchingMbglMapproperties.
🐞 Bug fixes
- Blank map on WPF and MAUI Windows —
WGLRenderableResource::bind()inplatform_frontend_windows.cppunconditionally calls `glBindFramebuffer(0...