Skip to main content

Vector objects on the map

A vector element is something the app puts on the map itself, as opposed to something a tile brought: a marker, a point, a line, a polygon, a text label, a popup, a 3D model. Every one is a geometry plus a style, held by an elements layer.

Worth deciding first

Elements are for app data the user interacts with — a handful to a few thousand. For a large dataset, tile it through GeoJSON vector tiling and style it with CartoCSS instead. See performance.

The layer

The surface API gives you one for free — map.elements() builds the source and the layer on first use. Working examples: markers and popups, a GeoJSON line.

MassifElements elements = map.elements();

Share the style

A style is an object, not a per-element setting. Build it once and point every element at it by id — that is what matters as soon as there are more than a few dozen:

map.elements().style("pin", Spec.of("marker")
.set("size", 26)
.set("color", 0xffe5484d)
.set("clickSize", 40));

for (Summit summit : summits) {
map.addMarker(Spec.of("marker")
.set("position", new double[] {summit.lon, summit.lat})
.set("style", "pin"));
}

A style key that is a string is looked up by id; an object there is built inline. Colours are 0xAARRGGBB.

The elements

Every element takes a style and a position or geometry. position is the shorthand for a point; geometry takes anything from the geometry kind, including GeoJSON.

ElementPosition fromStyle keys that matter
markerposition / geometry / baseBillboardsize, color, bitmap, anchorPointX/Y, clickSize
pointposition / geometrysize, color, clickSize
lineposes or a line / GeoJSON geometrywidth, color, clickWidth, stretchFactor
polygonposes + optional holes, or ringscolor, plus lineStyle for the border
textposition / geometryfontName, fontSize, color, strokeWidth, breakLines
balloonposition, plus title and descriptioncornerRadius, leftColor, titleFontSize, triangleWidth
{"type": "line",
"poses": [[6.86, 45.83], [6.92, 45.86], [6.98, 45.87]],
"style": {"type": "line", "width": 6, "color": 4285887861}}

A polygon's border is a nested style rather than a second registered object:

{"type": "polygon",
"poses": [[6.8, 45.8], [7.0, 45.8], [7.0, 45.9], [6.8, 45.9]],
"style": {"type": "polygon", "color": 2164195328,
"lineStyle": {"type": "line", "width": 2, "color": 4278190080}}}

Every key of every style: element styles reference.

Popups

A popup is an element like any other, added when a click happens and removed when the next one does. Nothing pops up by itself:

// consumeClick claims the tap. With plain onClick, the map's own onClick fires for the SAME
// tap and dismisses the popup as it opens.
map.elements().consumeClick(e -> {
show(e.position());
return true;
});
map.onClick(e -> dismiss());
popup = map.addPopup(Spec.of("balloon")
.set("position", at)
.set("title", summit.name)
.set("description", summit.metres + " m")
.set("style", Spec.of("balloon")
.set("cornerRadius", 6)
.set("leftColor", 0xffe5484d)
.set("titleFontSize", 14)));

titleField and descriptionField read the text out of the element's metaData instead, which is what a popup over tile features wants.

Attaching to another element

A billboard can hang off another one — that is how a label sits above its marker and moves with it:

{"type": "text", "baseBillboard": "pin-42", "text": "Mont Blanc",
"style": {"type": "text", "fontSize": 13, "verticalOffset": 18}}

hideIfOverlapped, placementPriority and causesOverlap decide what happens when two of them collide.

3D models

NMLModel draws a model in NML format at a position, with a scale and a rotation. Converting from COLLADA or glTF is done ahead of time; the SDK reads NML only.

BinaryData data = AssetUtils.loadAsset("fcd_auto.nml");
NMLModel model = new NMLModel(pos, data);
model.setScale(20);
source.add(model);

For a whole city of buildings, use the style's 3D extrusion instead of models — it is one draw call per tile rather than one per building.

Reading a click

An element click carries where the element was hit, where the finger was, and the click type:

map.elements().onClick(e -> {
Position at = e.position(); // the element's position
Position finger = e.clickPosition(); // where the tap landed
String name = e.get("vectorElement.metaData.name");
});

Clicks on tile features are a different event — see the feature click example.

The object API version

Elements predate the surface API and the classes are unchanged: Marker, Line, Polygon, Text, BalloonPopup, NMLModel, each with a *StyleBuilder.

MarkerStyleBuilder builder = new MarkerStyleBuilder();
builder.setSize(30);
builder.setColor(new Color(0xFF00FF00));
MarkerStyle style = builder.buildStyle();

MapPos pos = proj.fromWgs84(new MapPos(24.646469, 59.426939));
source.add(new Marker(pos, style));

Two differences worth knowing: positions are in the layer's projection, so they need fromWgs84, and a style is built by a builder rather than described. The surface API takes degrees and JSON for exactly those reasons.