Taos Engine ▦ Taos: API Documentation

7 — Vector Data

Live demo: run this tutorial in your browser — built from src/07_vector_data.ts. Fully self-contained (no credentials).

Three related ways to put data, not just imagery, on the globe: GeoJSON layers (your own polygons/lines/points), declarative styling of features by their properties, and CZML time-dynamic data (entities that animate along tracks). Sources: samples/geo_geojson.ts, samples/geo_tile_style.ts, samples/geo_flight_tracker.ts.

Part A — GeoJSON layers#

GeoJsonLayer bakes GeoJSON into an engine mesh anchored in your GeoFrame: extruded polygons (buildings), world-width lines (roads), and points. Load it three ways:

import { GeoJsonLayer } from 'taos/geo/index.js';

const layer = await GeoJsonLayer.fromUrl(device, '/data/city.geojson', geo.frame, style);
// or: GeoJsonLayer.fromGeoJson(device, jsonObject, geo.frame, style)
// or: GeoJsonLayer.fromParsed(device, parsedGeoJson, geo.frame, style)  // reuse across styles

The style controls how each geometry type bakes:

const layer = GeoJsonLayer.fromGeoJson(device, json, geo.frame, {
  polygon: {
    extrudeHeight: (p) => (typeof p?.height === 'number' ? p.height : 0),  // per-feature height
    color: (p) => colorOf(p, [0.65, 0.65, 0.68]),                          // per-feature RGB
  },
  point: { height: 22 },
  pickable: true,            // required for pick() and per-feature recoloring
  polygonLabelLift: 6,       // label height above the roof
});

The layer bakes to an engine Mesh; add it to the scene like any other renderable (the sample shows the exact draw wiring). Lines and points have their own bake options and dedicated features (GeoLineFeature for screen-constant-width roads).

Picking and highlighting#

With pickable: true you can ray-test the polygons and recolor the hit feature:

const hit = layer.pick([origin.x, origin.y, origin.z], [dir.x, dir.y, dir.z]);
if (hit) {
  console.log(hit.featureId, hit.properties, hit.position);
  layer.setFeatureColor(hit.featureId, [1, 0.8, 0.2]);  // highlight
}
layer.resetColors();   // restore all features

Labels#

layer.labels(...) produces label placements from the features' properties; feed them to GeoLabelFeature:

import { GeoLabelFeature } from 'taos/geo/geo_label_feature.js';   // direct subpath
import { TonemapFeature } from 'taos/engine/index.js';

const labels = layer.labels({ textProperty: 'name', includePolygons: true });
// The feature takes any `{ labels, frame }` source via its `scene` option, and
// runs before the tonemap pass so the text composites into the lit frame.
engine.addFeatureBefore(new GeoLabelFeature({
  scene: { labels, frame: geo.frame },
  pixelSize: 16,
  distanceFade: { near: 300, far: 1600, minOpacity: 0.1 },
}), TonemapFeature.name);

Part B — declarative styling with Cesium3DTileStyle#

Rather than hard-coding colors, describe them with a CesiumJS-compatible expression language over each feature's properties — the same styling that works on 3D Tiles also applies to a GeoJsonLayer.

import { Cesium3DTileStyle } from 'taos/geo/index.js';

const style = new Cesium3DTileStyle({
  color: {
    conditions: [
      ['${height} >= 140', "color('#7e3ff2')"],   // tall → purple
      ['${height} >= 90',  "color('#e23b3b')"],    // mid  → red
      ['true',             "color('white')"],       // else → white
    ],
  },
  show: '${height} > 100',                          // hide short features entirely
});

layer.applyStyle(style);   // evaluates color()/show() per feature, writes vertex RGBA

The expression language supports ${property} refs, the usual arithmetic / comparison / logical / ternary operators, and functions like color('name'|'#hex'), rgb(...), rgba(...), hsl(...), clamp, min, max, pow, etc. A conditions chain is evaluated top-to-bottom; the first true case wins. show that evaluates false sets the feature's alpha to 0 (and turns on alpha-test). geo_tile_style.ts wires this into a live editor with presets.

Part C — time-dynamic data (CZML)#

