Skip to main content

Migrating from Google Maps to Wemap

This guide is for teams that already ship a Google Maps JavaScript API integration on the web and want to move it to Wemap. It maps the Google APIs you use today onto their Wemap equivalents, shows the two integration paths, and flags the places where the two platforms differ in kind rather than in naming.

Migrating a native app from the Google Maps SDK for Android or iOS is a different exercise — start from the Android, iOS or mobile webview documentation instead.

The one thing that changes everything​

Google Maps gives you a blank canvas: you create a Map, then push your data onto it at runtime — markers, info windows, polylines — usually from your own backend on every page load.

Wemap gives you a livemap: a map that already knows its content. Places (pinpoints), their grouping (lists), their styling, the indoor floors, the search behaviour and the UI are configured once — in Wemap Pro or through the Content API — and the map renders them for you.

Practically, that means a chunk of your current code disappears rather than gets translated. Fetching POIs, building marker icons, writing info-window HTML, clustering, "search this area" — those are map features on Wemap, not application code. The migration work is mostly deciding what to delete.

The second difference worth knowing up front: indoor is a first-class citizen. Pinpoints, routes and markers can carry a floor level, and the map shows or hides them according to the displayed floor. If your map is purely outdoor you can ignore levels entirely; if you have buildings, this is the feature you were missing.

Credentials

Google's single API key becomes two values: an emmid (the livemap id) and a token (the owner's access token). Both come from Wemap Pro. For the Content API you additionally create an OAuth2 application to obtain a bearer token — see the REST API getting started.

Choose your target​

Google's JS API is one product. Wemap's web offering is two, and the right choice depends on how much of the map's UI you wrote yourself.

If your Google integration is…Migrate toWhy
A map with markers, info windows and a sidebar you built to list/search placesEmbed SDKYou get the search, POI cards, lists, routing UI and indoor switcher out of the box, and delete your own.
A map embedded inside a larger app UI where you own every pixel (your own panels, your own search field, your own marker rendering)JavaScript SDK@wemap/map renders the livemap and nothing else; you drive it from your own components.
Geocoding / Directions web services called from your backendServices APIServer-side REST, no client SDK needed.

A useful heuristic: start with the Embed SDK. It is the shortest migration by far, and it is the only one that replaces UI code rather than just map code. Move to the JS SDK only when you hit a customisation the Embed SDK doesn't expose.

Path A — Embed SDK​

Creating the map​

Google Maps
const map = new google.maps.Map(document.getElementById('map'), {
center: { lat: 48.8566, lng: 2.3522 },
zoom: 15,
});
Wemap Embed SDK
<div id="map-container"></div>
<script src="https://livemap.getwemap.com/js/sdk.min.js"></script>
<script>
const container = document.getElementById('map-container');
const livemap = wemap.v1.createLivemap(container, { emmid: 1234, token: 'MY_TOKEN' });

livemap.waitForReady().then(() => {
livemap.centerTo({ latitude: 48.8566, longitude: 2.3522 }, 15, true);
});
</script>

Two things to internalise:

  • No initial center/zoom in code. The livemap's default viewport, style, zoom range and bounds come from its configuration. Override at runtime if you must, but prefer configuring it once in Wemap Pro.
  • waitForReady() before anything else. It is the equivalent of Google's idle event, and calls made before it resolves are not guaranteed to land.

API mapping​

Google MapsEmbed SDK
map.setCenter(latLng)livemap.setCenter({ latitude, longitude })
map.setZoom(z)livemap.setZoom(z)
map.panTo() / combined center+zoomlivemap.centerTo(center, zoom, animate) or livemap.easeTo(options)
map.fitBounds(bounds)livemap.fitBounds([W, S, E, N], { padding, animate })
map.getCenter() / getZoom()livemap.getCenter() / getZoom() — both return Promises
map.setHeading()livemap.setBearing(bearing, { duration })
new google.maps.Marker({...})livemap.setPinpoints(pinpoints, { type: 'add' }) — full POI objects with the built-in card UI
marker.setMap(null) / rebuilding your marker setlivemap.setPinpoints(nextPinpoints, { type: 'replace' })
new google.maps.Polyline({ path })livemap.drawPolyline(coords, { color, width, opacity }) / removePolyline(id)
DirectionsService + DirectionsRendererlivemap.drawPolyline(coords, { useNetwork: true }), or navigateToPinpoint(id) / directionToPinpoint(id) for the full guidance UI
MarkerClusterer (separate library)Built in — configured on the livemap
map.addListener('click', cb)livemap.addEventListener('mapClick', cb)
Your own "search this area" UIBuilt-in search, plus livemap.setFilters(filters, { type })

setPinpoints is the one to look at hardest: it is what most Google integrations actually want. You hand it your places, and search, cards, clustering, filtering and deep-links all work on them without further code.

