Skip to content
From Google Maps

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 PlatformMaptoolkitWhat differs
Maps JavaScript APIMaps JS or MapLibre GL JSDifferent library entirely. See below
Maps Static APIStatic Maps APIDifferent parameter names, same idea
Directions APIRouting APICar, bike, foot and transit profiles
Distance Matrix APIMatrix APITravel times and distances between origins and destinations
Geocoding APIGeocoding APIForward and reverse
Elevation APIElevation APIPoint lookups
Maps SDK for Android and iOSMapLibre GL NativeOpen 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_time has 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:

GoogleMaps 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})
MarkerClustererthe cluster options on a GeoJSON source
styles: [...] on the map optionsthe 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:

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 returns 403 Access denied!, an unrecognised key 403 Api-key not found!, a key without access to that service 403 Api-key not authorized!. Google returns a JSON body with a status and an error_message. Code that reads response.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, AttributionControl defaults to compact: true, which collapses the credit behind a button as soon as the map is dragged. Set compact: false to 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

  1. Move the service APIs first, one at a time, behind a flag. They are independent of the map and each is a contained change.
  2. Build the new map alongside the old one on a single page, so the two can be compared directly.
  3. Write the coordinate conversion once, at the boundary, and use it everywhere.
  4. Restructure marker and overlay code onto sources and layers before porting features.
  5. Re-geocode anything keyed on a Google place ID.
  6. Check attribution renders on every map, at every breakpoint, after a drag.
  7. 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.