Maps: Geographic Annotation with Points, Lines, Polygons and Regions
Draw labelled points, lines and polygons on a map in Potato, pin places or label regions, over a GeoJSON outline, georeferenced image, GeoTIFF or your own tiles.
New in v2.12.0
geo_annotation puts a map on the annotation page. Annotators draw labelled points, lines and polygons on it, pin places, or give labels to the regions of an outline map. The map is OpenLayers 10.9.0, which Potato serves itself, so no map library loads from the internet.
annotation_schemes:
- annotation_type: geo_annotation
name: land_cover
description: "Outline the land cover"
task: draw
tools: [polygon, freehand, point]
labels:
- {name: Water, color: "#0072B2"}
- {name: Forest, color: "#009E73"}
basemap:
geojson: maps/regions.geojsonThe Potato repository has two runnable examples. examples/advanced/dialect-map/ is a draw-a-map dialect survey on a fictional country that uses all three tasks (Perceptual Dialectology), and examples/image/geo-annotation/ outlines land cover over georeferenced images, one image per item, with a suggested polygon on the first item.
Tasks
task | What the annotator does | What is stored |
|---|---|---|
draw (default) | Draws points, lines, polygons and freehand polygons, and gives each a label, plus a typed name with feature_text | One GeoJSON feature per drawing |
pin | Places one point for each label. Each label is a question, such as "Where were you born?" | One point per label |
classify_region | Picks a label, then clicks regions of the basemap outline to give them that label. Clicking a region again with the same label clears it | One feature per labelled region, with geometry: null and the region ID |
The basemap
Every scheme needs a basemap, in basemap or per item through basemap_field, and without one potato validate and the server refuse the config. Potato does not fall back to an internet map, so a study loads no map from the internet unless its config names one.
| Source | Config | Notes |
|---|---|---|
| GeoJSON outline | basemap: {geojson: maps/regions.geojson} | A file under task_dir, embedded in the page. Keep it simplified, a few hundred KB at most, because it is part of every annotation page |
| Georeferenced image | basemap: {image: /media/area.png, extent: [min_lon, min_lat, max_lon, max_lat]} | A PNG or JPEG in plate carrée (equirectangular) projection covering extent |
| GeoTIFF or Cloud Optimized GeoTIFF | basemap: {geotiff: /media/area.tif} | Read with geotiff.js 3.0.5, which ships with Potato. The file must be in EPSG:4326 or EPSG:3857, and the browser needs WebGL |
| XYZ tiles | basemap: {tiles: "https://tiles.example.org/{z}/{x}/{y}.png", attribution: "..."} | Your own tile server or a provider you have an agreement with. attribution is required for an external URL, and {-y} is accepted for TMS numbering |
A GeoJSON outline can be combined with one raster source, and the outline is drawn on top.
The OpenStreetMap Foundation's tile servers run on donated resources, and their tile usage policy forbids heavy use. A study with many annotators panning a map is heavy use, so Potato accepts the URL with a warning in the log. Host your own tiles or use a commercial provider.
basemap_field names an item field that holds the item's basemap: a GeoJSON FeatureCollection, a string URL (.tif and .tiff are read as GeoTIFF, anything else as GeoJSON), or an object with the same keys as basemap. The server checks each item's basemap against the same rules before sending the item, and an item may name an external server only when the scheme's own basemap names a URL on that host.
For classify_region, each outline feature is a region. Its ID is the region_id_property property (default id), then the GeoJSON feature ID, then r<index>, and its name is the region_name_property property (default name).
Options
| Key | Default | Meaning |
|---|---|---|
task | draw | draw, pin or classify_region |
labels | required | Strings or {name, color, description}. Colours default to an Okabe-Ito palette. Quote Yes, No and On, which YAML otherwise loads as booleans |
tools | [point, line, polygon, freehand] | draw only. A select-and-move tool is always added |
feature_text | off | true, or {prompt, required, placeholder}: a text box per feature, stored as properties.text |
basemap | none | geojson, image, extent, geotiff, tiles, attribution, max_zoom, region_id_property, region_name_property |
basemap_field | none | Item field with a per-item basemap |
preannotation_field | none | Item field with GeoJSON features to start from (not for classify_region) |
coordinate_order | lonlat | latlon if the preannotation data or the outline writes latitude first. Output is always [lon, lat] |
projection | EPSG:3857 | Display projection, EPSG:3857 (Web Mercator) or EPSG:4326. Storage is always EPSG:4326 |
center, zoom | fit the basemap | Initial view, with center: [lon, lat] |
height | 440 | Map height in pixels, from 200 to 1200 |
snapping | false | Snaps new vertices to existing features and to the outline |
min_features, max_features | 1 if required, none | draw only. Drawing tools are disabled at the maximum |
require_all_regions | false | classify_region: every region must get a label before Next |
label_requirement | none | {required: true} blocks Next until the task's own condition holds: min_features features for draw, a pin for every label for pin, at least one region (or all) for classify_region |
Using the map
To draw, choose a label, then a tool. A point is one click. A line is a click per point and a double-click on the last. A polygon is a click per corner, finished by clicking the first corner, double-clicking or pressing Enter, with Backspace removing the last corner and Esc cancelling. Freehand is press and drag. The select tool reshapes a feature by its outline or a vertex, and Delete removes it. Every feature appears in the list under the map with a label menu, the name box, Show and Delete.
For pins, the list shows one row per label, and clicking the map places the active one. For regions, pick a label button and click regions.
From the keyboard, Tab to the map, then the arrow keys pan and + and - zoom. Add point at map centre and Use map centre place points without a mouse, and every feature's label, name and deletion are reachable through the list.
Features from preannotation_field are marked "(suggested)". They are not saved as the annotator's answer until the annotator edits one or presses Accept suggestions, so an item the annotator only opened does not count as labelled.
Stored data
The answer is a GeoJSON FeatureCollection in EPSG:4326 following RFC 7946: positions are [longitude, latitude], exterior rings run counter-clockwise and holes clockwise, coordinates have 7 decimals (about 1 cm), and longitudes are wrapped into -180 to 180.
{"type": "FeatureCollection", "features": [
{"type": "Feature", "id": "f1",
"geometry": {"type": "Polygon", "coordinates": [[[-29.0, -38.3], [-28.2, -38.3], [-28.6, -37.5], [-29.0, -38.3]]]},
"properties": {"label": "Very different", "text": "Coast talk", "tool": "polygon"}}
]}The scheme also works on survey pages, where there is no item, so basemap_field and preannotation_field do nothing there.
Export
# Every feature as GeoJSON, one file per item (geojson/<item>.geojson)
python -m potato.export --config config.yaml --format geojson --output out/
# ... or one file for the whole project
python -m potato.export --config config.yaml --format geojson --output out/ --option layout=single
# Heat maps of overlapping polygons, plus CSVs of names and region labels
python -m potato.export --config config.yaml --format geo_overlap --output out/The GeoJSON export normalises every feature again and adds item_id, user_id, schema and feature_id to its properties. Features that cannot be normalised, such as a position out of range or a ring with no area, are left out and listed in the export warnings. classify_region answers get their region geometry back from the outline. geo_overlap is described in Perceptual Dialectology.
Limits
- A polygon that crosses the 180° meridian is not split, so draw two polygons.
- A GeoTIFF in a projection other than EPSG:4326 or EPSG:3857 does not show, and neither does any GeoTIFF without WebGL. The page says so, and the outline still works.
- A GeoTIFF is drawn without interpolation, so zooming in shows the real pixel boundaries.
- For pixel coordinates on an ordinary image rather than geographic ones, use image annotation.
Further Reading
- Perceptual Dialectology - Draw-a-map dialect surveys and overlap heat maps
- Camera Traps - Detector review with Darwin Core occurrence export
- Air-Gapped Annotation - Running Potato with no internet access
For implementation details, see the source documentation.