INVEKOS Schlaege API (Austria, AMA INSPIRE Schlaege, all years available on data.gv.at) =========================================================================================== Base: https://farm-subsidies-austria.exe.xyz (public port 8000; this file: /api/v1/invekos/docs/llm.txt) Read-only, no key. Every endpoint below is under /api/v1/invekos/. Start with /years, then /point or /kg/, then /history, /lineage, /quality for one Schlag / one area. All responses gzip when Accept-Encoding: gzip. Errors: JSON {"error": ...} with 400/404/413/503. Years loaded: [2022, 2023, 2024, 2025, 2026] releases: {"2022": "2022-2", "2023": "2023-2", "2024": "2024-2", "2025": "2025-2", "2026": "2026-1"} Source: https://inspire.lfrz.gv.at/009501/ds/inspire_schlaege__polygon.gpkg.zip (+ inspire_referenzen_) (-2 = final January release of the year, 2026-1 = current). Native CRS EPSG:31287, stored pre-transformed in EPSG:3035, coordinates snapped to 0.1 m, make_valid at import. Storage: one SQLite DB, geometries de-duplicated across years by hash — a Schlag whose outline did not change is stored once; unchanged gid across years == identical geometry. CORE (spec invekos_specs.md) ---------------------------- GET /api/v1/invekos/years {"years":[2022,...],"default":2026,"releases":{...},"crs_native":"EPSG:31287", "per_year":{"2025":{n_features,n_invalid_fixed,n_dropped,n_new_geoms,n_shared_geoms,area_ha}}, "unique_geometries":N,"hofstellen_per_year":{...},"change_pairs":[...]} GET /api/v1/invekos/schlaege?bbox=minx,miny,maxx,maxy[&crs=3035|31287][&year=|&nearest_to=] [&fields=snar,snar_code,area_ha,fs_kennung,geo_id,bio,ts,gid,betr_id,betr_dist_m,nearest_betr_id] [&format=geojson|wkb][&simplify=][&clip=1] Whole polygons intersecting bbox (not clipped unless clip=1). bbox max 25 km per side (400), max 50 000 features (413 -> split). Default fields snar,snar_code. nearest_to= picks the closest loaded year (ties -> earlier). Headers: X-Invekos-Year, X-Invekos-Release, X-Invekos-N, X-Elapsed-Ms. GeoJSON: {"type":"FeatureCollection","year":2024,"release":"2024-2","crs":"EPSG:3035", "n":1834,"clipped":false,"features":[{"geometry":...,"properties":{"snar":"WEIN","snar_code":901}}]} format=wkb: uint32 n, then per feature uint16 snar_code, uint32 wkb_len, wkb (little-endian). Extra fields: ts = GEOM_DATE_CREATED (ISO, second precision; same-second groups = same MFA session/farm), gid = geometry identity (same gid in another year => identical outline), geo_id = stable Schlag id across years, fs_kennung = Feldstueck (renumbered yearly), bio, betr_id = EXPERIMENTAL Hofstelle assignment of the Feldstueck by export-sequence model (see FARMS below — only ~30 % reproducible across years), betr_dist_m = FS centroid -> that Hofstelle, nearest_betr_id = plain nearest Hofstelle (95 % reproducible, but not ownership). Example: /api/v1/invekos/schlaege?bbox=4680000,2645000,4690000,2655000&nearest_to=2019 GET /api/v1/invekos/point?x=&y=[&crs=3035|31287] | ?lon=&lat=&crs=4326 [&year=][&geometry=0] The Schlag containing a point, for every loaded year: snar, class, area_ha, fs_kennung, bio, ts, gid, geometry (once per distinct gid). "What is farmed here and since when" in one call. GET /api/v1/invekos/kg/[?same params] Same as /schlaege with bbox = Katastralgemeinde bbox (7,850 KGs loaded from the BEV KG register via umfeld-at API). Adds "kg_code","bbox_3035" to the response. GET /api/v1/invekos/kgs[?bbox=] KG list with bboxes (EPSG:3035). GET /api/v1/invekos/snar_codes?year= [{"snar_code":901,"snar":"WEIN","n":48211,"area_ha":41200.5},...] full vocabulary per year. GET /api/v1/invekos/stats[?bbox=|kg=][&year=][&clip=1] Per snar_code n / area_ha (national when no bbox). clip=1 adds area_ha_clipped (geometry ∩ bbox). n_bio per code within bbox. CHANGE TRACKING - two layers, use LINEAGE unless you need the raw AMA id semantics -------------------------------------------------------------------------------------- (1) LINEAGE = geometry-based, independent of GEO_ID (works across the 2022->2023 renumbering). method gid: identical outline (same geometry hash) | near: IoU >= 0.99 (same outline within the 0.1 m snapping quantum - AMA re-exported every polygon 2022->2023 with sub-decimetre shifts, so the hash differs) | overlap: polygon intersection between the residual sets, kept when >= 5 % of either polygon. Per Schlag and pair a kind: same identical or near outline modified best IoU >= 0.5 split_merged >= 2 links covering >= 50 % partial 0 < covered < 0.5 gone (earlier year, no overlap at all) / new (later year, no overlap at all) National shares (from_year side): 2023->24 same 88 %, modified 7.7 %, split 0.6 %, partial 2.8 %, gone 0.8 % (2025->26: 90 / 5.8 / 0.5 / 2.5 / 0.7); 2022->23 (CAP reform + re-export): same 68 % (31 % bit-identical + 37 % near), modified 18 %, split 5.4 %, partial 7.1 %, gone 1.5 %. GET /api/v1/invekos/lineage?bbox=|kg=[&from=&to=][&kind=modified,split_merged,partial,new,gone] [&links=1][&geometry=0][&crs=] GeoJSON, one feature per later-year Schlag (kind != same by default) plus earlier-year Schlaege that vanished (kind=gone, geometry of the earlier year). Properties: geo_id, year, kind, n_links, max_iou, covered, snar, area_ha, gid; links=1 adds [{geo_id, method, iou, share_own, share_other, inter_ha}] of the linked Schlaege in the other year. GET /api/v1/invekos/lineage/summary[?bbox=|kg=][&exact=1] Per pair: from_year {same|modified|split_merged|partial|gone: n, area_ha, mean_iou, n_class_changed} and to_year {...|new}. National = precomputed; n_class_changed = land-use class (acker|gruenland|alm|wein|gloez|dauerkultur) of the best-IoU link differs. bbox = 2 km grid cells whose centre lies inside (ms); exact=1 = intersecting Schlaege (~2 s). GET /api/v1/invekos/history/[?geometry=0] One geo_id through the years it exists: snar, area, fs_kennung, bio, ts, gid, geometry once per distinct gid; "changes" (raw id-keyed) and "lineage": per pair predecessors/successors with kind, max_iou, covered and the linked geo_ids -> follow the chain across redraws and across 2022->2023. (2) RAW id-keyed change (kept for AMA-id semantics; void for 2022->2023, see caveats) kind: same (geo_id kept, gid unchanged) | geom (never occurs, see caveats) | gone (geo_id only in earlier year) | new (only in later year). GET /api/v1/invekos/changes?bbox=[&from=&to=][&kind=geom,gone,new][&geometry=0] GET /api/v1/invekos/changes/summary[?bbox=|kg=] counts/ha/n_snar_changed per kind, per pair. DIGITISATION QUALITY - can this polygon and its class be trusted? ------------------------------------------------------------------- Schlaege are drawn by farmers in the AMA MFA app; a share of them are slivers, verges drawn as fields, or outlines redrawn every year. Two families of indicators, precomputed per geometry: geometry compact = 4*pi*A/P^2 (1 circle, < 0.10 = sliver/spike), minwidth_m = 2*A/P (< 6 m = strip: road/verge/hedge), n_vert, area_ratio = SL_FLAECHE_BRUTTO_HA / polygon ha (|ratio-1| > 0.05 = attribute and drawing disagree) history outline_years / outline_first_year (consecutive years the outline is unchanged, identical or near, following the lineage chain; >= 3 = stable; hash_identical_years = bit-identical only), prev_kind (lineage kind vs previous year), prev_class + class_flip (best predecessor had another land-use class) reliability heuristic 0..1: 1 - 0.35 sliver - 0.25 thin - 0.15 area_mismatch - 0.15 outline seen one year only - 0.10 prev_kind partial|split - 0.20 class_flip. Use the components for your own thresholds; the score is a convenience. GET /api/v1/invekos/quality?bbox=|kg=[&year=][&min_reliability=][&max_reliability=][&geometry=0] GeoJSON per Schlag with all indicators + flags [sliver, thin, area_mismatch, new_outline, class_flip] and flag_counts for the bbox. max_reliability=0.6 -> only the dubious ones. GET /api/v1/invekos/quality/summary[?bbox=|kg=] Per year: share.sliver/thin/area_mismatch/new_outline/redrawn_prev/split_prev/ class_flip_prev/stable3, mean_compact, n, area_ha. National or 2 km grid cells in bbox. GET /api/v1/invekos/quality/grid[?bbox=][&year=] the 2 km cells themselves (reliability maps). Typical use (srtm-lidar-at style consumer): weight or drop Schlaege with reliability < 0.6 before deriving slope/land-use statistics; treat class_flip=true as "class unknown"; expect GLOEZ/LSE codes (hedges, verges) to be thin by design - filter by class, not only by width. FARM LINKAGE HELPERS (payments <-> INVEKOS chain, see AGENTS.md) ----------------------------------------------------------------------------- GET /api/v1/invekos/fs/?year= all Schlaege of one Feldstueck (all fields). GET /api/v1/invekos/timestamp_groups?bbox=[&year=][&min_n=2] Schlaege grouped by GEOM_DATE_CREATED second: n, area_ha, fs_kennungs, geo_ids, bio_share, centroid_3035, max_spread_m. Apply your own distance guard (<3 km) for farm clusters. GET /api/v1/invekos/hofstellen?bbox=[&year=][&crs=] farmstead points with betr_id (stable farm id). GET /api/v1/invekos/hofstelle/ a farmstead's coordinates per year (+farm_years, names). GET /api/v1/invekos/hofstellen/changes?bbox=[&from=&to=][&kind=moved,gone,new] kept/moved/gone/new points. GET /api/v1/invekos/fs//lineage?year= FS block followed across years via identical geometries (gid); FS_KENNUNG is renumbered yearly. WGS84 VIEWPORT LAYERS (Siedler Oesterreich contract; public as /api/schlaege and /api/hofstellen on port 8000) GET /api/v1/invekos/siedler/schlaege?west&south&east&north[&limit=3000<=5000][&tolerance_m=0][&year] {fields:[{id (GEO_ID), snar_code, snar_name, crop_group, area_ha, organic, geometry(WGS84)}], year, count, truncated, ready}. crop_group: getreide|mais|gruenland|wein|obst|alm|brache|sonst (keyword map over snar_name + snar_class). Default year = latest -2 release (bio known). Max 0.25 deg/side; truncation keeps the largest fields. GET /api/v1/invekos/siedler/hofstellen?west&south&east&north {points:[{id (salted sha256 of betr_id, stable, not reversible), lon, lat, organic?, size_class s|m|l (<10 / <50 / >=50 ha, EXPERIMENTAL farm_year)}], year}. REFERENZFLAECHEN (HEIM / ALM23 / HW23 / FO) - the farm anchor layer -------------------------------------------------------------------- AMA reference polygons (inspire_referenzen_), years [2022, 2023, 2024, 2025, 2026]. Imported: HEIM (Heimgut = the home block of one farm), ALM23 (Alm), HW23 (Hutweide), FO (Forst); LSE landscape elements are not imported. Same storage as Schlaege (EPSG:3035, 0.1 m, geometry de-dup by hash after vertex-order normalisation: a re-exported outline with rotated/reversed vertex sequence keeps its gid). ref_kennung (12 digits, stable across years: 96.8 % of 2022 kennungen exist in 2025) = AA KKKKK NNNNN: AA art code (11 ALM23, 16 HW23, 20 HEIM, 25 FO), KKKKK = Katastralgemeinde (KG) code, NNNNN = running serial per (art, KG). It carries NO operator/association identity. Links (precomputed per year): schlag_ref = Schlag -> Referenzflaeche containing its representative point; hof_ref = Hofstelle -> nearest HEIM within 500 m (dist_m, 0 = inside; Hofstellen usually sit on the farmyard just outside the HEIM polygon, so dist_m 5-50 m is normal). GET /api/v1/invekos/referenzen?bbox=|kg= or /referenzen/kg/ [&year=][&art=HEIM,ALM23,HW23,FO][&links=1][&geometry=0][&simplify=][&clip=1][&crs=] GeoJSON: ref_kennung, art, area_ha, kg_code, daterf (creation), gid; links=1 adds n_schlaege, schlag_area_ha and (HEIM) hofstellen [{betr_id, dist_m}]. GET /api/v1/invekos/referenz/[?geometry=0][&schlaege=0] One polygon through the years: art, area, geometry once per gid, schlaege inside it (geo_id, snar, class, area_ha, bio, fs_kennung), schlag_area_by_class, fs_kennungs, hofstellen anchored (HEIM). GET /api/v1/invekos/farm/[?geometry=1] The farm unit: Hofstelle -> nearest HEIM -> Schlaege inside it, per year: heim {ref_kennung, dist_m, area_ha, shared_with_betr_ids}, n_schlaege, schlag_area_ha, schlag_area_by_class, bio_share, fs_kennungs, timestamps (GEOM_DATE_CREATED seconds = MFA sessions), schlaege[]. Covers the home block only; Almen and leased blocks away from the Hofstelle are not linked to a betr_id (Alm operators = Agrargemeinschaften, no id in the open data). CAVEAT: a HEIM is a physical reference block, not a farm. 2025: 107,062 of 107,985 Hofstellen anchor to a HEIM (64 % within 25 m, 95 % within 100 m), but only 65,456 HEIMs hold exactly one Hofstelle; 16 k HEIMs hold 2-5+ (village cores: neighbours share the block). Check heim.shared_with_betr_ids before attributing the block's Schlaege to one betr_id. ~8 % of Schlaege (230 k of 2.95 M in 2025) lie in no imported Referenzflaeche (LSE, or outside HEIM/ALM/HW/FO). GET /api/v1/invekos/referenzen/summary per year x art: n, area_ha, n_schlaege, n_hofstellen. FARMS = betr_id PER FELDSTUECK — EXPERIMENTAL, VALIDATED NEGATIVE AS A FARM KEY ------------------------------------------------------------------------------ Idea: FS_KENNUNG is not random — the AMA export writes Feldstuecke in a spatially ordered sequence (consecutive FS are 0.5 km apart, random pairs 130 km) and a POC suggested farm-by-farm order in Betriebsnummer sequence. betr_build.py runs a sparse Viterbi along the FS sequence (states = Hofstellen + NULL, emission = FS -> Hofstelle distance, switch penalties, 300 nearest Hofstellen <= 15 km) for [2022, 2023, 2024, 2025, 2026]. Per year it looks good: 99.6 % assigned, FS -> Hofstelle median 0.5-0.65 km, p99 3.3 km, ~90 k farms, bio homogeneity 0.88-0.92 vs 0.82 shuffled. CROSS-YEAR IT FAILS: for Feldstuecke with identical geometry in two years (1.3 M per pair) the assigned betr_id agrees only 27-31 % (nearest Hofstelle: 95 %); a 2024 "farm" splits into a median 5 farms in 2025; farm non-alm area within 20 % year-over-year for only 19-23 % of farms (real farms, from payments: 90 % within 10 %). Grouping purity 0.48 vs 0.38 for naive sequence windows vs 0.125 random: the sequence carries neighbourhood, not farm identity. Also negative: farm brutto area == recipient BISS eligible ha within 0.1 % in the same Gemeinde for 7 % (shuffled 3.6 %). Treat betr_id/farm_year as a neighbourhood label only; nearest_betr_id is the stable one. GET /api/v1/invekos/farms?bbox=[&year=][&crs=] GeoJSON points = Hofstellen with whole-farm aggregates for the year: n_fs, n_schlaege, area_ha, alm_ha, bio_ha, max_fs_dist_m, plus name/name_dist_m = nearest BEV farmstead/settlement name. GET /api/v1/invekos/farm/[?geometry=1][&schlaege=1] adds "fs_assignment": per year n_fs, area_ha, alm_ha, bio_ha, max_fs_dist_m, feldstuecke[] {fs_kennung, dist_m, n_schlaege, area_ha, alm_ha, nearest_betr_id}; schlaege=1 lists the Schlaege of the assigned FS with class and area_by_class. "names" = BEV toponyms at the Hofstelle. (The older "years" block = HEIM home-block view is kept; the fs_assignment is the whole farm.) GET /api/v1/invekos/hofstelle/ now also carries farm_years[] and names{year: [...]}. GET /api/v1/invekos/fs/?year= includes betr_id / betr_dist_m. /point: every Schlag carries betr_id, betr_dist_m. TOPONYMS (BEV DLM-Namen, CC BY 4.0) — mirrored from umfeld-at /api/v1/toponyms (#17) ----------------------------------------------------------------------------------------------- 233 k official geographic names as points (±50 m): siedlung (58 k, f_code 7111 Einzelhaeuser = farmstead/Alm-hut names 33 k), ried (104 k cadastral field names keyed by KG), gebiet (Almen, Auen), gelaende (peaks, valleys), gewaesser, gletscher, sonstige (huts, chapels). Stored in data/invekos/toponym.db (refresh: python3 -m invekos_api.toponym_build), stichtag 2025-03-25. Links: hof_toponym = nearest 3 siedlung points <= 250 m of each Hofstelle per year (57.5 % of Hofstellen have one; f_code 7111 = the farm's own name, 7103 Dorf = the village); ref_toponym = toponyms inside / <= 150 m of every HEIM and ALM23 polygon (Alm names!). GET /api/v1/invekos/toponyms?bbox=|kg=[&layer=siedlung,ried,…][&f_code=7111][&q=][&crs=] GeoJSON points with id (BEV GLOBALID), layer, f_code, f_name, name, variant_name, kg_code, gemeinde_code/name, abandoned, objektart. Max 5000. Fuzzy national name search: use the umfeld-at API /api/v1/toponyms/search?q= directly (trigram, stem stripping, gemeinde/district filters). /referenz/ (HEIM, ALM23) carries "toponyms" [{name, layer, f_code, dist_m, inside}]. /point carries "toponyms_nearby" (<= toponym_radius, default 500 m, max 5000, 20 nearest). /farms and /farm/ carry the farmstead name(s). /point also returns "referenzen" (polygons containing the point per year); /hofstelle/ returns heim_by_referenz_year. TRENDS / STATISTICS ------------------- GET /api/v1/invekos/trends[?bbox=|kg=][&exact=1] Per year n / ha / bio ha by class (acker, gruenland, alm, wein, gloez, dauerkultur), digitised_cycle_share, plus per pair: "lineage" (geometry-based kinds), "changes" (raw id-keyed), "hofstellen" kept/moved/gone/new. National precomputed; bbox via 2 km grid cells (exact=1 recomputes from intersecting Schlaege). GET /api/v1/invekos/grid?year=[&bbox=][&class=] 2 km cells with per-class n/ha/bio/digitised. GET /api/v1/invekos/snar_codes/diff?from=&to= vocabulary added/removed/relabelled between years. GET /api/v1/invekos/kgs[?bbox=] KG codes/names/Gemeinde/Bundesland + bboxes. GET /api/v1/invekos/health, /license H3 CELLS (one byte per attribute per H3 res-12 cell; for browsers, CORS *, no key, no polygons) -------------------------------------------------------------------------------------------- Built for umfeld-at's NE glyph (same H3 res-12 ids as umfeld-at /ne and srtm-lidar-at /cells — join by cell id, client-side). Fetch straight from the browser; nothing to store on your side. GET /api/v1/invekos/h3?bbox=w,s,e,n&crs=4326&res=12[&year=][&format=columns|bin] GET /api/v1/invekos/h3?res=7[&year=][&bbox=] national overview (precomputed) GET /api/v1/invekos/h3/meta codes, grid, years, cache state Cells: H3 res-12 cells whose CENTRE lies in bbox (half-open: w <= lon < e, s <= lat < n, so adjacent tiles never share a cell) and that intersect >= 1 Schlag of `year` OR of prev_year (gone cells appear with cov 0, chg 5). Sorted by cell index. bbox span <= 0.05 deg per axis, else 413 {error, split:[4 sub-boxes]}. Default year = latest final (-2) release (2025 now; the summer -1 release has no bio flag). prev_year = the loaded year before `year`. format=columns (gzip JSON, default): {"res":12,"year":2025,"release":"2025-2","prev_year":2024,"n":N, "cell":["8c1e10164c899ff",...], "cls":[u8], "cov":[u8], "bio":[u8], "rel":[u8], "chg":[u8], "dcov":[i8]} format=bin (gzip, application/octet-stream): little-endian u32 n, n x u64 cell (H3 index as integer), then the six byte columns in the order cls, cov, bio, rel, chg, dcov (dcov is int8). n x 15 B raw; a 0.025 deg farmland tile (6-17 k cells) is 7-30 KB gz. Columns (all per cell, 0..255): cls dominant land-use class by area: 0 none (cov 0), 1 acker, 2 gruenland, 3 alm, 4 wein, 5 dauerkultur, 6 gloez (GLOEZ/LSE hedges, verges, trees), 7 sonst (code without class) cov Schlag coverage of the cell * 255 (sum of Schlag ∩ cell / cell area; overlaps clamp at 255) bio bio share of the covered area * 255, CAPPED AT 254 (a 100 % bio cell reads 254, never 255 — do not render 254 as "almost all"); 255 = unknown (summer release, or cov 0 / gone cell) rel area-weighted mean of the /quality reliability heuristic * 255 (sliver, thin, outline age, predecessor kind, class flip — hatch cells < 150 as dubious hand-drawn) chg lineage kind of the DOMINANT Schlag of the cell vs prev_year: 0 same (identical or IoU >= 0.99 outline), 1 modified (IoU 0.5-0.99), 2 split_merged, 3 partial, 4 new (no predecessor overlap), 5 gone (cell had a Schlag in prev_year, none in year). 0 everywhere for the first loaded year. Note: lineage records DIGITISATION, not tenure — a field that changed hands with the same outline is 0 (see caveats). dcov cov(year) - cov(prev_year), clamped to int8 (-128..127); 0 for the first loaded year. Method: each Schlag (clipped to the tile + 45 m) is filled with the res-14 cells whose centre it contains (49 per res-12 cell), the res-12 parent is the key; so cov has ~2 % resolution and a cell is "covered" when a res-14 centre is inside — slivers < 3 m wide may register 0 (gloez). Tiles are 0.025 deg lon/lat-aligned (umfeld-at lod=0; w = i*0.025). An aligned bbox + format=bin is the hot path: the stored body passes straight through (~2 ms server, ~5 ms via the proxy). Any other bbox is cut from the covering (<= 2x2) tiles by cell centre. First request for a tile computes it (0.3-1.5 s, once per release) and stores it (data/invekos/h3_tiles.db, size-capped; tiles are immutable per release). Warm a region: python3 -m invekos_api.h3cells warm 2025 w s e n Warm/cold: a stored tile is ~2 ms server / ~5 ms via the proxy; an UNBUILT tile is 0.3-1.5 s (3-4 s for a 2x2 unaligned bbox of unbuilt tiles). Every farmland tile of the DEFAULT year is pre-built after each import (python3 -m invekos_api.h3cells warm-all; ~12 k tiles, ~160 MB), so default-year requests are always warm; other years are built on first request (set a >= 5 s timeout for year= variants, or warm them yourself with a sweep). Check /h3/meta tiles_cached_by_release. res=7: one row per H3 res-7 cell of Austria with >= 1 Schlag (~16 k), ~70 KB gz, bbox= filters by centre. SEMANTICS DIFFER from res 12 — area-weighted from Schlag bbox centres: cls dominant class by area; cls2 second class by area (0 if none); shr dominant class share of the cell's Schlag area * 255 (so the legend can show cls/cls2 honestly at ~5 km2 per cell) chg NOT a kind code: share of the cell's Schlag area with a lineage event vs prev_year * 255 (modified + split_merged + partial + new Schlaege of `year`, plus gone Schlaege of prev_year, over the union area). 0 for the first loaded year. rel = 0 (n/a). bin layout = the six standard columns, then cls2 (u8), shr (u8) appended; X-Invekos-Cols and the JSON "cols" list give the order. A res-12 parser that reads six columns by offset still works. Rebuild after an import: python3 -m invekos_api.h3cells overview Headers: Access-Control-Allow-Origin *, Access-Control-Expose-Headers (all X-*, ETag), Cache-Control public max-age=86400, ETag (strong, keyed on release + prev release + semantics version + request shape — send If-None-Match, get 304 with the same X-* headers; the tag changes only when a new release is imported or H3 semantics are bumped), Content-Encoding gzip, X-Invekos-Year, X-Invekos-Release, X-Invekos-Prev-Year / X-Invekos-Prev-Release (the baseline of chg/dcov — "none" for the first loaded year; do not assume year-1), X-Invekos-N, X-Invekos-Cols, X-Invekos-Version, X-Data-Attribution (ASCII), X-Elapsed-Ms. Attribution to show: "INVEKOS Schlaege (c) AMA / BML (CC BY 4.0), per H3 cell by farm-subsidies-austria.exe.xyz" Example: /api/v1/invekos/h3?bbox=15.1,47.05,15.125,47.075&crs=4326&res=12&format=bin POLY / IVP1 (full-resolution Schlag outlines per 0.025-deg bin; for the umfeld-at outline glyph, LOD 1) ------------------------------------------------------------------------------------------------------- GET /api/v1/invekos/poly?bbox=w,s,e,n&crs=4326&format=bin bbox MUST be one aligned bin (w = i*0.025, s = j*0.025, e = w+0.025, n = s+0.025; else 400). Default year only (latest final -2 release; prev_year = the loaded year before it). No ids, no names, no farm attributes - pictures, sums and one cell's reading only; never re-serve polygons. Content: every Schlag of the year whose outline INTERSECTS the bin, UNCLIPPED (clip client-side when rasterising) - except outlines whose bbox exceeds 2 bins in either axis (19 giant Almen nationally), which are clipped to the bin +- 10 % and flagged (flags bit0). Plus the `gone` Schlaege of prev_year (chg 5, area 0, d_area = -prev area) so their outline can be drawn. Empty bin -> 200 with n = 0. Geometry: 0.5 m Douglas-Peucker in EPSG:3035 (topology not preserved), then bin-local integer units: origin = bin SW corner, x east, y north, 1 unit = 0.025 deg / 4096 (~0.46 m E-W, ~0.68 m N-S); signed, points outside 0..4095 are normal (unclipped outlines). Rings with < 3 points are dropped; holes follow the outer ring. Layout (little-endian; body is gzip via Content-Encoding): "IVP1" u32 n then per polygon: u8 cls 0 none 1 acker 2 gruenland 3 alm 4 wein 5 dauerkultur 6 gloez 7 sonst u8 bio 0 no, 1 yes, 255 unknown u8 rel reliability x 255 (same heuristic as /quality and the h3 rel column; < 150 dubious) u8 chg 0 same 1 modified 2 split_merged 3 partial 4 new 5 gone (vs X-Invekos-Prev-Year) u8 flags bit0 clipped to bin +- 10 %; bit1 continuation part of a multi-part outline (see below) uvarint area_m2 SL_FLAECHE_BRUTTO_HA x 1e4 of this year (0 for gone and for bit1 parts) svarint d_area_m2 area_m2 - area_m2 of the best-linked (max IoU) predecessor in prev_year; 0 when there is no lineage link (new); for gone records = -(prev area) uvarint nrings; per ring: uvarint npts, npts x (svarint dx, svarint dy) - zigzag deltas, first point relative to (0,0), ring NOT closed, outer ring first varint = LEB128; svarint = zigzag ((v << 1) ^ (v >> 63)) then LEB128. FORMAT NOTES (additive to the requested spec): 1. flags bit1: multi-part outlines (make_valid of an invalid source polygon, or a clipped giant) are emitted as one record per part. The largest part carries area_m2/d_area_m2; the other parts repeat cls/bio/rel/chg with flags bit1 set and area_m2 = d_area_m2 = 0 - so summing area_m2 over ALL records stays exact and a renderer may ignore bit1 entirely. 2. d_area_m2 is defined against the single best-linked predecessor (max IoU in `lineage`), not the sum of all predecessors - for split_merged/partial it is therefore a partial delta; gone records carry -(prev-year area_m2) even though their own area_m2 is 0. Round trip vs the stored outline (0.5 m simplification + 0.5 m quantisation): IoU median 0.99, min ~0.5 on GLOEZ slivers; area_m2 attribute vs polygon area < 0.3 % median. Size/latency (measured, default year): farmland bins 1-40 KB gz (p50 ~11 KB, p95 ~75 KB), densest Marchfeld bins (1.4 k polygons) 33-37 KB, vertex-heaviest Alm bins 130-183 KB - the national maximum; never near 4 MB. Stored per (release, bin) in data/invekos/poly_tiles.db (size-capped, immutable per release): first request 0.05-0.9 s (2.3 s for the densest bin disk-cold), stored bin ~5 ms via the proxy. Headers: ETag (strong: "ivp1----"; If-None-Match -> 304, also with the proxy's W/"...:gzip" re-tag), Cache-Control public max-age=86400, Content-Encoding gzip, X-Invekos-Year / -Release / -Prev-Year / -Prev-Release / -N (records) / -Format IVP1 / -Version, X-Data-Attribution (ASCII), X-Elapsed-Ms, CORS * with all of them exposed. Reference decoder: invekos_api.poly.decode_body. Attribution to show: "INVEKOS Schlaege (c) AMA / BML (CC BY 4.0), simplified 0.5 m by farm-subsidies-austria.exe.xyz" Example: /api/v1/invekos/poly?bbox=16.4,47.95,16.425,47.975&crs=4326&format=bin PERFORMANCE / LIMITS -------------------- 10x10 km, one year, default fields: typically 100-700 ms, ~1 MB gz. National summaries (/years, /trends, /changes/summary, /lineage/summary, /quality/summary) are precomputed and answer in < 50 ms; bbox/kg variants of them 0.3-1.5 s. /point < 100 ms. 25 concurrent peers OK (gunicorn workers behind the port-8000 proxy). Never returns a truncated list: 413 asks you to split. Use geometry=0 when you only need the numbers; simplify= for maps. DATA CAVEATS (measured, see AGENTS.md / invekos_api/README.md) -------------------------------------------------------------- * bio flag: AMA fills KZ_BIO_OEPUL_JN only in the winter release (-2). Summer releases (2026-1 until 2026-2 appears) have bio = null for every Schlag. Bio-Teilbetriebe of a Mutterbetrieb are flagged J like the main holding (AMA metadata). * GEO_ID is NOT a stable field id. (a) It was renumbered wholesale at the 2022->2023 CAP reform: 1 geo_id of 2.6 M is shared between 2022-2 and 2023-2, although 819 k geometries are identical (same gid). /changes for 2022->2023 therefore reports everything as gone/new and /history/ never spans 2022. (b) Within 2023-2026 a GEO_ID is a geometry VERSION id: kind=geom (same geo_id, changed outline) is 0 in every pair, so iou in /changes is always null; redrawing a Schlag mints a new geo_id (the old one is 'gone', the new one 'new'). Use /lineage, /lineage/summary and history.lineage instead (gid identity + polygon overlap), and /fs//lineage, which links FS blocks via identical geometries. * 2022->2023 was a re-export plus a partial re-cut: only 31 % of 2022 outlines are bit-identical, another 37 % are 'near' (IoU >= 0.99, Hausdorff median 0.1 m = sub-decimetre coordinate shifts of the same polygon), 18 % modified (IoU 0.5-0.99), 5 % split/merged, 7 % partial, 1.5 % gone. From 2023 on: ~88-90 % same, 6-8 % modified, <1 % gone per year. Treat 2022 as a different series when computing per-Schlag trends; 'near' is folded into kind=same everywhere (lineage, quality outline_years). * Land-use class flips: 1.1-1.4 % of same-outline Schlaege change class per year (mostly acker<->gruenland rotation), 3.5 % in 2022->2023; 15-19 % among split/partial ones. Per-Schlag class is a yearly statement, not a property of the field. * SL_FLAECHE_BRUTTO_HA is computed from the polygon (median ratio 0.9994, 99 % within 1 %): the area attribute carries no independent information; area_mismatch flags 0.01 %. * GEOM_DATE_CREATED: 2022 file has 55 % of Schlaege stamped 2018 (previous system migration); 2023-2026 files have 63-79 % stamped 2022 (the Oct 2022 CAP-reform re-digitisation, bulk batches of 50-60 polygons per second on 2022-10-05/06). Only the first appearance of a timestamp is an activity signal. * Quality layer thresholds (compact < 0.10, minwidth < 6 m, |area_ratio-1| > 0.05) are ours, not AMA's; national shares in /quality/summary: sliver 10.6 %, thin 14.5 %, mostly GLOEZ (75 % thin by design) and Alm (19 % sliver). Filter by class before judging shape. * Hofstellen leaving INVEKOS do not take their Schlaege with them: per 2 km cell, net Hofstellen loss vs Schlag-count change correlates r = -0.05 (2024->25). Farm exits are transfers to neighbours, not land abandonment. * FS_KENNUNG is renumbered every year (fs_link bridges it; 2022->2023 via gid: 680 k links, 520 k dominant). * GEOM_DATE_CREATED (ts) travels with the GEO_ID: an unchanged Schlag keeps its original digitisation timestamp for years. digitised_cycle_share in /trends measures redigitisation. * Hofstellen carry two id systems: 4-7 digit Betriebsnummer-like ids (~85 %) and 24-digit ids ("12..","16..","20..", ~15 %, >55 % of NEW Hofstellen since 2023). Both stable at a point. The source BETR_ID column (<=2023-1) is REAL-typed and float-mangles the 24-digit ids; we serve the exact INSPIRE token as text (id_kind = betriebsnummer|extended). * Releases 2022-2 (2.1 GB zip) and 2023-1 (3.7 GB) differ in size from all later ones (~1.25 GB): CAP 2023 reform, GLOEZ/LSE codes appear in 2023 (see /snar_codes/diff). LICENSE (GET /api/v1/invekos/license) ------------------------------------- Data: INVEKOS Schlaege + Hofstellen, Agrarmarkt Austria (AMA) / BML, CC BY 4.0, via data.gv.at / inspire.lfrz.gv.at. Required attribution on any redistribution: "Datenquelle: Agrarmarkt Austria (AMA), INVEKOS Schlaege , CC BY 4.0, bearbeitet" Modifications: reprojection, 0.1 m snapping, make_valid, attribute subset, cross-year de-duplication, derived change/statistics tables. KG bboxes: BEV via umfeld-at, CC BY 4.0. Code: MIT (repository LICENSE).