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.

Customization happens after fetch. The API has no override query params. Mutating the returned document gives you full control without baking client preferences into the API contract.

MapLibre / Mapbox

Recolor a fill or line layer

Before mount
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);
After mount
// 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

JavaScript
// Toggle visibility of CLU acreage labels.
map.setLayoutProperty('clu-labels', 'visibility', 'none');

Swap the label field

JavaScript
// Show the parcel id instead of acreage at zoom 14+.
map.setLayoutProperty('clu-labels', 'text-field', [
  'concat',
  ['get', 'id'],
]);

Custom hover treatment

JavaScript
// 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

JavaScript
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

JavaScript
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.

JavaScript
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.

JavaScript
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.

JavaScript
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

JavaScript
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 helper

Highlight 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.

JavaScript
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

JavaScript
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_id in place if you customize label rendering — every adapter relies on it for tile-boundary deduplication.
  • fetchLandStyle caches in-memory per (target, layers) for the session, so repeated calls are cheap.