# Protokół MapaDanych 0.1 {#protokol-datovamapa-0-1}

Normatywny opis interfejsu między dostawcą danych a portalem. W razie sprzeczności z inną dokumentacją obowiązuje ten dokument oraz schematy maszynowe:

- [JSON Schema manifestu](https://mapadanych.pl/schema/0.1/manifest.json) i [JSON Schema danych](https://mapadanych.pl/schema/0.1/data.json) – generowane ze schematów zod w `src/lib/protocol/`,
- [kontrakt OpenAPI dostawcy](https://mapadanych.pl/schema/0.1/provider-openapi.json).

Przygotowanie danych krok po kroku (zwłaszcza dla agentów AI) opisuje [poradnik dla agentów AI](https://mapadanych.pl/dokumentacja/dla-agentow-ai.md), przystępne wprowadzenie znajdziesz w [przewodniku dla dostawców](https://mapadanych.pl/dokumentacja/dla-dostawcow.md). Poprawne wzorce: [manifest-complete.json](https://mapadanych.pl/przyklady/manifest-complete.json), [data-points.json](https://mapadanych.pl/przyklady/data-points.json).

## Przegląd {#prehled}

```
Dostawca                             Portal MapaDanych                 Przeglądarka
────────                             ─────────────────                 ────────────
manifest.json  ◄── GET (cache 10 min) ── rejestr → katalog ──────────► /api/catalog
endpoint danych ◄── GET ?bbox&zoom&… ─── proxy + walidacja + cache ───► /api/data/{provider}/{dataset}
kafelki rastrowe ◄────────────────────────────────────────────────────── bezpośrednio z przeglądarki
```

## Zasady ogólne {#obecna-pravidla}

- Format JSON, kodowanie UTF-8, współrzędne WGS84 (EPSG:4326) w kolejności `[lon, lat]`.
- Komunikacja przez HTTPS (http tylko dla `localhost` podczas programowania).
- **Nieznane pola są ignorowane.** Nieznany typ `presentation` jest renderowany jako `auto`.
- Wersja `protocol` w postaci `major.minor`. Portal przyjmie manifest o tej samej wersji głównej.
- Identyfikatory (`provider.id`, `dataset.id`): małe litery bez znaków diakrytycznych, cyfry, łączniki, maks. 64 znaki. Globalny klucz zbioru to `provider/dataset`.
- Kolory tylko w zapisie szesnastkowym (`#rgb`, `#rrggbb`, `#rrggbbaa`).

## Manifest {#manifest}

| Pole | Wymagane | Opis |
|---|---|---|
| `protocol` | tak | Wersja protokołu, np. `"0.1"` |
| `provider.id` | tak | Identyfikator; musi odpowiadać id w rejestrze portalu |
| `provider.name` | tak | Nazwa wyświetlana jako źródło |
| `provider.description`, `homepage`, `logo`, `contact` | nie | |
| `provider.managedBy` | nie | `provider` (domyślnie) / `portal` – manifest usługi publicznej utrzymuje portal MapaDanych |
| `license` | nie | `{ name, url?, attribution }` – domyślna licencja wszystkich zbiorów |
| `datasets[]` | tak | 1–200 zbiorów danych o unikalnym `id` |

### Zbiór danych {#datova-sada}

| Pole | Wymagane | Opis |
|---|---|---|
| `id`, `title` | tak | |
| `description` | nie | |
| `category` | nie | `territory`, `nature`, `water`, `risks`, `energy`, `transport`, `society`, `economy`, `infrastructure`, `sample`, `other`; inna wartość → Inne |
| `tags`, `keywords` | nie | Do wyszukiwania i dla przyszłego asystenta |
| `useCases` | nie | Cele, do których zbiór się nadaje: `construction`, `housing`, `land-purchase`, `agriculture`, `investment`… |
| `coverage` | nie | `{ bbox?, area?, minZoom?, maxZoom? }` – poniżej `minZoom` portal nie wczytuje danych |
| `temporal` | nie | `{ updated?, frequency?, param? }` – `param` = klucz parametru, którym wybiera się okres (zob. [filtr czasowy](https://mapadanych.pl/dokumentacja/protokol.md#casovy-filtr)) |
| `license` | nie | Nadpisuje licencję dostawcy |
| `source` | tak | Skąd pobierać dane (zob. niżej) |
| `presentation` | tak | Jak dane renderować (zob. niżej) |
| `fields[]` | nie | Opis właściwości obiektów: `key`, `label`, `type`, `unit`, `decimals`, `description`, `primary` |
| `params[]` | nie | Filtry dla użytkownika (zob. niżej) |
| `cache` | nie | `{ ttl?: sekundy, store?: boolean }` |
| `live` | nie | `{ interval: sekundy }` – dane na żywo: otwarta mapa sama odświeża zbiór (10 do 3600 s), zob. [cache](https://datovamapa.cz/dokumentace/protokol.md#cache) |
| `detail` | nie | `{ url }` – szczegóły obiektu na żądanie po kliknięciu (rozkład jazdy, bogatszy opis, wyróżnienie na mapie), zob. [szczegóły obiektu](https://datovamapa.cz/dokumentace/protokol.md#detail-objektu) |
| `profile` | nie | Rodzaj zbioru ze wspólnym zachowaniem w portalu: `transit_vehicles` = bieżące pozycje pojazdów transportu publicznego, zob. [transport](https://datovamapa.cz/dokumentace/protokol.md#doprava) |

### `source` {#source}

**`geojson`** – format natywny.

```json
{ "type": "geojson", "url": "/data/kontenery", "mode": "bbox", "maxFeatures": 5000 }
```

- `url` może być względny wobec URL manifestu. Źródłem może być też **plik statyczny** (`lawki.geojson` obok `manifest.json` na dowolnym hostingu) – w trybie `full` portal nie wymaga żadnych parametrów zapytania, a plik jest poprawnym endpointem.
- `mode: "full"` – cały zbiór jednym zapytaniem (małe zbiory). `mode: "bbox"` – portal pyta o wycinek mapy.
- `maxFeatures` ≤ 5000.

**`raster`** – gotowe kafelki, przeglądarka wczytuje je bezpośrednio ze źródła.

```json
{ "type": "raster", "tiles": ["https://…/{z}/{x}/{y}.png"], "tileSize": 256, "minZoom": 0, "maxZoom": 19 }
```

Szablony obsługują `{z}/{x}/{y}` oraz `{bbox-epsg-3857}` (WMS GetMap). Tylko https.

### `presentation` {#presentation}

| `type` | Geometria | Opcje |
|---|---|---|
| `circle` | punkty | `color`, `radius`, `opacity`, `strokeColor`, `strokeWidth`, `label`, `cluster` |
| `heatmap` | punkty | `weight`, `radius`, `intensity`, `colors[]`, `opacity` |
| `line` | linie | `color`, `width`, `opacity`, `dash[]`, `label` |
| `fill` | powierzchnie | `color`, `opacity`, `outlineColor`, `outlineWidth`, `label` |
| `raster` | kafelki | `opacity` |
| `auto` | cokolwiek | `color` |

Wszystkie typy mają opcjonalną `legend: [{ label, color }]` (maks. 20 pozycji). Portal pokazuje ją w legendzie bezpośrednio na mapie i w wyborze warstw. Dla typów wektorowych potrafi wyprowadzić ją z kolorów, dla `raster` nie – kolory są zapieczone w obrazach kafelków, dlatego legendę rastra podaj ręcznie (np. przejmując ją z legendy usługi WMS / ArcGIS).

**Kolor** (`color`) to albo ciąg szesnastkowy, albo reguła według właściwości obiektu:

```json
{ "field": "druh", "type": "categorical", "values": { "papier": "#2563eb" }, "default": "#64748b" }
{ "field": "hodnota", "type": "interpolate", "stops": [[0, "#f7fcb9"], [100, "#004529"]] }
{ "field": "hodnota", "type": "step", "default": "#eee", "stops": [[10, "#fc8"], [50, "#e34"]] }
{ "type": "trend" }
```

`trend` koloruje obiekty według tego, czy ich wartość w czasie rośnie, stoi w miejscu, czy maleje. Oblicza go portal z wykresu zmian `dm.chart` – zob. [zmiany w czasie i trend](https://mapadanych.pl/dokumentacja/protokol.md#vyvoj-v-case-a-trend).

**Liczba** (`radius`, `width`, `weight`) to liczba albo `{ "field", "type": "interpolate", "stops": [[wartość, liczba], …] }`.

**Etykieta** (`label`): `{ field, minZoom?, color?, size? }`.

### `params` {#params}

Filtr, który użytkownik ustawia w portalu; portal przesyła go jako parametr zapytania `key=value`.

| `type` | Wartość w zapytaniu |
|---|---|
| `enum` | `key=a` (z `options`) |
| `multi-enum` | `key=a,b,c` |
| `number` | `key=42` (w zakresie `min`–`max`) |
| `range` | `key=od,do` |
| `boolean` | `key=true` / `false` |
| `date` | `key=2026-10-06` |
| `date-range` | `key=2026-01-01,2026-12-31` |
| `text` | `key=…` (maks. 200 znaków) |

Klucze `bbox`, `zoom`, `limit`, `cursor`, `lang`, `protocol` są zarezerwowane. Portal wysyła tylko zadeklarowane parametry, a ich wartości wcześniej sprawdza; niewypełniony parametr zastępuje wartością `default`.

### Filtr czasowy {#casovy-filtr}

Dane w czasie (wypadki, pomiary, zdarzenia) mają jeden parametr okresu i odwołują się do niego z `temporal.param`. Portal nie pokazuje go wtedy wśród pozostałych filtrów, lecz jako oś czasu w oknie pod legendą: suwak okresu i wykres słupkowy według lat, na którym okres można wybrać kliknięciem lub przeciągnięciem. Wybrany okres widać też przy warstwie w legendzie.

- Typ `range` z latami – `min` i `max` to pierwszy i ostatni rok danych, wartość `od,do` (np. `2019,2023`, jeden rok `2023,2023`). Obiekty za cały okres zwracaj łącznie (zbiorczo), nie z podziałem na poszczególne lata.
- Typ `date-range` – okres z dokładnością do dni, `2026-01-01,2026-06-30`.

```json manifest
{
  "protocol": "0.1",
  "provider": { "id": "miasto-przyklad", "name": "Miasto Przykład" },
  "license": { "name": "CC BY 4.0", "attribution": "© Miasto Przykład" },
  "datasets": [{
    "id": "nehody",
    "title": "Wypadki drogowe",
    "description": "Wypadki zarejestrowane przez policję według skutków.",
    "category": "transport",
    "keywords": ["wypadek", "wypadek drogowy", "bezpieczeństwo", "obrażenia", "skrzyżowanie"],
    "useCases": ["transport", "housing"],
    "coverage": { "bbox": [19.79, 49.97, 20.22, 50.13], "minZoom": 12 },
    "temporal": { "frequency": "co roku", "param": "obdobi" },
    "source": { "type": "geojson", "url": "https://data.miasto-przyklad.pl/mapadanych/wypadki", "mode": "bbox", "maxFeatures": 5000 },
    "presentation": {
      "type": "circle",
      "color": { "field": "nasledky", "type": "categorical", "values": { "obrażenia": "#dc2626", "szkoda materialna": "#facc15" }, "default": "#facc15" }
    },
    "fields": [
      { "key": "nasledky", "label": "Skutki", "type": "string", "primary": true },
      { "key": "datum", "label": "Data", "type": "date" }
    ],
    "params": [{ "key": "obdobi", "label": "Okres", "type": "range", "min": 2015, "max": 2025, "step": 1, "default": "2025,2025" }]
  }]
}
```

Wykres rysuje portal z `dm.timeline` w odpowiedzi (zob. [odpowiedź z danymi](https://mapadanych.pl/dokumentacja/protokol.md#datova-odpoved)). Przy wartościach w czasie, dla których liczba obiektów nie ma sensu (liczba ludności, cena), wyślij zamiast tego zbiorczy przebieg w `dm.chart` – portal pokaże go przy suwaku jako wykres liniowy, zob. [zmiany w czasie i trend](https://mapadanych.pl/dokumentacja/protokol.md#vyvoj-v-case-a-trend). Bez żadnego z nich zostanie tylko suwak okresu.

### Zmiany w czasie i trend {#vyvoj-v-case-a-trend}

Standardowy sposób prezentacji danych, których wartość przy obiekcie zmienia się w czasie – liczba mieszkańców gminy, cena, zmierzone stężenie, frekwencja odwiedzających. Zawsze, gdy dane mają taki szereg, wysyłaj go; portal sam narysuje z niego wykres i może według niego kolorować.

- **Wykres zmian.** Jeśli obiekt ma wartość za kilka okresów, wyślij ją w `feature.dm.chart` (zob. [odpowiedź z danymi](https://mapadanych.pl/dokumentacja/protokol.md#datova-odpoved)). Portal pokaże ją w szczegółach obiektu jako wykres liniowy z wartością za wybrany okres i zmianą względem jego początku. Dotyczy to także kartogramów, w których rok wybiera się parametrem: wyślij szereg za wszystkie lata, a wybrany rok jako `highlight` z jednakowym `from` i `to`. Gdy zbiór koloruje według wartości liczbowej, okres wybiera się parametrem, a wykresu brak, walidator zgłasza to jako `missing_trend_chart`.
- **Wykres zbiorczy przy legendzie.** Przebieg dla całego obszaru zbioru lub dla wycinka (np. liczbę ludności Polski) wyślij w `dm.chart` odpowiedzi – w takiej samej postaci jak wykres obiektu. Portal pokaże go przy filtrze czasowym, dopóki użytkownik nie otworzy żadnego obiektu, a wybrany okres zaznaczy według `highlight`. Jeśli zbiór koloruje według trendu, linia otrzyma kolor trendu całości – widać wtedy, czy zielone punkty nie powielają po prostu ogólnokrajowego przebiegu.
- **Kolor według trendu.** Jeśli sensem zbioru jest zmiana („rośnie czy maleje?”), ustaw `color: { "type": "trend" }`. Portal z wykresu każdego obiektu obliczy średnią zmianę na jeden okres w % wartości początkowej – w wyróżnionym okresie (`highlight`), w przeciwnym razie dla całego szeregu. Od `threshold` (domyślnie 0,5 %) w górę wartość **rośnie** (zielony), od −`threshold` w dół **maleje** (czerwony), pomiędzy nimi **stoi w miejscu** (szary). Kolory można nadpisać przez `colors: { up, flat, down }`, na przykład gdy wzrost jest niepożądany. Legendę portal wyprowadzi sam, a wykres w szczegółach pokoloruje tak samo jak obiekt na mapie. Obiekt bez wykresu pozostanie jasnoszary (`trend_without_chart`).

Trend opiera się na zmianie względnej, dlatego nadaje się dla wielkości dodatnich (liczby, ceny, stężenia). Wartości w okolicy zera, takie jak saldo migracji, koloruj przez `interpolate`, a zmiany pokazuj tylko wykresem.

```json manifest
{
  "protocol": "0.1",
  "provider": { "id": "wojewodztwo-przyklad", "name": "Województwo Przykład" },
  "license": { "name": "CC BY 4.0", "attribution": "© Województwo Przykład" },
  "datasets": [{
    "id": "vyvoj-obyvatel",
    "title": "Zmiany liczby mieszkańców gmin",
    "description": "Gdzie mieszkańców przybywa, a gdzie ubywa. Kolor według trendu w wybranym okresie, w szczegółach gminy wykres według lat.",
    "category": "society",
    "keywords": ["mieszkańcy", "ludność", "trend", "wzrost", "wyludnianie"],
    "useCases": ["housing", "public-administration"],
    "temporal": { "frequency": "co roku", "param": "obdobi" },
    "source": { "type": "geojson", "url": "https://data.wojewodztwo-przyklad.pl/mapadanych/mieszkancy" },
    "presentation": { "type": "circle", "color": { "type": "trend", "threshold": 0.5 }, "radius": 7 },
    "fields": [
      { "key": "nazev", "label": "Gmina", "type": "string", "primary": true },
      { "key": "obyvatel", "label": "Mieszkańcy na koniec okresu", "type": "integer" }
    ],
    "params": [{ "key": "obdobi", "label": "Okres", "type": "range", "min": 2015, "max": 2025, "step": 1, "default": "2015,2025" }]
  }]
}
```

Odpowiedź wysyła wtedy przy każdej gminie szereg za wszystkie lata, a wybrany okres w `highlight`, na przykład `{ "from": "2015", "to": "2025" }`.

## Zapytanie o dane {#datovy-dotaz}

```
GET {source.url}?bbox=minLon,minLat,maxLon,maxLat&zoom=10&limit=5000&lang=pl&protocol=0.1[&cursor=…][&{params}]
Accept: application/geo+json, application/json
User-Agent: DatovaMapa/0.1 (+https://mapadanych.pl/dokumentacja/dla-dostawcow)
```

- `bbox` i `zoom` tylko w trybie `bbox`. Wycinek jest **wyrównany do kafelków** poziomu `zoom` (liczba całkowita), więc zbliżone widoki prowadzą do tego samego zapytania – zarówno dostawca, jak i portal mogą buforować odpowiedzi.
- `limit` = ile obiektów portal przetworzy maksymalnie.
- Limit czasu odpowiedzi 10 s, rozmiar maks. 15 MB.

## Odpowiedź z danymi {#datova-odpoved}

GeoJSON `FeatureCollection` (RFC 7946). Obsługiwane geometrie: `Point`, `MultiPoint`, `LineString`, `MultiLineString`, `Polygon`, `MultiPolygon`. Obiekty z `geometry: null` są pomijane.

Opcjonalne rozszerzenia:

```json
{
  "type": "FeatureCollection",
  "features": [{
    "type": "Feature", "id": 1, "geometry": {…}, "properties": {…},
    "dm": { "title": "…", "description": "…", "url": "https://…", "color": "#e11d48", "chart": {…} }
  }],
  "dm": { "generated": "2026-10-06T08:00:00Z", "ttl": 3600, "total": 1, "truncated": false, "next": "…", "notice": "…", "timeline": […] }
}
```

- `feature.dm` – nagłówek i opis w szczegółach, link do szczegółów u źródła, nadpisanie koloru, wykres zmian.
- `feature.dm.transit` – pojazd transportu publicznego w zbiorach z profilem `transit_vehicles`, zob. [transport](https://datovamapa.cz/dokumentace/protokol.md#doprava).
- `feature.dm.chart` – przebieg jednej wartości obiektu w czasie, który portal narysuje w szczegółach obiektu jako wykres liniowy z wartością za ostatni okres (np. liczba mieszkańców gminy według lat) i z którego oblicza kolor według trendu. `label` mówi, co wykres pokazuje, opcjonalne są `unit`, `decimals`, `note` (uwaga pod wykresem, np. o zmianie metodyki) i `highlight: { from, to }` – wyróżniony okres, zwykle wybrany w filtrze czasowym; portal go podświetli, pokaże wartość na jego końcu, a zmianę i trend liczy od jego początku. Jednakowe `from` i `to` oznaczają jeden okres, np. rok wybrany przy kartogramie – wykres zatrzyma się na nim. `points` to pary `{ period, value }` uporządkowane od najstarszego okresu – `period` to rok, miesiąc lub dzień, tak jak w `dm.timeline`. 2–100 punktów. Wysyłaj go zawsze, gdy obiekt ma wartość za kilka okresów (zob. [zmiany w czasie i trend](https://mapadanych.pl/dokumentacja/protokol.md#vyvoj-v-case-a-trend)).
- `dm.ttl` – ważność odpowiedzi (portal użyje mniejszej z wartości `cache.ttl` i `dm.ttl`).
- `dm.total`, `dm.truncated` – ile jest obiektów łącznie / czy odpowiedź jest skrócona.
- `dm.notice` – krótki komunikat dla użytkownika (wyświetla się przy warstwie).
- `dm.next` – kursor do następnej strony (portal na razie go nie wykorzystuje, zarezerwowane).
- `dm.timeline` – liczby obiektów **w wycinku** według okresów za cały czas, który zbiór obejmuje, niezależnie od wybranego okresu i od `limit` (policz je zapytaniem agregującym, nie ze zwróconych obiektów). Portal rysuje z nich wykres przy legendzie. `period` to rok (`2023`), miesiąc (`2023-05`) lub dzień; opcjonalne `values` dzieli liczbę według wartości właściwości z `color.field`, a wykres koloruje je jak legendę. Maks. 400 okresów.
- `dm.chart` – zbiorczy przebieg wartości dla całego obszaru zbioru lub wycinka (np. liczba ludności Polski), w takiej samej postaci jak `feature.dm.chart`. Portal pokaże go przy filtrze czasowym jako wykres liniowy, gdy odpowiedź nie zawiera `dm.timeline`.

```json data
{
  "type": "FeatureCollection",
  "features": [],
  "dm": {
    "timeline": [
      { "period": "2024", "count": 2366, "values": { "obrażenia": 701, "szkoda materialna": 1665 } },
      { "period": "2025", "count": 1894, "values": { "obrażenia": 652, "szkoda materialna": 1242 } }
    ]
  }
}
```

Zbiorczy przebieg dla całego obszaru zamiast liczby obiektów (wybrany okres 2023–2025):

```json data
{
  "type": "FeatureCollection",
  "features": [],
  "dm": {
    "chart": {
      "label": "Liczba ludności Polski na 31 grudnia",
      "highlight": { "from": "2022", "to": "2025" },
      "points": [
        { "period": "2021", "value": 38036118 },
        { "period": "2022", "value": 37766327 },
        { "period": "2023", "value": 37636508 },
        { "period": "2024", "value": 37489087 },
        { "period": "2025", "value": 37350000 }
      ]
    }
  }
}
```

Obiekt z wykresem zmian w szczegółach:

```json data
{
  "type": "FeatureCollection",
  "features": [{
    "type": "Feature",
    "id": "1261011",
    "geometry": { "type": "Point", "coordinates": [19.9450, 50.0647] },
    "properties": { "nazev": "Kraków", "obyvatel": 806000 },
    "dm": {
      "chart": {
        "label": "Liczba mieszkańców na 31 grudnia",
        "points": [
          { "period": "2023", "value": 803300 },
          { "period": "2024", "value": 804900 },
          { "period": "2025", "value": 806000 }
        ]
      }
    }
  }]
}
```
- Właściwości z przedrostkiem `__dm_` są zarezerwowane dla portalu i zostają usunięte z odpowiedzi.

## Szczegóły obiektu {#detail-objektu}

Zbiór może oferować szczegóły obiektu na żądanie: po kliknięciu obiektu portal pokazuje okno z właściwości, a pod nim uzupełnia szczegóły pobrane osobno. Przydają się przy bogatszej treści, która nie mieści się we właściwościach: rozkład jazdy pociągu, przebieg przejazdu, wyróżnienie trasy na mapie. Pole `detail` w zbiorze zawiera szablon adresu z symbolem `{id}`, za który portal podstawia `id` obiektu z danych (obiekty muszą więc mieć stabilne `id`). Adres jest https albo względny wobec manifestu; walidator zgłasza szablon bez `{id}` (`detail_url_without_id`) i schemat inny niż https (`detail_url_not_https`).

```json
"detail": { "url": "https://data.example.cz/detail?id={id}" }
```

Odpowiedzią jest JSON `FeatureDetail` (`title` i `generated` są obowiązkowe, pozostałe pola opcjonalne):

- `title`, `subtitle`, `badge { text, color, textColor }` – nagłówek, `status { label, tone }` – stan z tonem `ok`, `warning`, `danger` lub `neutral`,
- `fields[] { label, value }`, `tags[]`, `notes[]` – dodatkowe dane, krótkie etykiety i uwagi,
- `journey.calls[]` – kolejne przystanki (najwyżej 300): `name`, `arrival`, `departure` (obowiązujący czas), `scheduledArrival`, `scheduledDeparture` (rozkład), `estimated` (czas jest szacunkiem), `platform`, `state` (`passed`, `current`, `upcoming`); czasy są w ISO 8601 z przesunięciem strefy,
- `highlight` – GeoJSON `FeatureCollection` do wyróżnienia na mapie, do około 200 kB: linie z właściwością `role` `passed` (przebyta część, przyciemniona) lub `upcoming`, punkty z `role` `stop` lub `current` i `name` jako etykietą,
- `color`, `url`, `notice`, `generated`, `ttl` (jak długo szczegóły są ważne, w sekundach).

Dopóki szczegóły są otwarte, mapa odświeża je z interwałem `live.interval` zbioru. Portal buforuje odpowiedzi według `ttl` i sprawdza ich schemat tak samo jak dane. Nieznane `id` dostawca zwraca jako `404`.

```json detail
{
  "title": "RJ 1000",
  "subtitle": "→ Praha-Zahradní Město · RegioJet a.s.",
  "badge": {
    "text": "RJ 1000",
    "color": "#0f766e"
  },
  "status": {
    "label": "+8 min",
    "tone": "warning"
  },
  "journey": {
    "calls": [
      {
        "name": "Ostrava hlavní nádraží",
        "departure": "2026-11-04T05:05:00+01:00",
        "scheduledDeparture": "2026-11-04T05:05:00+01:00",
        "platform": "2",
        "state": "passed"
      },
      {
        "name": "Pardubice hlavní nádraží",
        "arrival": "2026-11-04T07:30:00+01:00",
        "departure": "2026-11-04T07:38:00+01:00",
        "scheduledArrival": "2026-11-04T07:22:00+01:00",
        "scheduledDeparture": "2026-11-04T07:30:00+01:00",
        "platform": "3",
        "state": "current"
      }
    ]
  },
  "highlight": {
    "type": "FeatureCollection",
    "features": [
      {
        "type": "Feature",
        "geometry": {
          "type": "LineString",
          "coordinates": [
            [
              15.77,
              50.03
            ],
            [
              14.43,
              50.08
            ],
            [
              14.6,
              50.07
            ]
          ]
        },
        "properties": {
          "role": "upcoming"
        }
      },
      {
        "type": "Feature",
        "geometry": {
          "type": "Point",
          "coordinates": [
            15.77,
            50.03
          ]
        },
        "properties": {
          "role": "current",
          "name": "Pardubice hlavní nádraží"
        }
      }
    ]
  },
  "color": "#0f766e",
  "generated": "2026-11-04T07:35:00Z",
  "ttl": 30
}
```

Pełny przykład: [detail-jizda.json](https://datovamapa.cz/priklady/detail-jizda.json).

## Transport {#doprava}

Zbiory z bieżącymi pozycjami pojazdów transportu publicznego (pociągi, autobusy, tramwaje, metro…) oznacz `"profile": "transit_vehicles"`. Portal pokaże je wtedy we wspólnej warstwie „Transport na żywo”: wszystkie tym samym stylem (kształt według rodzaju, kolor według opóźnienia), z jedną legendą, filtrem według rodzaju transportu, opóźnienia i przewoźnika oraz z wyszukiwaniem kursu. Użytkownik wybiera więc rodzaj transportu, a nie źródło danych.

Zbiór z profilem musi mieć `live`, prezentację `circle` i obiekty jako punkty z `feature.dm.transit`. Walidator zgłasza brak `live` (`transit_missing_live`), inną prezentację (`transit_not_circle`), obiekt bez `dm.transit` (`transit_missing_properties`) i obiekt, który nie jest punktem (`transit_not_point`).

| Pole `dm.transit` | Wymagane | Opis |
|---|---|---|
| `mode` | tak | `rail`, `bus`, `tram`, `metro`, `trolleybus`, `ferry`, `funicular`, `other` |
| `label` | tak | Oznaczenie, którego szukają ludzie: pociąg (`R 1234`), linia (`188`) |
| `line` | nie | Linia, gdy różni się od oznaczenia (`S2`) |
| `headsign`, `operator` | nie | Cel kursu, przewoźnik |
| `delay_min` | nie | Opóźnienie w pełnych minutach, ujemne = przed czasem |
| `delay_band` | tak | `on_time` do 4 min, `minor` 5 do 14, `medium` 15 do 29, `major` 30 do 59, `extreme` 60 i więcej, `unknown` bez danych |
| `bearing` | nie | Kierunek jazdy w stopniach od północy |
| `trip_ref` | nie | Stały klucz kursu (ten sam przy kolejnych odświeżeniach); taki sam jak `id` obiektu, jeśli zbiór oferuje szczegóły kursu |
| `observed_at` | tak | Kiedy zmierzono pozycję (ISO 8601) |

Każdy pojazd ma być na mapie tylko raz. Gdy te same pojazdy zgłasza kilka źródeł, niech wydaje je tylko jeden zbiór. Właściwości w `properties` (opisy dla ludzi w języku kraju) pozostają, `dm.transit` to neutralna część dla portalu.

```json data
{
  "type": "FeatureCollection",
  "features": [{
    "type": "Feature",
    "id": "1234|20261104",
    "geometry": { "type": "Point", "coordinates": [14.4352, 50.0833] },
    "properties": { "vlak": "R 1234", "cil": "Brno hl.n.", "zpozdeni_min": 6 },
    "dm": {
      "transit": {
        "mode": "rail", "label": "R 1234", "headsign": "Brno hl.n.", "operator": "České dráhy",
        "delay_min": 6, "delay_band": "minor", "bearing": 135, "trip_ref": "1234|20261104",
        "observed_at": "2026-11-04T07:35:10Z"
      }
    }
  }]
}
```
Pełny przykład: [data-doprava.json](https://datovamapa.cz/priklady/data-doprava.json).

## Cache {#cache}

- Portal przechowuje odpowiedzi przez czas `min(cache.ttl, dm.ttl)`, domyślnie 300 s. Manifesty 10 min.
- Po upływie tego czasu przechowuje wpis jeszcze 24 h jako kopię zapasową: gdy źródło nie odpowiada, pokazuje ostatnie dane z ostrzeżeniem.
- `cache.store: false` – dostawca nie zgadza się na trwałe zapisanie danych w warstwie danych portalu (krótkotrwałe buforowanie odpowiedzi według `ttl` obowiązuje nadal).
- `live: { interval }` – dane na żywo. Włączony zbiór w otwartej mapie sam pobiera się ponownie co `interval` sekund (10 do 3600), bez odświeżania strony; przy ukrytej karcie odświeżanie się zatrzymuje. Interwał krótszy niż `cache.ttl` walidator zgłasza (`live_interval_below_ttl`), bo mapa pytałaby częściej, niż zmieniają się dane. Portal oznacza zbiór z `live` jako działający na żywo także bez „(na żywo)” w nazwie.

## Walidacja {#validace}

Zgodność z protokołem sprawdza [walidator](https://mapadanych.pl/dokumentacja/api.md#validator) (`POST /api/validate`, test na żywo `GET /api/validate?manifestUrl=…` lub narzędzia MCP `validate_manifest` i `validate_data`). Rozróżnia:

- **błędy** (`errors`) – naruszenie tego dokumentu; manifestu z błędem portal nie przyjmie, danych z błędem nie wyświetli,
- **zalecenia** (`warnings`) – dane działają, ale gorzej się je wyszukuje, wyświetla lub łączy.

Każde ustalenie ma stabilny `code` ([przegląd kodów](https://mapadanych.pl/dokumentacja/api.md#kody-nalezu)), ścieżkę `path`, a przy zaleceniach wskazówkę `hint`. Kody nie zmieniają się między wersjami minor, nowe mogą dochodzić.

## Rozwój protokołu {#vyvoj-protokolu}

- Nowe opcjonalne pola i nowe typy `presentation` / `source` = nowa wersja minor (0.2…).
- Zmiana znaczenia istniejących pól = nowa wersja major; portal przez okres przejściowy obsługuje obie.
- Kandydaci do kolejnych wersji: `symbol` (ikony), `extrusion` (3D), wymiar czasowy (`time` przy obiektach + parametr), `vector-tiles` / `pmtiles` / `ogc-features` jako źródła, stronicowanie przez `next`, agregacja dla małych przybliżeń.
