Skip to main content

The surface API

The SDK has two public faces. The object API is the classic one: a class per capability, a setter per option, and a per-language binding generated by SWIG — that is what the Javadoc and Jazzy reference documents.

The surface API is the one everything new is built on. An app names objects by id, describes them with JSON specs, and reads or writes anything through a dotted path. It is the same shape in every language, so an example reads the same in Kotlin, Swift and TypeScript, and it is what the NativeScript plugin, the C ABI and the coming React Native binding all sit on.

Where to look

This page is the concept. Every spec type, property, method and event is in the API reference, generated from the SDK build. The design rationale is in the facade internals.

Five things, and that is the whole API

create(kind, id, spec)build an object and register it under an id
destroy(kind, id)drop it
set / getwrite or read any property, by dotted path
call(method, args)run a method — route, search, fit bounds
on(event)subscribe

Adding a feature to the SDK never adds a method here. It adds a row in a generated table, which is why the reference can be generated and cannot drift.

A map, from nothing

MassifMap map = MassifMap.attach(mapView);

map.addLayer("basemap", Spec.of("raster")
.set("source", Spec.of("http")
.set("url", "https://tile.openstreetmap.org/{z}/{x}/{y}.png")
.set("maxZoom", 19)));

map.camera().moveTo(new Position(6.8652, 45.8326), 11);

Positions are longitude first, matching GeoJSON and the wire format.

Kinds and ids

Every object belongs to a kind — the namespace its id lives in, and the set of spec types it can be built from. There are thirteen, one page each in the reference:

KindWhat it holds
layerwhat is drawn
sourcewhere tiles come from
style / styleset / assetsthe decoder, its CartoCSS, and the package it is read from
optionsfog, sky, light, terrain
element / elementstylemarkers, popups, lines, and how they look
geometry / featureshapes and shapes-with-properties
search / routing / geocodingservices and their requests
dataraw bytes

An id is the app's own name — "basemap", "routes", "pin" — and is how one object reaches another without holding a reference to it:

{"type": "vector", "source": "osm", "style": "outdoor"}

A string where a spec is expected is an id lookup. An object there is an anonymous child built on the spot. Both are checked against the class the key needs, so a style id naming a source is refused rather than cast.

A spec is a constructor plus properties

A factory only handles what the C++ constructor takes. Every other key is applied afterwards through the property table — so an option added to a class is usable here on the next build, with no factory change:

{"type": "persistent-cache",
"databasePath": "/data/.../osm.db",
"capacity": 268435456,
"source": {"type": "http", "url": "https://…/{z}/{x}/{y}.pbf", "maxZoom": 14}}

capacity is not a constructor argument. It reaches setCapacity through the table, the same way opacity on a layer or rangeStart on the fog does.

Dotted paths

Anything readable or writable is one path, and the path traverses OBJECT properties:

map.set("fog.rangeStart", 1.2);
map.set("terrain.exaggeration", 1.4);
map.fog().apply(Map.of("enabled", true, "rangeEnd", 4.0)); // one crossing, not three

double tilt = map.getDouble("camera.tilt");

A group (map.fog(), layer.style()) is only a path prefix — there are no 700 hand-written accessors behind it, and apply hands the whole object over in one call rather than looping.

Events

map.onClick(e -> map.camera().animate(2).moveTo(e.position(), 14));
layer.onFeatureClick(e -> show(e.featureId(), e.get("name")));

Payloads are read lazily — a feature with a long geometry costs nothing unless the handler asks for the geometry — and an event object is valid only for the duration of the handler. The event names and their payloads are listed per class in the reference.

Bringing an existing app across

An app on the object API does not have to rebuild its map. adopt gives an object it already built an id, and with it every property, method and event of the surface API:

MassifLayer base = map.adoptFirst("base", VectorTileLayer.class);
base.onFeatureClick(e ->);

That also covers the reverse direction — an AssetPackage subclass written in Kotlin, Swift or TypeScript (an app folder, an encrypted bundle) is adopted under an id, and any assets key then names it. Writing a whole data source that way is the extension guide.

What it does not do

  • Register a new spec type. The table is generated at build time, so an extension constructs its object and adopts it under an id instead.
  • Replace the object API. Both surfaces are supported; the surface API is the one that grows.

Every example in the gallery is written against it, on all three platforms.