Customizing styles
The /v1/styles endpoint ships one good baseline per layer per target. Color, halo, label-field, and visibility tweaks are a client-side concern — here are the recipes for each renderer.
MapLibre / Mapbox
Recolor a fill or line layer
import { fetchLandStyle } from 'landmapmagic';
const style = await fetchLandStyle({
apiKey: 'YOUR_API_KEY',
target: 'maplibre',
layers: ['clu'],
});
for (const layer of style.layers) {
if (layer.id === 'clu-outline' && layer.paint) {
layer.paint['line-color'] = '#ff6b35';
layer.paint['line-width'] = 3;
}
}
map.setStyle(style);// Mutate live properties via the MapLibre API.
map.setPaintProperty('clu-outline', 'line-color', '#ff6b35');
map.setPaintProperty('clu-outline', 'line-width', 3);Hide labels for one layer
// Toggle visibility of CLU acreage labels.
map.setLayoutProperty('clu-labels', 'visibility', 'none');Swap the label field
// Show the parcel id instead of acreage at zoom 14+.
map.setLayoutProperty('clu-labels', 'text-field', [
'concat',
['get', 'id'],
]);Custom hover treatment
// Layers ship with feature-state aware paint expressions; hook up
// mousemove + setFeatureState yourself.
let hoveredId = null;
map.on('mousemove', 'clu-outline', (e) => {
if (!e.features?.length) return;
if (hoveredId != null) {
map.setFeatureState(
{ source: 'clu', sourceLayer: 'clu', id: hoveredId },
{ hover: false }
);
}
hoveredId = e.features[0].id;
map.setFeatureState(
{ source: 'clu', sourceLayer: 'clu', id: hoveredId },
{ hover: true }
);
});
map.on('mouseleave', 'clu-outline', () => {
if (hoveredId != null) {
map.setFeatureState(
{ source: 'clu', sourceLayer: 'clu', id: hoveredId },
{ hover: false }
);
}
hoveredId = null;
});Leaflet
The Leaflet target returns an array of tileLayer / vectorGrid descriptors. Mutate the descriptor before mount, or call native Leaflet methods afterwards.
Override vectorGrid styles before mount
import { fetchLandStyle } from 'landmapmagic';
import L from 'leaflet';
import 'leaflet.vectorgrid';
const { layers } = await fetchLandStyle({
apiKey: 'YOUR_API_KEY',
target: 'leaflet',
layers: ['clu'],
});
const cluDesc = layers.find((d) => d.id === 'clu');
// Replace the vector style for the polygon source-layer with your own.
cluDesc.options.vectorTileLayerStyles.clu = {
weight: 3,
color: '#ff6b35',
fillColor: '#ff6b35',
fillOpacity: 0.1,
};
L.vectorGrid.protobuf(cluDesc.options.url, cluDesc.options).addTo(map);Restyle after mount
import { mountLeafletLandMap } from 'landmapmagic/leaflet';
const handle = await mountLeafletLandMap({
apiKey: 'YOUR_API_KEY',
map,
layers: ['clu'],
});
// vectorGrid exposes setFeatureStyle for per-feature overrides.
const grid = handle.layers.get('clu');
grid?.setFeatureStyle(featureId, {
weight: 4,
color: '#ff6b35',
fillColor: '#ff6b35',
fillOpacity: 0.4,
});Color features by a property
The Leaflet baseline ships static styles. To color by a feature property (e.g. SSURGO by hydrologic group, PLSS by admin_level), pass a function for that source-layer's style.
import { fetchLandStyle } from 'landmapmagic';
import L from 'leaflet';
import 'leaflet.vectorgrid';
const { layers } = await fetchLandStyle({
apiKey: 'YOUR_API_KEY',
target: 'leaflet',
layers: ['ssurgo'],
});
const HYDGRP_COLORS = {
A: '#2E7D32',
B: '#66BB6A',
C: '#FFA726',
D: '#EF5350',
};
const desc = layers.find((d) => d.id === 'ssurgo');
desc.vectorTileLayerStyles.ssurgo = (properties) => ({
weight: 0.5,
color: '#424242',
fill: true,
fillColor: HYDGRP_COLORS[properties.hydgrp] ?? '#9E9E9E',
fillOpacity: 0.6,
});
L.vectorGrid.protobuf(desc.url, {
...desc.options,
vectorTileLayerStyles: desc.vectorTileLayerStyles,
}).addTo(map);Add hover styling
Leaflet's VectorGrid exposes setFeatureStyle / resetFeatureStyle for transient per-feature paint. Wire it up after mount.
const grid = handle.layers[0]; // first vectorGrid for the layer
let hoveredId = null;
grid.on('mouseover', (e) => {
const id = e.layer?.properties?.lmm_label_id;
if (!id) return;
if (hoveredId != null) grid.resetFeatureStyle(hoveredId);
grid.setFeatureStyle(id, {
weight: 4,
color: '#FF8C5A',
opacity: 1,
});
hoveredId = id;
});
grid.on('mouseout', () => {
if (hoveredId != null) grid.resetFeatureStyle(hoveredId);
hoveredId = null;
});Render labels as DivIcons
The Leaflet target returns a separate labelSourceLayer (deduped server-side via lmm_label_id). Spawn a second vectorGrid pinned to that source-layer and place L.divIcon markers in its load handler.
const labelGrid = L.vectorGrid.protobuf(desc.url, {
...desc.options,
interactive: false,
vectorTileLayerStyles: {
[desc.labelSourceLayer]: () => ({ weight: 0, opacity: 0, fill: false }),
},
getFeatureId: (f) => f.properties[desc.labelIdField ?? 'lmm_label_id'],
});
const seen = new Set();
const labelLayer = L.layerGroup().addTo(map);
labelGrid.on('load', () => {
for (const tile of Object.values(labelGrid._tiles ?? {})) {
const features = tile?._features?.[desc.labelSourceLayer]?.features ?? [];
for (const f of features) {
const props = f.feature.properties;
const id = String(props[desc.labelIdField ?? 'lmm_label_id'] ?? '');
if (!id || seen.has(id)) continue;
seen.add(id);
const [lng, lat] = f.feature.geometry.coordinates;
L.marker([lat, lng], {
icon: L.divIcon({
className: 'lmm-label',
html: `<span>${props.calcacres} ac</span>`,
}),
interactive: false,
}).addTo(labelLayer);
}
}
});
labelGrid.addTo(map);Google + deck.gl
The Google target returns deck.gl-friendly accessor descriptors (static, by-zoom, by-property). Override accessors before they reach deck.gl, or pass props overrides through the mount helper.
Override an accessor before mount
import { fetchLandStyle } from 'landmapmagic';
const { vectorOverlays } = await fetchLandStyle({
apiKey: 'YOUR_API_KEY',
target: 'google',
layers: ['clu'],
});
const cluOverlay = vectorOverlays.find((o) => o.id === 'clu');
const polygonSub = cluOverlay.subLayers.find((s) => s.kind === 'polygon');
polygonSub.accessors.getLineColor = { kind: 'static', value: [255, 107, 53, 255] };
polygonSub.accessors.getLineWidth = { kind: 'static', value: 3 };
// then feed the modified overlay through your existing mount helperHighlight features on hover
deck.gl handles hover via pickable: true and the onHovercallback. Wrap the MVTLayer's color/width accessors so they branch on a tracked id.
import { MVTLayer } from '@deck.gl/geo-layers';
let hoveredId = null;
const layer = new MVTLayer({
id: 'clu',
data: cluOverlay.url,
pickable: true,
stroked: true,
filled: false,
uniqueIdProperty: 'id',
getLineColor: (f) =>
f.properties.id === hoveredId ? [255, 140, 90, 230] : [255, 107, 53, 230],
getLineWidth: (f) => (f.properties.id === hoveredId ? 4 : 2),
lineWidthUnits: 'pixels',
onHover: (info) => {
const id = info?.object?.properties?.id ?? null;
if (id !== hoveredId) {
hoveredId = id;
layer.setNeedsUpdate(); // trigger re-render
}
},
updateTriggers: {
getLineColor: [hoveredId],
getLineWidth: [hoveredId],
},
});Drop labels entirely
import { mountGoogleLandMap } from 'landmapmagic/google';
const handle = await mountGoogleLandMap({
apiKey: 'YOUR_API_KEY',
map,
overlay,
layers: ['clu'],
});
// handle.layers is a Map<string, deck.gl Layer> keyed by sublayer id.
// Remove the label TextLayer for clu:
const labelLayer = handle.layers.get('clu:labels');
if (labelLayer) {
overlay.setProps({
layers: overlay.props.layers.filter((l) => l !== labelLayer),
});
}Tips
- Mutate before mount when you need a setting in effect on initial paint (e.g. styling a layer you immediately scroll over).
- Use the renderer's native API after mount for interactive tweaks (toggling visibility, recoloring on selection, etc.).
- Keep
lmm_label_idin place if you customize label rendering — every adapter relies on it for tile-boundary deduplication. fetchLandStylecaches in-memory per (target, layers) for the session, so repeated calls are cheap.