Skip to content

From Esri

Esri is a Map plus a MapView, a World Geocoding Service, and a Network Analyst stack (Route, Service Area, and a dozen siblings). unmap is one key and three clients. The jobs that are a map, a search, or a route map cleanly. The rest of the ArcGIS platform does not come with you.

Stay on Esri if the app is an ArcGIS Online organisation: hosted feature layers, a web map you did not write, Living Atlas, SceneView, or anything that talks to a FeatureServer. unmap has no equivalent for those, and pretending otherwise would waste a week. Everything below assumes you are replacing a Canadian basemap, a Canadian locator, or a Canadian route.

npm i @unmap/sdk

The map

ArcGIS Maps SDK for JavaScript builds a Map (the basemap) and a MapView (the camera). unmap builds both in one call and hands back a MapLibre map.

esriConfig.apiKey = "ESRI_API_KEY";
const map = new Map({ basemap: "arcgis/streets" });
const view = new MapView({
  container: "viewDiv",
  map,
  center: [-114.0719, 51.0447],
  zoom: 11,
});
import { Unmap } from "@unmap/sdk";
 
const unmap = new Unmap({
  key: "um_live_...",
  container: "map",
  center: [-114.0719, 51.0447],
  zoom: 11,
});
live result
the code that produced it
import { Unmap } from "@unmap/sdk";

const unmap = new Unmap({
  key: "um_live_...",
  container: "map",
  center: [-114.0719, 51.0447],
  zoom: 11,
});

MapView.center is already [longitude, latitude]. You do not flip it. arcgis/streets becomes style: "base". arcgis/topographic and arcgis/outdoor are closest to outdoor. arcgis/dark-gray and arcgis/light-gray are muted with mode: "dark" or mode: "light". There is no imagery basemap. The twelve styles are on Maps API.

Widgets you added with view.ui.add (Search, Directions, BasemapToggle, Legend) do not exist as drop-ins. Zoom and compass are already on the map. Search and routing are the two clients below, and you render the list.

Graphics and popups

A Graphic on view.graphics becomes a MapLibre Marker. A PopupTemplate becomes a Popup. For a FeatureLayer of hundreds of points, add them as a GeoJSON source instead: WebGL draws those, the DOM does not.

view.graphics.add(
  new Graphic({
    geometry: { type: "point", longitude: -114.0719, latitude: 51.0447 },
    symbol: { type: "simple-marker", color: "red" },
    popupTemplate: { title: "Calgary Tower" },
  }),
);
import { maplibregl } from "@unmap/sdk";
 
new maplibregl.Marker()
  .setLngLat([-114.0719, 51.0447])
  .setPopup(new maplibregl.Popup().setHTML("<p>Calgary Tower</p>"))
  .addTo(unmap.map!);
live result
the code that produced it
import { Unmap, maplibregl } from "@unmap/sdk";

const unmap = new Unmap({
  key: "um_live_...",
  container: "map",
  center: [-114.0719, 51.0447],
  zoom: 11,
});

const [place] = await unmap.geocoder.search("Calgary Tower");

new maplibregl.Marker({ color: "#FF3E9A" })
  .setLngLat([place.lng, place.lat])
  .setPopup(new maplibregl.Popup().setText(place.name))
  .addTo(unmap.map!);

view.graphics.remove(graphic) becomes marker.remove(). A FeatureLayer URL (…/FeatureServer/0) has no unmap twin: export the features as GeoJSON and add a source, or keep that layer on Esri.

Geocoding

The World Geocoding Service (findAddressCandidates, suggest, reverseGeocode) and locator.addressToLocations become unmap.geocoder. There is no Search widget to drop in.

const url = new URL("https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer/findAddressCandidates");
url.searchParams.set("address", "Calgary Tower");
url.searchParams.set("f", "pjson");
url.searchParams.set("token", "ESRI_API_KEY");
const data = await (await fetch(url)).json();
const { x, y } = data.candidates[0].location;
view.goTo([x, y]);
const results = await unmap.geocoder.search("Calgary Tower");
const [first] = results;
if (first) unmap.map!.setCenter([first.lng, first.lat]);
const hints = await unmap.geocoder.autocomplete("calgary tow", { limit: 5 });
const here = await unmap.geocoder.reverse(-114.07, 51.05);
const nearby = await unmap.geocoder.nearby("pharmacy", {
  near: [-114.07, 51.05],
  radius: 1000,
});
live result
the code that produced it
import { Unmap } from "@unmap/sdk";

