MapaDanych.pl

Wersja do odczytu maszynowego: /dokumentacja/dla-agentow-ai.md

Instrukcja dla agentów AI: podłączenie danych do portalu MapaDanych

Ten dokument jest przeznaczony dla agentów AI, którzy przygotowują dane dla portalu MapaDanych. Dostawcą może być każdy, kto ma dane z lokalizacją. Opisuje cel, procedurę, zasady i kryteria ukończenia pracy. Człowiek, który kieruje agentem, znajdzie przystępniejszą wersję w przewodniku dla dostawców.

Cel

Opublikować przez HTTPS:

  1. manifest – JSON zgodny ze schematem manifestu,
  2. dla każdego zbioru danych źródło danych – plik GeoJSON lub endpoint zgodny ze schematem danych i kontraktem OpenAPI dostawcy, ewentualnie istniejące kafle rastrowe / WMS,

tak, aby walidator zwrócił "valid": true i jak najmniej ostrzeżeń.

Co przeczytać

Źródło Dlaczego
llms.txt spis całej dokumentacji
Protokół 0.1 specyfikacja normatywna – w razie wątpliwości obowiązuje ona
JSON Schema manifestu, danych dokładne typy, limity i opisy pól
OpenAPI dostawcy kontrakt endpointu, można z niego wygenerować serwer
manifest-complete.json, manifest-minimal.json poprawne wzory
data-points.json, data-polygons.json poprawne wzory odpowiedzi
Katalog co już jest w portalu – nie twórz duplikatów, inspiruj się słowami kluczowymi

Jeśli potrafisz korzystać z MCP, połącz się z serwerem https://mapadanych.pl/mcp (Streamable HTTP, bez uwierzytelniania). Narzędzia documentation, schema, example, validate_manifest, validate_data i catalog obejmują wszystko, co jest w tej instrukcji. Zob. serwer MCP.

Procedura

1. Inwentaryzacja danych

Ustal, jakie dane przestrzenne ma dostawca: bazy danych (PostGIS, MS SQL), pliki GIS (SHP, GPKG, KML, CSV ze współrzędnymi), istniejące API, usługi WMS/WMTS. Dla każdego źródła ustal:

2. Projekt zbiorów danych

Sytuacja source
do ~2 000 obiektów, zmieniają się najwyżej raz dziennie plik statyczny, "type": "geojson", "mode": "full"
więcej obiektów lub dane na żywo endpoint, "mode": "bbox", ustaw coverage.bbox, a przy dużych zbiorach coverage.minZoom
istnieją już kafle WMS / XYZ "type": "raster" z szablonem {bbox-epsg-3857} lub {z}/{x}/{y}; uzupełnij presentation.legend (kolory i etykiety z legendy usługi, np. ArcGIS …/MapServer/legend?f=json lub WMS GetLegendGraphic)

3. Konwersja danych

Konwersja pliku GIS do GeoJSON:

ogr2ogr -f GeoJSON -t_srs EPSG:4326 -lco RFC7946=YES -lco COORDINATE_PRECISION=6 lavicky.geojson vstup.shp

Przeliczenie współrzędnych w Pythonie:

from pyproj import Transformer

to_wgs84 = Transformer.from_crs("EPSG:2180", "EPSG:4326", always_xy=True)
lon, lat = to_wgs84.transform(x_puwg, y_puwg)  # always_xy=True zwraca [lon, lat]

4. Manifest

Wypełnij nie tylko pola obowiązkowe, ale też te, od których zależy, czy ktoś dane znajdzie:

5. Wizualizacja

Geometria Odpowiedni typ Uwaga
punkty, dziesiątki do setek circle kolor według kategorii (categorical), etykieta przez label
punkty, tysiące circle z cluster lub heatmap mapa ciepła tylko dla gęstości, nie pokazuje pojedynczych obiektów
linie line szerokość może zależeć od wartości
powierzchnie z wartością fill + interpolate lub step kartogram; opacity 0.4–0.6, aby było widać podkład
punkty lub powierzchnie, których wartość zmienia się w czasie circle / fill + color: { "type": "trend" } gdy chodzi o zmianę (rośnie / maleje); szereg wartości w dm.chart przy każdym obiekcie, trend oblicza portal
powierzchnie bez wartości fill z jednym kolorem

Zasady: kolory hex; kategorie maks. ~8 kolorów; dla wartości użyj skali stopniowej od jasnej do ciemnej; field w prezentacji musi odpowiadać właściwości w danych (i w fields, z typem liczbowym przy interpolate/step). Jeśli wartość obiektu ma historię, wyślij ją w dm.chart przy dowolnej prezentacji – także przy kartogramie z wyborem roku (wybrany rok w highlight).

6. Endpoint (tylko przy mode: "bbox" lub danych na żywo)

Zaimplementuj GET zgodnie z kontraktem OpenAPI:

