From Mapbox or Google Maps
@dock/js is built on MapLibre, the open fork of Mapbox GL JS 1. Code written for Mapbox GL JS mostly runs as is: the map, sources, layers, markers, popups and events are the same. From Google Maps, the concepts are the same and the names change. This page maps each one. The dock side of every block below runs in the docs tests.
One rule first: points are [lon, lat] everywhere in dock, as in Mapbox. Google writes { lat, lng }.
Concepts
| Mapbox GL JS | Google Maps JS | dock |
|---|---|---|
mapboxgl.accessToken = 'pk...' | <script src="...?key=..."> | new Client({ key: 'dk_...' }) |
new mapboxgl.Map({...}) | new google.maps.Map(el, {...}) | createMap(maplibregl, dock, {...}) |
style: 'mapbox://styles/...' | mapId, styles | styleId: 'st_...' from the style editor |
center: [lng, lat] | center: { lat, lng } | center: [lon, lat] |
new mapboxgl.Marker() | new google.maps.Marker({...}) | new maplibregl.Marker() |
map.on('click', e => e.lngLat) | map.addListener('click', e => e.latLng) | map.on('click', e => e.lngLat) |
| Geocoding API, forward | Geocoder.geocode({ address }) | dock.geocode(q) |
| Geocoding API, reverse | Geocoder.geocode({ location }) | dock.reverse([lon, lat]) |
mapbox-gl-geocoder, <mapbox-search-box> | new google.maps.places.Autocomplete(input) | attachSuggest(input, dock, { onPick }) |
| Directions API | DirectionsService.route(...) | dock.route(from, to, { mode }) |
| Directions waypoints between the first and the last | waypoints: [{ location }] | dock.route(from, to, { via: [...] }), up to 10 |
Directions exclude=point(lon lat) | avoidTolls, avoidHighways, avoidFerries | dock.closures(): every route avoids them |
place_name, full_address | formatted_address | label(place, 'en') |
| no equivalent | no equivalent | dock.door('7QSVX'): a door code, to the door |
A map
import mapboxgl from 'mapbox-gl';
mapboxgl.accessToken = 'pk...';
const map = new mapboxgl.Map({ container: 'map', style: 'mapbox://styles/mapbox/streets-v12', center: [106.9177, 47.9186], zoom: 15 });const map = new google.maps.Map(document.getElementById('map'), { center: { lat: 47.9186, lng: 106.9177 }, zoom: 15 });import * as maplibregl from 'maplibre-gl';
import { Client, createMap } from '@dock/js';
const dock = new Client({ key: 'dk_...' });
const map = createMap(maplibregl, dock, { container: 'map', center: [106.9177, 47.9186], zoom: 15 });createMap returns a maplibre-gl Map. Every Mapbox GL JS call on it (addSource, addLayer, flyTo, fitBounds, queryRenderedFeatures) works the same. Under a bundler, maplibre-gl 6 needs its worker URL set: see Bundlers.
Mapbox styles (mapbox://...) and Google map ids do not load on dock tiles. Pass the colors as options, or save a style in the style editor and pass styleId. The base map has no text labels yet.
Markers and clicks
new mapboxgl.Marker().setLngLat([106.9303, 47.9202]).addTo(map);
map.on('click', (e) => console.log(e.lngLat.lng, e.lngLat.lat));new google.maps.Marker({ position: { lat: 47.9202, lng: 106.9303 }, map });
map.addListener('click', (e) => console.log(e.latLng.lng(), e.latLng.lat()));new maplibregl.Marker().setLngLat([106.9303, 47.9202]).addTo(map);
map.on('click', (e) => console.log(e.lngLat.lng, e.lngLat.lat));Search
const url = `https://api.mapbox.com/search/geocode/v6/forward?q=${encodeURIComponent('Сансар 24')}&access_token=pk...`;
const { features } = await (await fetch(url)).json();
console.log(features[0].properties.full_address, features[0].geometry.coordinates);const { results } = await new google.maps.Geocoder().geocode({ address: 'Сансар 24' });
console.log(results[0].formatted_address, results[0].geometry.location.toJSON());const { results } = await dock.geocode('Сансар 24', { limit: 5 });
console.log(label(results[0], 'en'), results[0].point);Type the address the way people say it: a building, a door, a landmark, Latin or Cyrillic. Show it with label. A place with no matched official address ends in "(unofficial)", as the API requires.
Mapbox's types filter is types on GET /v1/geocode too (API 0.70.0): door, business, building, district, khoroo, area, street, landmark, comma separated; types on geocode in all three SDKs.
Reverse
const url = 'https://api.mapbox.com/search/geocode/v6/reverse?longitude=106.9301&latitude=47.9201&access_token=pk...';
const { features } = await (await fetch(url)).json();const { results } = await new google.maps.Geocoder().geocode({ location: { lat: 47.9201, lng: 106.9301 } });const here = await dock.reverse([106.9301, 47.9201], { radius: 50 });
console.log(here.district_en, here.khoroo, here.results.length);Mapbox's types filter is types on GET /v1/reverse too (API 0.69.0): door, business, building, landmark, comma separated; types on reverse in all three SDKs.
Directions
const url = 'https://api.mapbox.com/directions/v5/mapbox/driving/106.9177,47.9186;106.9303,47.9202?geometries=geojson&access_token=pk...';
const { routes } = await (await fetch(url)).json();
console.log(routes[0].distance, routes[0].duration, routes[0].geometry);const { routes } = await new google.maps.DirectionsService().route({
origin: { lat: 47.9186, lng: 106.9177 }, destination: { lat: 47.9202, lng: 106.9303 }, travelMode: 'DRIVING',
});
console.log(routes[0].legs[0].distance.value, routes[0].legs[0].duration.value);const trip = await dock.route([106.9177, 47.9186], '7QSVX', { mode: 'driving' });
console.log(trip.distance_m, trip.duration_s, trip.geometry, trip.to.gap_m);Metres and seconds, as in both. to can be a door code: the route ends at that door, not the middle of the building. geometry is a GeoJSON line; draw it with addSource and addLayer as on Mapbox. The walk left from the road to the door is to.gap_m and is not part of the line. Modes: driving, walking, cycling.
Closed roads
const url = 'https://api.mapbox.com/directions/v5/mapbox/driving/106.9177,47.9186;106.9303,47.9202?exclude=point(106.9240%2047.9190)&geometries=geojson&access_token=pk...';
const { routes } = await (await fetch(url)).json();const { routes } = await new google.maps.DirectionsService().route({
origin: { lat: 47.9186, lng: 106.9177 }, destination: { lat: 47.9202, lng: 106.9303 }, travelMode: 'DRIVING', avoidFerries: true,
});const trip = await dock.route([106.9177, 47.9186], [106.9303, 47.9202], { mode: 'driving' });
console.log(trip.closures.as_of, trip.closures.avoided);
const avoid = excludePolygons(await dock.closures());
console.log(closureAt(avoid, [106.9303, 47.9202]) ?? 'no closure at the end');Mapbox avoids the points you send with each request, at most 50, driving only. Google avoids no area, only tolls, highways and ferries. In dock nobody sends the area: a partner reports a closed road once (POST /v1/closures: flood, washout, bridge down, snow, mud, works), and every route avoids it from its first day to its last. trip.closures.avoided lists the ids; show them with the route. An end inside a closure is DockError 422 end_in_closure. On your own Valhalla, offline: excludePolygons gives the rings for exclude_polygons, closureAt names the closure at an end. See Road closures.
Errors
Mapbox and Google give an HTTP status or a status string. dock throws DockError with status, code (not_found, rate_limited, quota_exceeded, ...) and retryAfter in seconds. See Errors.
iOS and Android
The iOS SDK (Swift) and the Android SDK (Kotlin) are clients of the same API, with the same method names as the JS SDK. They do not draw the map themselves: draw it with MapLibre Native, the open fork of Mapbox GL Native. Points are LonLat(lon, lat), longitude first, in both. Apple and Google write latitude first; Mapbox on Android writes Point.fromLngLat(lng, lat), as dock. Every call to the API is async throws in Swift and a suspend function in Kotlin: no callbacks, no delegates. label, excludePolygons and the tile and style helpers need no network. Each dock name below is checked against the SDK sources by the docs tests.
| Mapbox (iOS) | Google Maps (iOS) | dock (Swift) |
|---|---|---|
MapboxOptions.accessToken = "pk..." | GMSServices.provideAPIKey("...") | try Client(key: "dk_...") |
CLLocationCoordinate2D(latitude: lat, longitude: lng) | CLLocationCoordinate2D(latitude: lat, longitude: lng) | LonLat(lon, lat) |
MapView(frame:mapInitOptions:) | GMSMapView(options:) | MapLibre Native MLNMapView, its styleURL a file holding dock.styleJSON() |
Search SDK PlaceAutocomplete.suggestions(for:) | GMSPlacesClient.findAutocompletePredictions(fromQuery:...) | dock.geocode(q, proximity: here) |
Search SDK SearchEngine.reverse(options:) | GMSGeocoder().reverseGeocodeCoordinate(_:) | dock.reverse(LonLat(lon, lat)) |
Directions.calculate(_:) | Routes API over HTTP, no SDK call | dock.route(from: .point(here), to: "7QSVX", mode: .driving) |
RouteOptions.roadClassesToAvoid | Routes API routeModifiers | dock.closures(), then excludePolygons(closures) |
SearchResult.address?.formattedAddress(style:) | GMSAddress.lines, GMSPlace.formattedAddress | label(place, lang: .en) |
SearchError, DirectionsError | NSError | DockError: status, code, retryAfter |
| no equivalent | no equivalent | dock.door("7QSVX"): a door code, to the door |
| Mapbox (Android) | Google Maps (Android) | dock (Kotlin) |
|---|---|---|
MapboxOptions.accessToken = "pk..." | com.google.android.geo.API_KEY in the manifest | Client("dk_...") |
Point.fromLngLat(lng, lat) | LatLng(lat, lng) | LonLat(lon, lat) |
mapView.mapboxMap.loadStyle(Style.STANDARD) | SupportMapFragment.getMapAsync { ... } | MapLibre Native map.setStyle(Style.Builder().fromJson(dock.styleJson())) |
placeAutocomplete.suggestions(query) | placesClient.findAutocompletePredictions(request) | dock.geocode(q, proximity = here) |
searchEngine.search(ReverseGeoOptions(center), callback) | Geocoder(context).getFromLocation(lat, lng, 1) | dock.reverse(LonLat(lon, lat)) |
MapboxDirections.builder().routeOptions(...) | Routes API over HTTP, no SDK call | dock.route(Client.End.Point(here), Client.End.Code("7QSVX"), RouteMode.DRIVING) |
RouteOptions.exclude(...) | Routes API routeModifiers | dock.closures(), then excludePolygons(closures) |
SearchResult.fullAddress | Address.getAddressLine(0), Place.formattedAddress | label(place, Lang.EN) |
onError(e: Exception) | ApiException | DockError: status, code, retryAfter |
| no equivalent | no equivalent | dock.door("7QSVX"): a door code, to the door |
Search, reverse, route, closures and errors work as in the JS blocks above. The full pages, with code that runs in the SDK tests: iOS SDK, Android SDK.
What dock does not have yet
Coverage is Ulaanbaatar. No text labels on the base map, no satellite imagery, no traffic, no place autocomplete view in the iOS and Android SDKs (their Suggester gives suggestions as the user types, your view draws the list; the JS SDK has attachSuggest), no static images, no Street View. The travel time matrix is GET /v1/matrix (up to 10 by 10, like Mapbox's Matrix API), matrix in all three SDKs. Isochrones are GET /v1/isochrone (up to 4 contours of 60 minutes, like Mapbox's Isochrone API), isochrone in all three SDKs. Map matching is GET /v1/matching (up to 100 points, like Mapbox's Map Matching API), matching in all three SDKs. The best order for stops is GET /v1/optimized-trips (up to 12, like Mapbox's Optimization API v1), optimizedTrips in all three SDKs. The buildings and doors at or near a point are GET /v1/tiles/{sources}/tilequery/{lon},{lat} (up to 50, within 1000 m, like Mapbox's Tilequery API), tilequery in all three SDKs. The Android SDK (Kotlin, sdk/android) and the iOS SDK (Swift, sdk/swift) are first cuts: no map view of their own, not on a package index. Both give the dock style for MapLibre Native: Android (styleJson), iOS (styleJSON, written to a file). Both also record trips and search telemetry, with consent. Their pages: iOS SDK, Android SDK.
What dock has that they do not
Door codes: five characters per building and per хашаа, typed or shared (dock.door('7QSVX')). Door, floor and flat in the address. Routes that end at the door. Confidence on every door, and the official state address behind it or an "(unofficial)" label. See JS SDK.