MapConductor is an open-source SDK that puts map SDKs behind one shared API. It ships for Android (Jetpack Compose), iOS (SwiftUI) and React (TypeScript), covers 15 providers today, and is published under Apache-2.0.
This is our first post about the project itself, so here is what it does, and when it is worth having.
Every map SDK is good at something
Google Maps, Mapbox, HERE, ArcGIS, MapLibre, MapTiler, TomTom, Longdo, Mappls — each of these has been refined over years, and each is strong somewhere. Road and address detail, navigation, connecting to existing GIS assets, design freedom, regional coverage. There is no general answer to "which one is best"; the answer moves with the app you are building and the places you are shipping to.
And yet, from the implementation side, that choice is settled within the first few lines. Code that draws a map binds tightly to one SDK's types and concepts, so by the time you want to compare, the means of comparing is what is missing.
This is not a complaint about anyone's design. It is the ordinary consequence of each vendor designing carefully around its own strengths: the concepts and the names do not line up. Lining them up is not the job of the vendor supplying the SDK — it falls to whoever writes on top of it. MapConductor takes on that layer.
What it does
Your app depends only on the unified API. Drivers absorb the per-provider differences, so changing provider means changing exactly three things: the dependency, the map view type and the state object type. How you write markers, shapes, camera work and events does not change.
// The overlays are declared outside the branch, so swapping does not touch them
val overlays: @Composable MapViewScope.() -> Unit = {
Marker(markerState)
Polyline(routeState)
}
if (useMapLibre) {
MapLibreMapView(state = maplibre, content = overlays)
} else {
GoogleMapView(state = googlemaps, content = overlays)
}You can also hold two of them at once and switch at runtime. Hand the camera you were just looking at to the side you are switching to, and the switch is not visible as a jump.
It is not only the code that agrees
The usual worry about "one shared API" is that the way you write it lines up while the results do not. Zoom 14 covering different ground in different environments, the distance between two points disagreeing between Android and iOS — that really happens.
MapConductor keeps that inside the SDK.
| Implemented in the SDK | What that buys you |
|---|---|
| Geodesy | Distance, heading, area and interpolation written from the same formulas in Kotlin, Swift and TypeScript. Nothing depends on an SDK utility, so displayed numbers never disagree between platforms |
| Camera | Zoom and camera altitude convert into each other, calibrated against measured visible areas. Zoom 14 covers the same ground on every provider |
| Marker rendering | Native markers when there are few; past 2,000 the engine switches to raster tiles on its own. Camera work stays smooth at tens of thousands, and not a line of your code changes |
| Shapes | Holed polygons, great circles and the like are drawn as raster tiles where the native SDK cannot do them, so the result looks the same |
| Attribution | Rules that switch the credit by zoom and area are part of the map design object |
A wrapper that only dispatches to each SDK ends up limited by whichever SDK does least. That is why the missing pieces are drawn by the SDK itself.
What it is good for
One API to learn
Every map SDK names things its own way, so each change of provider and each move to another platform means learning it again. With MapConductor there is one API, carrying the same names and the same meanings on Android, iOS and React. What someone learns on Android holds on iOS, and onboarding does not grow with the number of providers.
Code with no map SDK name in it
GeoPoint and MarkerState belong to no map SDK. Building a route, testing whether a point falls inside an area, deciding what to show — that logic can be written with no SDK type in it at all. Shared map components can be factored out as a library instead of one build per provider.
The spec discussion happens once
A different map SDK on Android, iOS and the web means markers behave differently and camera numbers mean different things, so the same feature gets specified three times. One shared model turns those three conversations into one.
What we are really after: being able to choose
Everything above is written from the side that embeds a map. That is not the whole of it.
Today, picking a map SDK is the first commitment an implementation makes. That turns selection into "decide, then build" rather than "try, then decide", and the comparison never leaves the spreadsheet. That is not a good state for the side being chosen, either — a good SDK can drop out of consideration not on quality, but because the app was already written against a different one.
A shared API changes the order. You can run them, compare them on the same screen of the same app, and decide from there. What decides it is price, coverage, freshness of the data, rendering quality — the things vendors actually compete on.
So MapConductor is built not to hide what each vendor is good at. What is unified is the part every provider already has. Calling getMapViewHolder() on the state object hands back the native map view and map instance, so anything only one SDK offers is written against that vendor's own API. A wrapper that hid everything would erase what makes each provider different; this one does not. "The common parts through MapConductor, the parts that matter through the vendor's API" is a perfectly normal way to use it.
After HTML5 settled what browsers had to implement, browsers stopped competing on compatibility itself and started competing on speed and capability. Map SDKs are no different: with a shared interface in place, the question becomes how fast, how accurately and how widely you can draw. A common layer is not a brake on individual SDKs — it points their progress the same way.
One more thing, and this one is direction rather than product. Map data, geocoding and tiles have come bundled with the SDK of the vendor that supplies them. Separate display from data, and providers with no map SDK of their own — geocoding only, or one dataset — have more places to be used. There is no feature aimed at this today. It is where we are heading, not a list of what ships now.
Complying with the terms of each dataset and service is the responsibility of the developer who integrates it. A combination being technically possible says nothing about it being permitted.
When it fits, and when it does not
A good fit:
- One map experience across several platforms
- Keeping the option to change provider later
- Markers, shapes and camera work as the core of the feature
- Thousands to tens of thousands of markers
- Distances and areas that have to agree everywhere
A poor fit:
- A provider-specific showpiece — custom shaders, a vendor-exclusive layer
- Turn-by-turn navigation and other features that are the SDK's own product
- One platform, one provider, and no intention of ever changing
It is not all or nothing, though. The native map instance is always reachable, so the unusual parts can be written against the provider directly.
Getting started
MapLibre runs with no API key, so the first map takes about five minutes.
- Get started — your first MapLibre map
- Why MapConductor — the thinking this post is drawn from
- Architecture — how the unified API, Core and drivers divide up
- Providers and setup — per-platform installation
We publish development progress and releases on Discord. Design feedback and bug reports are both welcome. The code is on GitHub.
