Skip to main content

Composite Vector Tile Layer

CompositeVectorTileLayer is a VectorTileLayer that weaves external data sources (raster, hillshade, extra vector / contour) into a master CartoCSS style's layer order. Each external source is placed at the slot of a matching layer name and configured from a #name { … } block in the style — including zoom- and param::-dependent expressions.

Fork feature

Added in PR #19. Pairs with the libs-massif config symbolizers (submodule PR massif-maps/massif-maps-libs#5). Full property reference: docs/features/composite-layer-reference.md.

Why​

Normally, mixing a hillshade or raster overlay into a vector basemap means juggling several independent Layers and keeping their z-order and opacity in sync by hand. CompositeVectorTileLayer lets a single CartoCSS style own the whole stack: you name a layer in the style, attach a source to that name, and the SDK renders it in the right slot with style-driven configuration.

Usage​

import com.massifmaps.layers.CompositeVectorTileLayer
import com.massifmaps.layers.CompositeSourceType
import com.massifmaps.datasources.HTTPTileDataSource

// Master style + its base vector source, as for a normal VectorTileLayer.
val composite = CompositeVectorTileLayer(baseVectorSource, mbVectorTileDecoder)

// Raster overlay placed at the style layer named "satellite".
composite.addExternalDataSource(
"satellite",
HTTPTileDataSource(0, 19, "https://your.tiles/sat/{z}/{x}/{y}.jpg"),
CompositeSourceType.COMPOSITE_SOURCE_TYPE_RASTER
)

// Hillshade placed at the style layer named "hillshade". The elevation decoder is
// resolved per tile from the DEM's "dem_encoding" meta data (pass one to override
// the source-level default).
composite.addExternalDataSource(
"hillshade",
demSource,
CompositeSourceType.COMPOSITE_SOURCE_TYPE_HILLSHADE
)

// An extra vector/contour source, master-styled and filtered to its layer name.
composite.addExternalDataSource(
"contour",
contourSource,
CompositeSourceType.COMPOSITE_SOURCE_TYPE_VECTOR
)

mapView.layers.add(composite)

Sources are dynamic — add or remove them at runtime:

composite.removeExternalDataSource("hillshade")
composite.addVectorDataSource("labels", extraLabelsSource) // shortcut for VECTOR type

With the surface API​

The layer itself is a spec:

map.addLayer("base", Spec.of("composite-vector")
.set("source", "osm")
.set("style", "outdoor"));
External sources are methods, not spec keys

A spec cannot name a slot, so external sources are wired after construction with the layer's methods: addExternalDataSource(name, source, type) — type as the CompositeSourceType integer (0 raster, 1 hillshade, 2 vector), the facade having no enum argument — addVectorDataSource, removeExternalDataSource, getExternalDataSourceNames, setExternalDataSourceZoomLevelBias, setExternalDataSourceMaxOverzoomLevel and getExternalChildLayer. The layer reference has the spec keys.

Source types​

TypeRenders asNotes
COMPOSITE_SOURCE_TYPE_RASTERRasterTileLayerStyled by raster-* properties.
COMPOSITE_SOURCE_TYPE_HILLSHADEHillshadeRasterTileLayerDecoder from the DEM's dem_encoding; hillshade-* properties.
COMPOSITE_SOURCE_TYPE_VECTORown VectorTileLayerMaster-styled, filtered to its layer name, overzooms independently via its MaxOverzoomLevel.

Styling from CartoCSS​

Each external source is configured by a block whose selector matches its name. Properties are evaluated per frame, so [view::zoom] and param:: parameters animate smoothly:

#satellite {
raster-opacity: 0.6;
}

#hillshade {
hillshade-exaggeration: 1.4;
hillshade-illumination-direction: 315;
}

#contour {
contour-base-interval: 100;
}

Supported property families include raster-opacity, hillshade-exaggeration / -illumination-direction / …, and contour-base-interval / …. [view::zoom] expressions and param:: bundle parameters are supported. See the full config reference for every property, smooth-vs-per-zoom-level timing, and interpolation syntax.

Smooth hillshade exaggeration​

The fork also adds HillshadeRasterTileLayer.getExaggeration() / setExaggeration() — a per-frame relief factor applied as a shader uniform (no re-decode, default 1.0), which is what lets hillshade-exaggeration animate without a tile rebuild.

A hillshade slot draws exactly what a stand-alone HillshadeRasterTileLayer draws with the same values. The CartoCSS hillshade-exaggeration is that layer's exaggeration, not MapLibre's property of the same name: to match a MapLibre style, see Matching MapLibre (hillshade-contrast, hillshade-method: standard, hillshade-height-scale: 1).

How it renders​

The tile-build-time rendererLayerFilter means one renderer can't be re-filtered per frame, so each style-layer group renders on its own stable-filtered layer: group 0 on the composite layer itself, later groups on internal VectorTileLayers over the base source, with the external children drawn between them in painter order. The style background is drawn once by the bottom group. This works in 2D and over 3D terrain.

A vector slot at several depths​

A project may list one source layer several times, one attachment per entry, so it draws at several depths - Massif's contour::contour_index, contour::contour under the roads and contour::contour_label among the names. A VECTOR slot of that name draws each entry's own styles where the entry stands, through a child layer per depth; consecutive entries share one. A slot listed once keeps its single child.

Known limitations & follow-ups​

  • Each style-layer group (groups = external slots + 1) builds only its own styles, but parses the tile again. A single-pass renderer (one parse) is a scoped follow-up.
  • raster-comp-op is parsed but not yet applied.
  • New API surfaces via SWIG — regenerate proxies (swigpp-*.py) when building from source: CompositeVectorTileLayer and HillshadeRasterTileLayer.Exaggeration.