CORS nie jest potrzebny – portal wysyła zapytania z serwera.

Python (FastAPI + PostGIS):

from fastapi import FastAPI

app = FastAPI()

@app.get("/datovamapa/kontejnery")
def kontejnery(bbox: str | None = None, zoom: int | None = None, limit: int = 5000, druh: str | None = None):
    west, south, east, north = map(float, bbox.split(",")) if bbox else (-180, -90, 180, 90)
    druhy = druh.split(",") if druh else None
    rows = db.fetch_all(
        """
        SELECT id, druh, objem, vyvoz::text, ST_AsGeoJSON(geom, 6)::json AS geometry
        FROM kontejnery
        WHERE geom && ST_MakeEnvelope(%s, %s, %s, %s, 4326)
          AND (%s::text[] IS NULL OR druh = ANY(%s))
        LIMIT %s
        """,
        (west, south, east, north, druhy, druhy, limit + 1),
    )
    features = [
        {"type": "Feature", "id": r["id"], "geometry": r["geometry"],
         "properties": {"druh": r["druh"], "objem": r["objem"], "vyvoz": r["vyvoz"]}}
        for r in rows[:limit]
    ]
    return {"type": "FeatureCollection", "features": features, "dm": {"truncated": len(rows) > limit, "ttl": 3600}}

TypeScript (Cloudflare Worker / Node, dane w pamięci):

// KONTEJNERY: tablica GeoJSON Feature z punktami, wczytana przy starcie (z bazy danych lub pliku).
declare const KONTEJNERY: { type: "Feature"; id: number; geometry: { type: "Point"; coordinates: [number, number] }; properties: Record<string, unknown> }[];

export default {
  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url);
    const [west, south, east, north] = (url.searchParams.get("bbox") ?? "-180,-90,180,90").split(",").map(Number);
    const limit = Math.min(Number(url.searchParams.get("limit")) || 5000, 5000);
    const inside = KONTEJNERY.filter(({ geometry: { coordinates: [lon, lat] } }) =>
      lon >= west && lon <= east && lat >= south && lat <= north);
    return Response.json(
      { type: "FeatureCollection", features: inside.slice(0, limit), dm: { total: inside.length, truncated: inside.length > limit } },
      { headers: { "Content-Type": "application/geo+json", "Cache-Control": "public, max-age=300" } },
    );
  },
};

7. Walidacja

Powtarzaj, dopóki są błędy, i przejrzyj każde ostrzeżenie (napraw je albo świadomie pozostaw i uzasadnij):

# manifest (bez publikacji)
curl -sX POST https://mapadanych.pl/api/validate -H 'Content-Type: application/json' --data @manifest.json
# dane względem konkretnego zbioru
curl -sX POST https://mapadanych.pl/api/validate -H 'Content-Type: application/json' \
  --data "{\"data\": $(cat lavicky.geojson), \"dataset\": $(jq '.datasets[0]' manifest.json)}"
# test na żywo opublikowanego manifestu i wszystkich źródeł
curl -s 'https://mapadanych.pl/api/validate?manifestUrl=https://www.gmina-przyklad.pl/datovamapa/manifest.json'

Odpowiedź: { valid, summary, errors: [{ path, code, message, hint? }], warnings: [...] }, przy teście na żywo dodatkowo datasets[] z zapytaniem, czasem odpowiedzi i liczbą obiektów. Znaczenie kodów jest opisane w API portalu.

8. Przekazanie człowiekowi

Na koniec podsumuj:

Gdy człowiek potwierdzi licencję i treść, zarejestruj manifest: POST https://mapadanych.pl/api/registration z { "manifestUrl": "…" } lub narzędziem MCP register_provider. Dane będą dostępne jako niezatwierdzone (z ostrzeżeniem, poza katalogiem), dopóki operator ich nie zatwierdzi. Po każdej zmianie manifestu czekają na ponowne zatwierdzenie. Szczegóły: rejestracja.

Zasady

Kryteria ukończenia pracy

Polecenie dla agenta (do skopiowania)

Człowiek może dać swojemu agentowi ten tekst:

Przygotuj nasze dane przestrzenne dla portalu MapaDanych (https://mapadanych.pl).
Postępuj zgodnie z instrukcją https://mapadanych.pl/dokumentacja/dla-agentow-ai.md
(spis dokumentacji: https://mapadanych.pl/llms.txt, serwer MCP: https://mapadanych.pl/mcp).
Źródła danych: <opisz bazy danych, pliki, usługi>.
Gdzie opublikować: <strona / serwer / repozytorium>.
Licencja: <np. CC BY 4.0, atrybucja „© Gmina Przykład”> – innej nie wymyślaj.
Gotowe, gdy test na żywo walidatora zwraca valid: true i spełnisz kryteria ukończenia pracy.
Na koniec przekaż mi podsumowanie zgodnie z krokiem 8.