Events​

addEventListener / removeEventListener replace addListener and google.maps.event.clearListeners. The names you will reach for first:

livemap.addEventListener('pinpointOpen', (e) => console.log(e.pinpoint.name));
livemap.addEventListener('mapClick', (e) => { /* … */ });
livemap.addEventListener('mapMoved', (e) => { /* Google: 'idle' */ });
livemap.addEventListener('contentUpdated', (e) => { /* visible content changed */ });

Also available: ready, mapLongClick, mapUpdate, pinpointClick, pinpointClose, listOpen/listClose, eventOpen/eventClose, indoorLevelChanged, floorChanged, guidingStarted/guidingUpdated/guidingStopped, userLocationUpdated, fullscreenEnter/fullscreenExit, actionButtonClick, userLogin/userLogout, permissionsDenied.

See the event listeners example.

Path B — JavaScript SDK​

Use this when you need the map surface without the Wemap UI. @wemap/map is a thin wrapper over maplibre-gl, so if you have ever used Mapbox GL or MapLibre the API will feel familiar — and the raw instance stays reachable as map.maplibre.

Creating the map​

Wemap JS SDK
import { core } from '@wemap/core';
import { Coordinates } from '@wemap/geo';
import { WemapMap } from '@wemap/map';
import 'maplibre-gl/dist/maplibre-gl.css';
import '@wemap/map/style.css';

await core.init({ emmid: '31668', token: 'YOUR_TOKEN' });

const map = new WemapMap({ container: 'map' });
await map.whenReady();

map.setCenter(new Coordinates(48.8566, 2.3522));

core.init() has no Google equivalent — it fetches the livemap configuration that every other package reads. It must run before you construct a WemapMap.

There is no <script>-loader callback and no libraries= parameter: install the packages you need, or load the UMD bundles in dependency order. See Installation.

API mapping​

Google MapsJS SDK
new google.maps.Map(el, opts)new WemapMap({ container: 'map', ...opts })
google.maps.event.addListenerOnce(map, 'idle', …)await map.whenReady()
new google.maps.LatLng(lat, lng)new Coordinates(lat, lng, alt?, level?) from @wemap/geo
LatLngBoundsBoundingBox(northEast, southWest) from @wemap/geo
map.setCenter() / setZoom() / getBounds()map.setCenter() / setZoom() / getBounds() — synchronous, like Google
map.panTo()map.flyTo({ center, zoom, duration })
map.fitBounds(bounds)map.fitBounds(boundingBox)
new google.maps.Marker()new DomMarkerLayer(map) then markers.add({ id, position, color | icon | element })
marker.setMap(null)markers.remove(id)
marker.addListener('click', cb)markers.on('click', ({ id, position }) => …)
MarkerClusterernew DomMarkerLayer(map, { cluster: { enable: true } })
new google.maps.Polyline()shapes.drawLineString(geometry, style) on a ShapeLayer
new google.maps.Polygon()shapes.drawPolygon(geometry, style)
map.data.addGeoJson(json)ShapeLayer with simplestyle-spec properties
DirectionsService.route()new Router().directions(origin, destination, 'WALK', options)
DirectionsRenderernew ItineraryLayer(map).set(itinerary, { fitBounds: true })
map.setOptions({ myLocationEnabled })new UserLocationLayer(map) fed from @wemap/positioning
PlacesService.nearbySearch / your POI backendmap.search(query), map.filterPois(), map.setPoiHighlighted()
POI click on Google's own POIsmap.onPoiClick(({ pinpoint, coordinates }) => …)
map.addListener('click', cb)map.on('click', cb) — also layer-scoped: map.on('click', 'layer-id', cb)
(no equivalent)map.setLevel() / LevelControl — indoor floors

Routing​

Google Maps
new google.maps.DirectionsService().route(
{ origin, destination, travelMode: google.maps.TravelMode.WALKING },
(result, status) => { if (status === 'OK') renderer.setDirections(result); }
);
Wemap
import { Router } from '@wemap/routing';
import { ItineraryLayer } from '@wemap/map';

const itineraries = await router.directions(origin, destination, 'WALK', {
isWheelchair: true,
});
new ItineraryLayer(map).set(itineraries[0], { fitBounds: true });

Modes are WALK, BIKE, CAR, TRANSIT. Google's waypoints and optimizeWaypoints exist under the same names. isWheelchair (avoid stairs and escalators) has no Google equivalent, and is usually a reason teams migrate in the first place. Full detail in the Routing guide.

Distance Matrix maps to POST /v4/directions-multiple on the Routing API: itineraries to many destinations in one call, in log(n) rather than one request per destination. Origin and destinations must sit within the same custom map.

Gotchas​

