Ingest places
Status: Proposed 2026-09-29, farmers markets added to the OSM step 2026-10-24
Three scripts feed places: ingest:places:osm, ingest:places:gardens, and ingest:sanctuaries. All three POST to POST /api/ingest/places under the contract. Places are the safest resource to automate because a place is a business or a field, never a person. The first import and its history are on the data sources page.
Item shape
| Field | Required | From |
|---|---|---|
sourceId | yes | osmId (node/123, way/456, relation/789) or the curated key |
name, type, veganLevel | yes | see the mapping below |
location { lng, lat } | yes | the node itself, center for ways and relations, or geocoded |
address, city, postcode | addr:housenumber plus addr:street, addr:city, addr:postcode | |
website, phone, hours | website or contact:website, phone or contact:phone, opening_hours | |
tags[] | see richer tags | |
description | source text, else generated | |
chain | true when brand or brand:wikidata is set | |
sourceUrl | https://www.openstreetmap.org/ followed by the osmId |
area is not sent. The API derives it from coordinates through a table of bounding boxes per home area, and city falls back to that box's name when addr:city is missing. A free-text city never decides the area.
OpenStreetMap
Query the Overpass mirror inside the Southern California bounding box with out center tags, sending a descriptive User-Agent. Two clauses share that tiled fetch: nwr["diet:vegan"~"^(yes|only)$"], and named nwr["amenity"="marketplace"]. The same mirror fallback and the same stop after consecutive failed tiles cover both.
| OSM tags | type | veganLevel |
|---|---|---|
diet:vegan=only | by amenity or shop | full |
diet:vegan=yes | by amenity or shop | options |
amenity=restaurant, amenity=food_court | restaurant | |
amenity=fast_food with diet:vegan=only | restaurant | full |
amenity=fast_food with diet:vegan=yes | skipped | |
amenity=cafe, amenity=ice_cream | cafe | |
amenity=bar, amenity=pub | venue | |
shop=supermarket, greengrocer, health_food, convenience | grocery | |
named farmers or produce amenity=marketplace | grocery, tag market:farmers, chain false | options, or full when diet:vegan=only |
any other shop=* | shop | |
| anything else, including flea, craft, and antique markets | skipped and counted |
A marketplace counts as farmers or produce when marketplace or marketplace:type is a farmers or produce value, or when the name reads as a farmers market, produce market, greenmarket, or farm stand. A cafe that is also tagged diet:vegan stays a cafe: the diet mapping runs when the feature is not a farmers market. The schedule is the nightly OSM step on the automations page, 12:42 AM Pacific. Farmers markets are inside that step. Six tiles in a row failing on both Overpass mirrors abort the run; the next night retries.
The fast-food rule in one line: a fast-food place with a vegan option is not worth a pin, a fully vegan one is.
Chains: brand or brand:wikidata present sets chain: true, except on a farmers market, where chain stays false. The public API hides chains unless the caller asks for them (includeChains=true) and never deletes them, because a fully vegan chain is still useful.
Richer tags go into tags[] as key:value strings, so the client can filter without a schema change: cuisine:thai (one tag per value of a semicolon-separated cuisine), wheelchair:yes, outdoor_seating:yes, takeaway:yes, delivery:yes. Only the values yes, no, and limited are kept for the last four.
Description: when OSM has no description, generate one sentence from what it does have, level first, then type, city, cuisine: "Fully vegan cafe in Long Beach. Cuisine: thai, vegan." Never invent hours or claims.
Community gardens
ingest:places:gardens queries leisure=garden with garden:type=community, plus landuse=allotments that carry a name. Every hit becomes type: garden, veganLevel: full, with website and opening_hours when present and a generated description ("Community garden in San Pedro."). Unnamed allotments are skipped: a pin without a name helps nobody. Gardens matter because growing food is the quietest form of outreach a Grove can host.
Curated sanctuaries
ingest:sanctuaries reads scripts/data/sanctuaries.json in the API repo. Each entry is hand-verified from the sanctuary's own site: name, address, website, a description in the app's own words, tags such as tours and volunteering. It is sent with source: curated, a stable kebab-case sourceId (farm-sanctuary-socal), type: sanctuary, veganLevel: full.
Entries without coordinates are geocoded with Nominatim at no more than one request per second with a descriptive User-Agent, and the result is written back into the JSON so the lookup runs once. Adding a sanctuary is a pull request to that file with the source page linked in the PR.
Cadence and attribution
| Script | Cadence | Notes |
|---|---|---|
ingest:places:osm | nightly, 12:42 AM Pacific | --dry-run, read the counts, then run. Includes farmers and produce markets. An Overpass outage (six tiles in a row failing on both mirrors) aborts the run for the next night |
ingest:places:gardens | nightly | same run as OSM |
ingest:sanctuaries | when the JSON changes | small, safe to run any time |
OpenStreetMap data is ODbL. A place with source: osm shows "Data from OpenStreetMap contributors" on its page, and the map tiles carry the OSM attribution in the style. Nominatim results are also ODbL and are used only to place a curated pin.