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.
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.
- Java
- TypeScript (NativeScript)
- Object API
MassifElements elements = map.elements();
const elements = map.elements();
LocalVectorDataSource source = new LocalVectorDataSource(mapView.getOptions().getBaseProjection());
VectorLayer layer = new VectorLayer(source);
mapView.getLayers().add(layer);
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:
- Java
- TypeScript (NativeScript)
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"));
}
const pin = map.elements().style('pin', { type: 'marker', size: 26, color: 0xffe5484d, clickSize: 40 });
for (const summit of summits) {
map.addMarker({ type: 'marker', position: summit.at, style: pin.id });
}
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.
| Element | Position from | Style keys that matter |
|---|---|---|
marker | position / geometry / baseBillboard | size, color, bitmap, anchorPointX/Y, clickSize |
point | position / geometry | size, color, clickSize |
line | poses or a line / GeoJSON geometry | width, color, clickWidth, stretchFactor |
polygon | poses + optional holes, or rings | color, plus lineStyle for the border |
text | position / geometry | fontName, fontSize, color, strokeWidth, breakLines |
balloon | position, plus title and description | cornerRadius, 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.