These are the ones that cost real debugging time.

Coordinate order. Google is { lat, lng }. Wemap's SDK inputs are latitude-first — new Coordinates(lat, lng), { lat, lng }, or [lat, lng]. But GeoJSON geometries passed to ShapeLayer, and anything you do directly on map.maplibre (including passthrough map.on('click') events), use [lng, lat]. Mixing them up puts your data in the ocean, silently.

The container needs an explicit height. Same as Google: a bare <div> collapses to 0px and the map renders blank.

Zoom is fractional. 16.5 is valid. Google's integer-ish zoom levels are not a constraint here.

Getters are async in the Embed SDK, sync in the JS SDK. livemap.getCenter() returns a Promise (the map lives in an iframe); map.getCenter() does not.

Layers are not destroyed with the map. DomMarkerLayer, ShapeLayer, ItineraryLayer and UserLocationLayer need destroy(), or map.remove({ layers: true }) to cascade. Forgetting this leaks on SPA route changes.

HTTPS is mandatory for positioning and VPS (or localhost in development) — browsers block geolocation and camera access on insecure origins.

Bringing your data over​

Your Google integration almost certainly fetches places from your backend and turns them into markers on every page load. On Wemap you have two ways to handle that, and they can be combined on the same map.

Option 1 — Managed content​

Your places live in Wemap as pinpoints, grouped into lists, and the livemap renders them without any code from you. Get them in by:

  • Wemap Pro — manually, or by bulk import (xlsx and other formats) for a one-off migration.
  • Data sync — when your source of truth stays on your side, we set up a custom synchronisation between your data and the Wemap backend, so the map always reflects it. Talk to us about your format and refresh rate rather than writing the integration yourself. The same pinpoints are also reachable through the Content API if you prefer to push them.

This is the option that deletes the most code. Search, categories and tags, media, POI cards, clustering, filtering, deep-linking and indoor binding all come with it. Prefer it whenever your places are reasonably stable.

Option 2 — Dynamic injection​

Your data stays in your backend and you push it to the map at runtime:

  • Embed SDK — livemap.setPinpoints(pinpoints, { type: 'add' \| 'replace' }). The injected places still get the built-in card and search UI.
  • JS SDK — DomMarkerLayer for plain pins you render and handle yourself.

Use it for anything that cannot be pre-loaded: per-user or per-session results, real-time positions (vehicles, availability), stock or pricing that changes by the minute, or content you are still prototyping.

Which one​

Your data is…Use
Stable, shared by all users, editable by non-developersManaged content
Owned by your CMS but slow-movingContent API sync
Per-user, real-time, or computed on the flyDynamic injection

Mapping your Google sources​

Your Google sourceMove it to
A POI table in your databasePinpoints via POST /v3.0/pinpoints — see the Pinpoints API
A My Maps / KML / GeoJSON exportConvert to pinpoints, then group with lists — or import the file in Wemap Pro
Place IDs from the Places APIRe-geocode addresses through /v3.0/geocoding/geocode; Google Place IDs are not portable
Marker icons and categoriesCategories and tags on the pinpoint, styled once in Wemap Pro

Pinpoint fields map closely to what you already store: name, latitude, longitude, address, description (HTML), altitude, category, tags, media_file. Keep your own identifiers on the pinpoint (tags work well) so you can reconcile with your source system later.

Content API access is granted on request — contact us.

What does not map one-to-one​

Being straight about this saves you a discovery call:

  • Street View — the equivalent is Wemap Vision, our spatial computing offer: immersive on-site views with AR and VPS positioning on top. It answers "what does it look like there" differently from Street View rather than reproducing it — see Spatial computing.
  • Places Autocomplete / Place Details — Wemap search covers your livemap content, not a global business directory. Global geocoding is available through the Geocoding service; a worldwide POI database is not.
  • Traffic, transit and bicycling layers — not exposed as toggleable layers. TRANSIT routing is supported where transit data is available for the deployment.

Conversely, these have no Google equivalent and are usually why you are here: indoor multi-level maps with automatic building selection, VPS positioning, wheelchair routing, and content management (pinpoints, lists, events, media) as a product rather than as your backend.

Migration checklist​

  1. Get your emmid and token from Wemap Pro.
  2. Pick the path — Embed SDK unless you own the whole UI.
  3. Stand up an empty map and confirm it renders (container height, waitForReady() / whenReady()).
  4. Load your places as pinpoints (Content API) rather than pushing markers at runtime.
  5. Port your event listeners — this is usually where your remaining app logic lives.
  6. Delete: clustering library, info-window templates, POI fetching, search UI, "search this area", custom marker icon generation.
  7. Re-check coordinate order everywhere you touch GeoJSON or map.maplibre.
  8. Add levels if you have indoor content.

Next steps​