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.
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"));
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
| Type | Renders as | Notes |
|---|---|---|
COMPOSITE_SOURCE_TYPE_RASTER | RasterTileLayer | Styled by raster-* properties. |
COMPOSITE_SOURCE_TYPE_HILLSHADE | HillshadeRasterTileLayer | Decoder from the DEM's dem_encoding; hillshade-* properties. |
COMPOSITE_SOURCE_TYPE_VECTOR | own VectorTileLayer | Master-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-opis parsed but not yet applied.- New API surfaces via SWIG — regenerate proxies (
swigpp-*.py) when building from source:CompositeVectorTileLayerandHillshadeRasterTileLayer.Exaggeration.