Repository navigation
munim-maps 0.5.1
Google's photorealistic 3D map on iOS, a globe, 3D terrain and sky for MapLibre (through MapLibre GL JS), and MapModelLayer over @rnmapbox/maps and react-native-maps on Android.
Added
- MapLibre: globe, 3D terrain and sky through a second renderer for the
maplibreprovider, MapLibre GL JS 5 in a WebView, since MapLibre Native has neither.maplibre={{ renderer: 'auto' | 'native' | 'web' }}:auto(default) keeps MapLibre Native unless the map asks for something only GL JS has (theglobeprop,maplibre.projectionother than Mercator,maplibre.terrain,maplibre.sky, or astyleJsonwith its own projection, terrain or sky;resolveMapLibreRenderer()tells you which and why). New options:projection: 'globe' | 'vertical-perspective' | {…},terrain(truefor keyless AWS Terrain Tiles, or yourraster-demtiles, withexaggeration),sky(true,falseor a style-specsky, with the globe's atmosphere),fov,fadeDuration,allowEvaluate. The same API as the Native renderer (markers with dragging and continuousonMarkerDrag, clustering, callouts,MarkerView, shapes with dashes and gradients, tile overlays, camera, regions, fitting, flights, conversions on the terrain, the whole style spec at runtime, the MapLibre commands,onProviderEvent), plusmaplibreCommands(ref)getRenderer,setProjection,getProjection,isGlobe,setTerrain,getTerrain,queryTerrainElevation,setSky,getSky,easeTo,jumpToandevaluate, and therendererandprojectionTransitionevents. - munim-maps' 3D layer drawn inside GL JS (a custom layer rendered with three.js in GL JS's own WebGL context): GLB models, shapes, avatars, labels, stems, zones, paths and effects stay on the map on the flat map, on the globe and through the globe-to-Mercator transition, stand on the terrain under them and are hidden by terrain and the style's 3D buildings.
measureAlignment()measures them against GL JS's own projection. - GL JS 5.24.0 and three.js 0.186.1 load from jsDelivr through the engine's URL handler (same origin, cached on disk). To bundle them, add
maplibre-gl@5.24.0(andthree@0.186.1) to the app and turn onmaplibre: { bundledWeb: true }(Expo),munimMaps.maplibreBundled=true(Gradle) orMUNIM_MAPS_MAPLIBRE_BUNDLED=1(CocoaPods);scripts/maplibre/copy-maplibre-web.jscopies them with their licences. The npm package carries neither (the renderer's page is about 160 KB). - iOS: the
NitroMunimMaps/MapLibreWebsubspec (included by/MapLibre;MUNIM_MAPS_PROVIDERS=maplibre-webbuilds GL JS alone). Android:munimMaps.maplibreWeb(on with MapLibre). - Swift and Kotlin:
MunimMapEngine.variant(an engine can have more than one implementation; the container swaps when it changes),MunimMapEngines.variant(for:options:), andfetchCamera,fetchVisibleRegion,fetchPoint,fetchCoordinate,fetchAlignmentandfetchOverlayHithooks (defaulted to the synchronous getters) that JavaScript's promises now use, so WebView engines answer exactly. - Example:
munimmapsexample://maplibre/web(globe, city and Alps cameras) andmunimmapsexample://maplibre/web/checks. MapModelLayerover@rnmapbox/mapson Android: the layer finds the MapboxMapView(bymapTestID, or the nearest one) and draws with Mapbox's own camera (centre, zoom on 512-point tiles, pitch, bearing, padding, Mapbox's 36.87° field of view), every frame. No flag: the adapter compiles whenever@rnmapbox/maps(or munim-maps' Mapbox engine) is in the app, against the Mapbox SDK version@rnmapbox/mapsbuilds with (compileOnly, nothing added to the APK). Taps on models fireonModelPressthrough Mapbox's gestures plugin, next to@rnmapbox/maps' ownonPress. On a Galaxy A14: within 0.09 pt of Mapbox'spixelForCoordinateat seven cameras (one padded) and during an animation, within 2 pt on screenshots while dragging.MapModelLayerover react-native-maps on Android (Google Maps) without munim-maps' Google engine: the Google adapter compiles whenever react-native-maps is in the app. It finds the camera's centre with Google's projection, so react-native-maps'mapPaddingis followed. On a Galaxy A14: within 0.35 pt of Google's projection, within 2 pt on screenshots while dragging. NoonModelPresshere: Google's map has a single click listener, react-native-maps'.- Example:
munimmapsexample://layer/rnmapboxandlayer/rnmaps-google(Android): vehicles, avatars, shapes, effects, zones and paths over each library's map;/checkmeasures them against the host's own projection at seven cameras (one with map padding) and during a host-driven animation (MUNIM_MAPS_LAYER_OVERlog lines),/pan,/pan45and/pan45pad(/tex: the Mapbox map in a TextureView) show a probe (the host's magenta circle under munim-maps' green tile) for screenshots while dragging:example/scripts/layer-pan.shdrags and saves frames,layer-probe.pymeasures them,run-check.shruns a deep link and keeps its log.@rnmapbox/mapsis in the example for Android only (example/react-native.config.js), its token from the same keys file as the others. - Google's photorealistic 3D map on iOS:
google={{ mode: '3d' }}now works on both platforms, with the same API as Android: munim models drawn as Google's own glTF models (modelRenderingauto/native;overlayfalls back to native with an error), polylines, polygons and markers drawn natively,googleMap(ref).flyTo,flyAround,stopCameraAnimation,getCamera3d,setCamera3d, and themap3dReady,map3dSteady,cameraAnimationEndandmodeChangeevents. Turn it on with the Expo plugin'sgoogleMaps3d: true(now for iOS too) or"munimMaps.googleMaps3d": "true"inios/Podfile.properties.json(MUNIM_MAPS_GOOGLE_MAPS_3D=1); the key needs the Maps 3D SDK for iOS. Google ships that SDK only as a SwiftUI Swift package (GoogleMaps3D1.0.0): the newNitroMunimMaps/Google3Dsubspec hosts its map in the Google engine's view, the podspec adds the package to the pod with React Native'sspm_dependency, and the config plugin adds a Run Script phase to the app target that embeds its framework and resource bundle (scripts/google3d/embed-google-maps-3d.sh; without Expo, add a Run Script phase at the end of the app target that runsbash "${SRCROOT}/../node_modules/munim-maps/scripts/google3d/embed-google-maps-3d.sh"). On iOS,screenSizeis approximated from the camera's distance andtintrecolours the model'spaint*materials. See docs/providers.md. googleMap(ref).map3dDiagnostics()(iOS): how munim-maps drives the 3D map (camera log, flights, models).
Fixed
- Android, Google photorealistic 3D map: munim models stood on their tails (the bus upright on its back end, the balloon on its side), because Google's Maps 3D SDK reads glTF files Z-up and turns a model's +Z to its heading. They are now stood up (
tilt-90) and turned 180°, as on iOS, so they stand upright and heading 0 faces north (checked from the side and from above on a Galaxy A14). Example:munimmapsexample://google/3d/cam/<lat>,<lng>,<altitude>,<heading>,<tilt>,<range>starts the 3D map at that camera. - Android, Google photorealistic 3D map:
screenSizewas ignored, so every model was drawn at its real size (a 26-point bus was a few pixels from 2 km away). It now scales from the camera's distance, as on iOS: the model's height is read from its glTF (remote models are downloaded and cached first), and since the Maps 3D SDK (0.2.0) keeps drawing a model at the scale it was added with, a model whose scale changes is added again. On a Galaxy A14 ascreenSize: 60balloon is 59 points tall from 2.4 km. - Cesium:
getCamera()andgetVisibleRegion()could answer with the previous camera, because the page posts its region 150 ms after the camera stops: on a Galaxy A14 the region after afitToCoordinateswas still the one before it. Both now ask the page (thefetchCamera/fetchVisibleRegionhooks), on iOS and Android. - Android: the 3D layer stayed one frame behind once the map stopped moving whenever Filament skipped the last frame (the GPU busy during a fast drag); a skipped frame is now drawn on the next one.
- Android, Mapbox engine: models were off with camera padding on a pitched map (38 pt with 160 points of top padding at 50°, measured over
@rnmapbox/mapswith the same camera code): Mapbox moves its centre of perspective to the padded centre, whichMapCameraStatenow models (principalX/principalY, drawn with Filament's camera shift). - Android, Google: with map padding the camera's field of view was measured as if the target were straight ahead (17.6° instead of 30°, 11 pt off). Padded cameras now use the field of view measured without padding, and Google's starting field of view is the 30° measured on Android.
Changed
- Android:
MapModelLayerkeeps its 3D view exactly over the map's view (position and size, before every draw), so it lines up even when the layer and the map are not the same size, and finds the map again after it is remounted. - Android:
MapViewAdaptersfinds its built-in adapters by class name (like the engines);MapCameraSource.setTapListener(default: none) lets an adapter pass map taps to the layer. - iOS: two-format model sources (
munim-maps-vehicles) resolve to GLB on the Google engine in 3D mode (modelFormatFor(provider, modelRendering, googleMode)). - Swift: Mapbox's glTF reader moved to the shared
GLBInfo(Core/GLBInfo.swift), which also writes tinted GLB copies.
Found running Google's photorealistic 3D map on an iPad (Maps 3D SDK for iOS 1.0.0), and worked around:
- The SDK's
flyCameraTo/flyCameraAroundmodifiers fly to the camera of the previous SwiftUI update, so munim-maps flies the camera itself through the map's camera binding. - Changes to a model the map already shows are applied one SwiftUI update late, so a still model that changes is drawn anew (markers, polylines and polygons are too); moving models are updated in place.
- glTF models are read Z-up and turned by their +Z axis: munim-maps stands them up (
tilt-90) and turns them 180°. - A package product of a static-library pod is linked into the app but not embedded, and linking the product into the app target as well links its wrapper twice (Debug builds then fail with duplicate
Bundle.modulesymbols), so the config plugin adds a build phase that copies the framework and its resource bundle into the app and signs it.