const unmap = new Unmap({ key: "um_live_..." });

const results = await unmap.geocoder.search("Calgary Tower", { limit: 3 });

Esri candidates carry address, location: { x, y }, score, and attributes (Addr_type, PlaceName, …). unmap results carry id, name, layer, lng, lat, names, and a flat address. There is no magicKey to pass from suggest to find, no batch geocodeAddresses, and no ArcGIS Places contacts or hours. Category search is nearby, not a category filter on the locator. The Geocoding API is the contract.

location on Esri is { x, y } in the output spatial reference, usually WGS84 lon/lat. That pair is already [lng, lat].

Routing and service areas

ArcGIS Route (/Route/NAServer/Route_World/solve) and Service Area become router.route and router.isochrone. Esri's stops are x,y (longitude first). The geometry Esri returns is often compressed or in Web Mercator; unmap returns a WGS84 GeoJSON LineString.

const url = new URL("https://route-api.arcgis.com/arcgis/rest/services/World/Route/NAServer/Route_World/solve");
url.searchParams.set("stops", "-114.0719,51.0447;-113.9871,51.0899");
url.searchParams.set("f", "pjson");
url.searchParams.set("token", "ESRI_API_KEY");
const data = await (await fetch(url)).json();
const route = await unmap.router.route([-114.0719, 51.0447], [-113.9871, 51.0899], {
  mode: "auto",
});
 
unmap.map!.addSource("route", {
  type: "geojson",
  data: { type: "Feature", properties: {}, geometry: route.geometry },
});
unmap.map!.addLayer({
  id: "route",
  type: "line",
  source: "route",
  paint: { "line-color": "#3887be", "line-width": 4 },
});
live result
the code that produced it
import { Unmap } from "@unmap/sdk";

const unmap = new Unmap({
  key: "um_live_...",
  container: "map",
  center: [-114.03, 51.07],
  zoom: 11,
});

const route = await unmap.router.route([-114.0719, 51.0447], [-113.9871, 51.0899], { mode: "auto" });

unmap.map!.on("load", () => {
  unmap.map!.addSource("route", {
    type: "geojson",
    data: { type: "Feature", properties: {}, geometry: route.geometry },
  });
  unmap.map!.addLayer({
    id: "route-line",
    type: "line",
    source: "route",
    layout: { "line-cap": "round", "line-join": "round" },
    paint: { "line-color": "#FF3E9A", "line-width": 3 },
  });
});

Driving is auto. Walking is pedestrian. Cycling is bicycle. Truck takes a vehicle profile in metres and tonnes, not Esri's attribute parameters on a travel mode. There are no extra stops: a request is exactly two points. There is no returnDirections text, no barriers, and no time windows.

Service Area is the same idea as router.isochrone, with a smaller surface: up to four contours, 120 minutes each.

const bands = await unmap.router.isochrone([-114.07, 51.05], {
  minutes: [10, 20],
  mode: "auto",
});

Closest Facility, OD Cost Matrix, Location-Allocation, and Fleet Routing have no unmap twin. A matrix is repeated route calls, and you will feel the meter. The Routing API documents modes, the truck profile, and what fails outside Canada.

What has no equivalent

  • Feature layers, MapServer, FeatureServer, and hosted layers. Export to GeoJSON or keep them on Esri.
  • ArcGIS Online web maps, Living Atlas, and Experience Builder.
  • SceneView and 3D.
  • Imagery and hybrid basemaps.
  • Spatial analysis as a service (buffer, intersect, geoenrichment).
  • Batch geocoding, Places contacts, and magicKey suggest.
  • Closest Facility, OD matrix, Location-Allocation, barriers, and time windows.
  • Named users, groups, and an Enterprise portal.
  • Coverage outside Canada.

If the app is GIS, not a map in a product, stay on Esri.

Checklist

  • Esri API key or named-user session replaced with um_live_... / um_test_...
  • Map + MapView replaced with new Unmap({ container, ... })
  • Graphics and popups on unmap.map
  • Locator, suggest, and reverse pointed at unmap.geocoder
  • Route and Service Area pointed at unmap.router, with the line drawn as GeoJSON
  • Feature layers either exported or left on Esri
  • Web maps, Living Atlas, and SceneView either dropped or left on Esri
  • Origin configured for production traffic (Plans & Limits)

The Quickstart is the same map built from scratch. The Examples are one idea per page if you would rather copy a working file than translate one.