Skip to content

From HERE

HERE is a platform object, a set of JavaScript map classes, and a family of REST APIs (Geocoding & Search v7, Routing v8, Isoline). unmap is one key and three clients. The jobs map cleanly. The object model does not: you will throw away H.Map, H.service.Platform, and the HERE UI toolkit.

Stay on HERE if you need coverage outside Canada, HERE's fleet extras (tolls, live traffic as a product, EV routing), or a map that is not a GL vector map. Truck routing exists on both sides; unmap's truck profile is documented on the Routing API and is only as good as OpenStreetMap's restriction tags.

npm i @unmap/sdk

The map

HERE constructs a Platform, asks it for a default layer, then constructs an H.Map. unmap constructs the map in one call.

const platform = new H.service.Platform({ apikey: "HERE_API_KEY" });
const layers = platform.createDefaultLayers();
const map = new H.Map(document.getElementById("map"), layers.vector.normal.map, {
  center: { lat: 51.0447, lng: -114.0719 },
  zoom: 11,
});
new H.mapevents.Behavior(new H.mapevents.MapEvents(map));
H.ui.UI.createDefault(map, layers);
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,
});

HERE's vector.normal.map is style: "base". Night and grey layers are mode: "dark" and style: "muted". There is no satellite layer. Zoom and compass land on the map by default; controls: false takes them off.

HERE's JS API takes { lat, lng }. unmap takes [lng, lat]. REST on HERE also tends to write lat,lng (at=51.04,-114.07). Flip both.

Markers

H.map.Marker plus H.ui.InfoBubble become MapLibre Marker and Popup.

const marker = new H.map.Marker({ lat: 51.0447, lng: -114.0719 });
map.addObject(marker);
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!);

map.addObject / map.removeObject become marker.addTo(map) / marker.remove(). A group of markers is a GeoJSON source, not an H.map.Group.

Geocoding and autosuggest

HERE Geocoding & Search v7 (/v1/geocode, /v1/autosuggest, /v1/revgeocode, /v1/browse) become four methods on unmap.geocoder. HERE's at and in are near and bbox, and the coordinate order flips.

const url = new URL("https://geocode.search.hereapi.com/v1/geocode");
url.searchParams.set("q", "Calgary Tower");
url.searchParams.set("apiKey", "HERE_API_KEY");
const data = await (await fetch(url)).json();
const { lat, lng } = data.items[0].position;
map.setCenter({ lat, lng });
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 });

HERE items carry id, title, address, position, and a rich categories / contacts / openingHours block when Discover or Browse hits a POI. unmap results carry id, name, layer, lng, lat, names, and a flat address. There are no contacts, hours, or HERE ids you can round-trip. Browse-by-category is nearby. The Geocoding API is the contract.

Routing and isoline

HERE Routing v8 (/v8/routes) and Isoline become router.route and router.isochrone. HERE's polyline codec goes away: the geometry is GeoJSON.

const url = new URL("https://router.hereapi.com/v8/routes");
url.searchParams.set("origin", "51.0447,-114.0719");
url.searchParams.set("destination", "51.0899,-113.9871");
url.searchParams.set("transportMode", "car");
url.searchParams.set("return", "polyline,summary");
url.searchParams.set("apiKey", "HERE_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 },
  });
});

transportMode=car is auto. pedestrian and bicycle keep those names. truck takes a vehicle profile in metres and tonnes (height, width, length, weight, …), not HERE's truck[grossWeight] kilograms. There are no via points, no return=polyline to request, and no flexible-polyline to decode.

Isoline is the same idea as HERE's Isoline API, 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",
});

What has no equivalent

  • Coverage outside Canada.
  • Live traffic as a product, tolls, EV, and public transit. Truck exists; it is OSM tags, not a permit check.
  • HERE identifiers, contacts, opening hours, and Place photos.
  • The HERE UI toolkit and H.service.Platform.
  • Waypoint routing, matrix routing, and fleet telematics.
  • Satellite imagery.

Checklist

  • HERE API key replaced with um_live_... / um_test_...
  • { lat, lng } and lat,lng query params flipped to [lng, lat]
  • H.Map replaced with new Unmap({ container, ... })
  • Geocode, autosuggest, revgeocode, and browse pointed at unmap.geocoder
  • Routing v8 and Isoline pointed at unmap.router, with the line drawn as GeoJSON
  • Flexible-polyline decoding deleted
  • Fleet-only features either dropped or left on HERE
  • Origin configured for production traffic (Plans & Limits)

The Quickstart is the same map built from scratch. Truck defaults and the limits of the OSM restriction data are on the Routing API.