Skip to main content

Custom shapes

Bring your own geometry. POST a canonical FeatureCollection and the renderer draws every feature as a styled overlay on top of the base map. Use this for areas of interest, project boundaries, exclusion zones, custom ship tracks (e.g. AIS data you already have), pipelines, or anywhere the built-in regions= / markers / routes parameters don't fit.

POST-only. GeoJSON doesn't fit in a URL.

When to use which

If you have…Use
A list of ISO codes with valuesdata= / regions[]
Points you want as icons (airport, tank, factory)markers
Two endpoints + you want a great-circle or sea route between themroutes
Arbitrary polygons / polylines / styled points from your own datashapes (this page)

Examples

A polygon AOI + a dashed track across the Atlantic + a labelled point:

curl -sS -X POST 'https://api.maproll.io/render/map' \
-H 'content-type: application/json' \
--data-binary @- <<'JSON' > sample.svg
{
"scope": "world", "theme": "dark", "width": 1400,
"shapes": { "type": "FeatureCollection", "features": [
{ "type": "Feature",
"geometry": { "type": "Polygon",
"coordinates": [[[-10,50],[10,50],[10,60],[-10,60],[-10,50]]] },
"properties": { "fill": "#f59e0b", "stroke": "#92400e", "opacity": 0.5, "title": "AOI" } },
{ "type": "Feature",
"geometry": { "type": "LineString",
"coordinates": [[-74,40],[-9,38],[4,52]] },
"properties": { "stroke": "#60a5fa", "strokeWidth": 3, "dashArray": "6 4" } },
{ "type": "Feature",
"geometry": { "type": "Point", "coordinates": [-77,38] },
"properties": { "fill": "#ef4444", "radius": 8, "title": "DC" } }
]}
}
JSON

A choropleth with a project boundary on top:

curl -sS -X POST 'https://api.maproll.io/render/map' \
-H 'content-type: application/json' \
-d '{
"scope": "world", "theme": "light", "width": 1400,
"regions": [{"id":"US","value":80},{"id":"FR","value":50},{"id":"DE","value":120}],
"shapes": { "type": "FeatureCollection", "features": [{
"type": "Feature",
"geometry": { "type": "Polygon",
"coordinates": [[[10,46],[18,46],[18,55],[10,55],[10,46]]] },
"properties": { "fill": "#3b82f6", "stroke": "#1e40af", "opacity": 0.35, "title": "Project area" }
}]}
}'

The base choropleth shows through underneath where opacity < 1, and routes / markers / labels (if any) sit on top for readability.

Body schema

shapes?: {
type: "FeatureCollection";
features: Feature[];
};

Each Feature:

{
type: "Feature";
geometry: Point | LineString | MultiLineString | Polygon | MultiPolygon;
properties?: ShapeStyle | null;
id?: string | number;
}

Coordinates are GeoJSON-canonical [lon, lat]. Geometry types accepted: Point, LineString, MultiLineString, Polygon, MultiPolygon. MultiPoint and GeometryCollection are rejected.

Style properties

Per feature, on each feature's properties:

FieldTypeDefaultNotes
fillstring (#rrggbb)theme.highlightPolygon / Point fill
strokestring (#rrggbb)theme.textLine / outline color
strokeWidthnumber, 1..201Stroke width in px
opacitynumber, 0..10.7 polygon, 1 line/point
dashArraystringSVG stroke-dasharray, e.g. "6 4"
radiusnumber, 1..406Point only — circle radius in px
titlestring, ≤ 200 charsNative browser hover tooltip (<title>)

Unknown keys on properties are tolerated and ignored. Drop a properties: null if you have no styling — the feature renders with theme defaults.

Limits and behaviour

  • Up to 500 features per request (MAX_SHAPES_FEATURES).
  • 4 MB body limit (shared across the whole request body) is the implicit ceiling on total vertex count.
  • Schema-invalid input (e.g. coordinates: [999, 0], missing geometry, unsupported geometry type) returns 400 invalid_body.
  • Per-feature runtime failures (off-projection, empty path after projection) don't fail the request. The render returns 200, the offending features are skipped, and X-Geo-Warnings: invalid-shapes:<idx>:<reason>,... lists them.
  • Layer order: above the choropleth (so the base map shows through where opacity < 1); below routes / labels / markers (so they stay readable).
  • Cache key: a SHA-1 of your normalised request includes the shapes payload, so semantically-identical shape sets cache to the same ETag regardless of feature order. Change a single properties.fill and you get a new cache entry.
  • Points in shapesmarkers. Points here render as styled <circle>s. Use markers when you want an icon from the 30-icon library.

Caveats

  • Antimeridian. d3.geoPath handles polygon clipping along the antimeridian. LineStrings that cross ±180° may exhibit the same wraparound the routes parameter avoided via explicit splits — for cross-Pacific tracks, pre-split your LineString into two segments yourself.
  • Tooltips. properties.title becomes the SVG <title> element. When you embed the rendered SVG via <img> in a browser, the host browser may or may not surface SVG titles as hover labels. Embedding the SVG inline (or via <object>) is more reliable for hover.
  • Snap-to-port and other intelligent routing. Out of scope here — the shapes layer renders your geometry verbatim. Use routes with mode: sea for sea-lane routing.