Migrate from Google Maps to Maptoolkit
The Google Maps JavaScript API and MapLibre GL JS share no interface, so the map layer is a rewrite rather than a port. The service APIs are a different story: Directions, Distance Matrix, Geocoding and Elevation all have direct counterparts, and moving those is mechanical.
Plan the work in that order. The services can move first, on their own, while the map stays where it is.
What maps to what
| Google Maps Platform | Maptoolkit | What differs |
|---|---|---|
| Maps JavaScript API | Maps JS or MapLibre GL JS | Different library entirely. See below |
| Maps Static API | Static Maps API | Different parameter names, same idea |
| Directions API | Routing API | Car, bike, foot and transit profiles |
| Distance Matrix API | Matrix API | Travel times and distances between origins and destinations |
| Geocoding API | Geocoding API | Forward and reverse |
| Elevation API | Elevation API | Point lookups |
| Maps SDK for Android and iOS | MapLibre GL Native | Open source, same style URLs as the web |
Maptoolkit also has a Weather API, an Isochrone API and a Map Matching API. Google has no first-party isochrone product at all, so if you have been approximating reachability with repeated Distance Matrix calls, that is worth looking at during the move.
What does not map
- Places API. Autocomplete, place details, photos, opening hours, ratings and reviews have no equivalent. The Geocoding API resolves addresses and place names; it is not a business directory. For many migrations this is the deciding factor, so check it first.
- Street View. No equivalent, and no plan for one.
- Traffic layer and traffic-aware routing. Routes are computed on the road network without
live traffic, so
departure_timehas no congestion model behind it. - Roads API. Snap to Roads has a counterpart in the Map Matching API; speed limits do not.
- Maps Embed API. No equivalent iframe product. Embed a real map instead.
- Routes API route optimization. No equivalent. Stop ordering and vehicle routing are not offered.
- Cloud-based map styling. Style JSON is edited as a file rather than in a console, and custom cartography on a Maptoolkit account is an Enterprise arrangement.
Coordinate order, which will break something
Google uses {lat: 48.21, lng: 16.37} objects, or LatLng instances, latitude first.
Maptoolkit, MapLibre, GeoJSON and every example in these docs use [16.37, 48.21] arrays,
longitude first.
The two orders are both plausible-looking numbers, so a swapped pair does not throw. It puts your map in the wrong place, usually in the sea off Somalia if the coordinates were European. Wrap the conversion in one function early and use it everywhere:
const toLngLat = (g) => [g.lng, g.lat];The service APIs are their own case: point parameters on the routing endpoints are
lat,lng, latitude first, matching Google rather than the map libraries. Check the reference
for the endpoint you are calling rather than assuming either order.
Rewriting the map
Use Maps JS unless you have a reason not to. It is MapLibre GL JS with the
Maptoolkit services wired in, so the style switcher, terrain toggle and isochrone control are
one line each instead of code you write, and the API key is a constructor option. Plain
MapLibre GL JS is the same rewrite with maplibregl in
place of maptoolkit and no apiKey option.
- <script src="https://maps.googleapis.com/maps/api/js?key=GOOGLE_KEY"></script>
+ <script src="https://unpkg.com/@maptoolkit/maps@11.0.0-beta.3/dist/maptoolkit.js"></script>
+ <link rel="stylesheet" href="https://unpkg.com/@maptoolkit/maps@11.0.0-beta.3/dist/maptoolkit.css" />
- const map = new google.maps.Map(document.getElementById("map"), {
- center: { lat: 48.21, lng: 16.37 },
- zoom: 12,
- });
+ const map = new maptoolkit.Map({
+ container: "map",
+ apiKey: API_KEY,
+ style: `https://styles.maptoolkit.net/maptoolkit/maptoolkit.summer.json?api_key=${API_KEY}`,
+ center: [16.37, 48.21],
+ zoom: 12,
+ });
Note what happened to the centre. Google takes an object with named lat and lng, so the
order is unambiguous; here it is a bare array in [longitude, latitude] order, which is the
mistake that costs the most time on this migration. There is a
section on it above.
The rest of the translation, in the order you will hit it:
| Maps JS | |
|---|---|
new google.maps.Marker({position, map}) | new maptoolkit.Marker().setLngLat(c).addTo(map) |
new google.maps.InfoWindow({content}) | new maptoolkit.Popup().setHTML(content) |
map.setCenter(), map.setZoom() | map.setCenter(), map.setZoom(), or map.jumpTo({center, zoom}) |
map.panTo() | map.easeTo({center}) or map.flyTo({center}) |
map.fitBounds(LatLngBounds) | map.fitBounds([[w, s], [e, n]]) |
google.maps.event.addListener(map, "click", fn) | map.on("click", fn) |
e.latLng.lat(), e.latLng.lng() | e.lngLat.lat, e.lngLat.lng |
new google.maps.Polyline({path}) | a GeoJSON source plus a line layer |
new google.maps.Data() | map.addSource(id, {type: "geojson", data}) |
| MarkerClusterer | the cluster options on a GeoJSON source |
styles: [...] on the map options | the style JSON itself |
Two differences are structural rather than cosmetic.
Zoom is fractional. MapLibre zoom is a float, so zoom: 12.5 is valid and the map
animates continuously between levels. Google’s integer levels map onto it directly, but layout
tuned to integer steps may need revisiting.
Vector data goes into the style, not onto the map. Google attaches overlay objects to the map. MapLibre adds a source, then one or more layers that draw it. It is more code for a single polyline and much less for a thousand, because the styling is declarative and the rendering is on the GPU. Expect to restructure anything that draws more than a handful of features.
Markers at scale
If you are drawing more than a few hundred markers, do not port Marker to
maptoolkit.Marker. Each one is a DOM element in both libraries, and the performance ceiling
is the same. Put the points in a GeoJSON source with a symbol or circle layer and let the
GPU draw them. This is usually the single biggest rendering improvement available in the move,
and it is easy to miss because the naive translation works fine at demo scale.
The service APIs
Each is a small rewrite of request and response shapes rather than a URL swap:
- Routing API for Directions
- Matrix API for Distance Matrix
- Geocoding API for Geocoding
- Elevation API for Elevation
Two shape differences worth knowing before you start. Google wraps every response in a
status field that you check before reading results; Maptoolkit uses the HTTP status and
returns the payload directly. And Google returns an encoded polyline inside a nested
routes[].legs[].steps[] structure, where the Routing API returns the geometry at
paths[0].points.
What bites on day one
- Errors are
text/plain. A missing key returns403 Access denied!, an unrecognised key403 Api-key not found!, a key without access to that service403 Api-key not authorized!. Google returns a JSON body with astatusand anerror_message. Code that readsresponse.json()on a failure will throw. - There is no
status: "ZERO_RESULTS". An empty result is an empty result, expressed in the payload rather than in a status string. - Attribution is a license condition, not a logo you can restyle away. Maptoolkit and
OpenStreetMap stay visible. In MapLibre,
AttributionControldefaults tocompact: true, which collapses the credit behind a button as soon as the map is dragged. Setcompact: falseto keep it readable. - Billing is per request, not per map load or per session. There is no session-token concept, so the autocomplete billing model has no analogue. A like-for-like estimate needs your tile volume, which a map-load count does not give you.
- Place IDs do not survive. Anything you have stored as a Google place ID is not resolvable here. If your database keys on place IDs, plan to re-geocode.
Doing it in stages
- Move the service APIs first, one at a time, behind a flag. They are independent of the map and each is a contained change.
- Build the new map alongside the old one on a single page, so the two can be compared directly.
- Write the coordinate conversion once, at the boundary, and use it everywhere.
- Restructure marker and overlay code onto sources and layers before porting features.
- Re-geocode anything keyed on a Google place ID.
- Check attribution renders on every map, at every breakpoint, after a drag.
- Run both for a full billing period before turning Google off.
Pricing
Self-service plans are billed through RapidAPI. See RapidAPI for how to subscribe and authenticate. Plan allowances and prices are on the Maptoolkit pricing page. Enterprise customers call the native hosts with a Maptoolkit API key.
Tile requests and API requests are metered separately and at different rates.