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 values | data= / regions[] |
| Points you want as icons (airport, tank, factory) | markers |
| Two endpoints + you want a great-circle or sea route between them | routes |
| Arbitrary polygons / polylines / styled points from your own data | shapes (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:
| Field | Type | Default | Notes |
|---|---|---|---|
fill | string (#rrggbb) | theme.highlight | Polygon / Point fill |
stroke | string (#rrggbb) | theme.text | Line / outline color |
strokeWidth | number, 1..20 | 1 | Stroke width in px |
opacity | number, 0..1 | 0.7 polygon, 1 line/point | |
dashArray | string | — | SVG stroke-dasharray, e.g. "6 4" |
radius | number, 1..40 | 6 | Point only — circle radius in px |
title | string, ≤ 200 chars | — | Native 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) returns400 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.filland you get a new cache entry. - Points in
shapes≠markers. Points here render as styled<circle>s. Usemarkerswhen you want an icon from the 30-icon library.
Caveats
- Antimeridian.
d3.geoPathhandles polygon clipping along the antimeridian. LineStrings that cross ±180° may exhibit the same wraparound theroutesparameter avoided via explicit splits — for cross-Pacific tracks, pre-split your LineString into two segments yourself. - Tooltips.
properties.titlebecomes 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
routeswithmode: seafor sea-lane routing.
Related
POST /render/map— the JSON body schema- Markers — icons at lat/lon
- Routes — great-circle and sea-lane paths
- Annotations — hover text on regions