To animate entities over time — aircraft, vehicles, satellites — use a Clock plus position properties sampled over time. The easiest source is CZML, which loadCzml parses into entities (each with an interpolated position) and a clock:

import { loadCzml } from 'taos/geo/index.js';

const doc = loadCzml(czmlJson);   // { clock, entities, byId }
const clock = doc.clock!;          // a Clock built from the document packet

Each frame, advance the clock and read every entity's interpolated ECEF position, then convert to world space and move its marker:

engine.beforeFrame((frame) => {
  clock.tick(frame.dt);                          // advance sim time (respects multiplier + loop)
  for (const entity of doc.entities) {
    const ecef = entity.position?.getValueEcef(clock.currentTime);
    if (!ecef) {
      continue;
    }
    const world = geo.frame.worldFromEcefPoint(ecef);
    markerFor(entity).setPosition(world.x, world.y, world.z);
  }
});

A CZML position with interpolationAlgorithm: 'LAGRANGE' and several cartographicDegrees samples gives smooth curved tracks; 'LINEAR' (the default) gives straight segments. The Clock carries startTime/stopTime/multiplier (sim seconds per real second) and a clockRange ('LOOP_STOP', 'CLAMPED', 'UNBOUNDED'); clock.fraction gives a [0,1] timeline position for a scrubber.

Building tracks in code#

You don't need a CZML document — you can build a SampledPositionProperty directly:

import { Clock, SampledPositionProperty } from 'taos/geo/index.js';

const clock = new Clock({ startTime: 0, stopTime: 1800, multiplier: 120 });

const track = new SampledPositionProperty();
track.algorithm = 'LAGRANGE';
track.degree = 2;
track.addSampleLonLat(0,    -122.30, 37.62, 0);
track.addSampleLonLat(600,  -122.10, 37.80, 3000);
track.addSampleLonLat(1800, -121.90, 38.05, 10000);

// each frame: track.getValueEcef(clock.currentTime) → worldFromEcefPoint → place

geo_flight_tracker.ts animates three aircraft along sampled tracks with paths, points, and labels.

Part D — styling streamed vector tiles (MapLibre GL)#

Parts A–B styled your own GeoJSON / 3D-Tiles features. The streamed OSM vector tiles from tutorial 3 (addVectorTiles) have their own styling path: a VectorStyle that says which OpenMapTiles source-layers render and their fill colors, road widths, and building extrusion heights. Pass it as opts.style:

import { parseMapLibreStyle, type VectorStyle } from 'taos/geo/index.js';

// Hand-author a VectorStyle…
const blueprint: VectorStyle = {
  fill: [{ sourceLayer: 'water', color: [0.02, 0.06, 0.12] }],
  line: [{ sourceLayer: 'transportation', color: [0.2, 0.85, 1.0], width: 8, filter: ['==', 'class', 'primary'] }],
  extrusion: [{ sourceLayer: 'building', color: [0.1, 0.45, 0.62], heightProp: 'render_height' }],
};
geo.addVectorTiles(OPENFREEMAP_VECTOR, { style: blueprint });

// …or adapt a real MapLibre GL style document:
const parsed = parseMapLibreStyle(mapLibreStyleJson);   // { style, sourceUrl? }
geo.addVectorTiles(OPENFREEMAP_VECTOR, { style: parsed.style });

parseMapLibreStyle is a best-effort adapter: it reads the subset of a MapLibre GL style that maps onto 3D geometry — fill polygons (draped), line ribbons (widened to meters), and fill-extrusion (buildings) — pulling each layer's source-layer, a constant paint color, line width, extrusion height, and feature filter. Paint expressions it can't cheaply evaluate, symbols, raster, and hillshade are skipped; it's not a conformant MapLibre renderer. Omit style entirely for the built-in default look (DEFAULT_VECTOR_STYLE).

To restyle live, geo.remove(vectorTileset) and addVectorTiles again with the new style — the terrain underneath stays put. geo_vector_style.ts swaps between hand-authored and parsed styles over keyless OpenFreeMap tiles.

A style's symbol layers become map labels (place names, street names, POIs); GeoScene extracts them and exposes them as a label source — feed geo straight to a GeoLabelFeature to render them (see tutorial 4).

Next#