# GeoJSON

## Що це таке

GeoJSON — відкритий формат для обміну об'єктами карти, тобто точками, лініями й
площами разом із відомостями про них, у вигляді одного звичайного документа
JSON. Його стандартизовано в IETF як RFC 7946 у серпні 2016 року на заміну
специфікації спільноти, вперше опублікованої 2008 року. Текст GeoJSON
передається з типом медіа application/geo+json і зазвичай зберігається з
розширенням .geojson, хоча звичайне .json теж зареєстроване.

У форматі є три види об'єктів, і кожен указує свій вид у полі «type».

- **Geometry** — це форма. Типів сім: Point, MultiPoint, LineString,
  MultiLineString, Polygon, MultiPolygon і GeometryCollection. Це фігури моделі
  Simple Features від Open Geospatial Consortium (OGC), ті самі, що їх кодування
  WKT і WKB передають у текстовому та двійковому вигляді.
- **Feature** — це одна річ на карті: геометрія, об'єкт «properties» з її
  атрибутами й необов'язковий «id». Геометрія може бути null, якщо місце об'єкта
  невідоме.
- **FeatureCollection** містить список об'єктів у полі «features». Саме його
  містять більшість файлів і відповідей API.

Один об'єкт для розумного лічильника виглядає так:

```json
{
    "type": "Feature",
    "id": "meter-0417",
    "geometry": {
        "type": "Point",
        "coordinates": [12.5683, 55.6761]
    },
    "properties": {
        "name": "Meter 417",
        "installed": "2023-04-12"
    }
}
```

Кожна позиція — це масив чисел: спершу довгота, потім широта, а далі
необов'язкова висота в метрах. Специфікація вимагає саме такого порядку для
перших двох, тож точка вище лежить у центрі Копенгагена, на 12,57 градуса
східної довготи й 55,68 градуса північної широти. Усі координати використовують
одну систему відліку — довготу й широту на WGS 84 у десяткових градусах, яку OGC
позначає як CRS84. Версія 2008 року дозволяла файлу вказати іншу систему в полі
«crs». У RFC 7946 це поле прибрано, бо змішані системи відліку спричиняли
проблеми сумісності, а іншу систему допущено лише там, де всі сторони
заздалегідь про неї домовилися.

Полігони мають власні правила. Кожне кільце замкнене, має щонайменше чотири
позиції, і остання повторює першу. Перше кільце — зовнішня межа, а наступні
кільця, якщо вони є, — це отвори. RFC 7946 додає правило правої руки: зовнішні
кільця йдуть проти годинникової стрілки, а отвори — за нею. Проте версія 2008
року про напрямок обходу нічого не казала, тому парсерам належить приймати
полігони, що це правило порушують.

## Чому це важливо

Усе, що читає JSON, прочитає й GeoJSON. Браузер розбирає його без плагіна,
бібліотека вебкарт малює його напряму, а документна база даних зберігає як є.
RFC 7946 називає бібліотеки вебкарт на JavaScript, документні бази даних на
основі JSON і веб-API місцями, де формат широко використовують. Таке поширення
робить його звичним вибором, коли картографічні дані виходять із настільної GIS
на вебсторінку чи в API.

На ньому будуються й стандарти. OGC API – Features, стандарт OGC для надання
географічних об'єктів через HTTP, рекомендує GeoJSON усюди, де дані можна в
ньому подати. Сервер, що пропонує GeoJSON, повертає FeatureCollection для списку
об'єктів і окремий Feature для одного, обидва як application/geo+json, а його
типова система відліку — ті самі довгота й широта на WGS 84, які фіксує GeoJSON.

Ті самі проєктні рішення задають і межі формату.

- **Одна система відліку.** Дані в національній сітці координат треба
  перетворити на довготу й широту перед записом і перетворити назад у кожного
  споживача, який рахує в метрах.
- **Лише прості фігури.** Типи геометрії охоплюють точки, лінії з прямих
  відрізків і полігони. Криві, тривимірні тіла на кшталт 3D-моделей міст і
  координати поза WGS 84 лишаються за межами формату, і OGC API – Features
  наводить саме ці випадки як приклади даних, яких GeoJSON передати не може.
- **Розмір.** Кожну координату записано десятковими цифрами, а FeatureCollection
  — це одне значення JSON, яке звичайний парсер JSON читає до останнього байта,
  перш ніж віддати хоч один об'єкт. RFC 8142 визначає GeoJSON Text Sequences з
  типом медіа application/geo+json-seq, де кожен запис містить один текст
  GeoJSON, тож як завгодно великі набори даних можна читати поступово.
- **Без спільних меж.** Два сусідні полігони зберігають свою спільну межу кожен
  повністю, тож межу записано двічі, і правка однієї копії може відкрити щілину
  чи перекриття.

RFC 7946 прямо каже й про те, що формат лишає іншим. Власного захисту
конфіденційності чи цілісності GeoJSON не дає, тож дані під час передавання
покладаються на TLS. Розділ про безпеку попереджає, що публікація розташування
вразливих людей, тварин, середовищ існування чи об'єктів може наразити їх на
стеження або шкоду, і що розмиття позицій в анонімізованому наборі даних навряд
чи зупинить цілеспрямовану атаку.

## Як це виглядає на практиці

У
[понад тридцяти GIS-проєктах, які було побудовано, розгорнуто й підтримувано в продакшні](/uk/portfolio/led-the-development-deployment-and-support-of-over-35/),
GeoJSON був одним із форматів, між якими GDAL/OGR переносив дані, поряд із
shapefiles, GeoTIFF, KML і векторними тайлами. Під усім цим просторові дані
зберігав PostgreSQL із PostGIS, фронтендом були вебкарти на Mapbox GL і Leaflet,
а безпосередньо з даними працювали в QGIS і JOSM.

Систему відліку треба визначити раніше за все це. Під час
[міграції з MSSQL на PostgreSQL із розширенням PostGIS](/uk/portfolio/improved-geographical-map-application-performance-by-10x-through-6/)
понад 50 мільйонів просторових записів пройшли через ETL-пайплайни, кроки
трансформації яких привели їх до стандарту SRID EPSG:4326. Цей код позначає той
самий датум WGS 84 у градусах, який фіксує GeoJSON, а в посібнику PostGIS
зазначено, що вивід функції ST_AsGeoJSON відповідає RFC 7946 для геометрії в
EPSG:4326 без жодного перетворення.

## Де виникають проблеми

**Точки опиняються в чужому морі.** Шар данських адрес з'являється посеред
Аравійського моря, а кожна перевірка діапазону все одно проходить, бо обидва
числа — коректні градуси. Широту й довготу переставили місцями на вході.
EPSG:4326 формально ставить широту перед довготою, багато джерел даних цього
дотримуються, а GeoJSON очікує довготу першою. Кожну позицію слід будувати з
іменованих полів, а не з порядку стовпців, і тримати тестовий об'єкт за відомою
адресою, щоб перестановку ловив тест, а не перегляд карти.

**Координати приходять у метрах.** Шар зникає з карти або клієнт його відхиляє,
а у файлі стоять координати в сотнях тисяч там, де мають бути градуси. Геометрію
зберігали в проєктованій національній системі координат і експортували як є.
Спершу її треба перетворити на довготу й широту за допомогою `ST_Transform(geom,
4326)` у PostGIS. Без перетворення ST_AsGeoJSON типово додає поле «crs», те
саме, яке в RFC 7946 прибрано.

**Полігони малюються навиворіт в одному клієнті.** Зона обслуговування заливає
всю карту, крім власної фігури, тоді як інші інструменти малюють той самий файл
правильно. Цей клієнт розрізняє внутрішнє й зовнішнє за напрямком кожного
кільця, а кільця йдуть у протилежний бік, що версія 2008 року дозволяла.
Посібник PostGIS зазначає, що деякі клієнти вимагають правила правої руки, тож
його варто застосувати перед експортом через `ST_ForcePolygonCCW(geom)` і
перевірити результат у кожному клієнті, який його споживає.

**Файли несуть більше цифр, ніж потрібно карті.** Шар детальних полігонів
завантажується на телефоні кілька секунд, а координати мають дев'ять і більше
знаків після коми. ST_AsGeoJSON типово записує до дев'яти знаків, а RFC 7946
зазначає, що перехід від шести до п'ятнадцяти знаків може майже подвоїти розмір
тексту, тоді як шість знаків уже дають точність близько 10 см. Варто передати
менше значення `maxdecimaldigits` і спершу зменшити точність через
`ST_ReducePrecision`, бо посібник PostGIS попереджає, що обрізання цифр лише під
час виводу може зробити геометрію невалідною.

**Внутрішні стовпці потрапляють у properties.** Публічний endpoint повертає
імена власників, суми витрат чи нотатки, що мали лишитися в базі даних.
Отримавши цілий рядок, ST_AsGeoJSON кладе в «properties» кожен стовпець, крім
геометрії. Слід вибрати в підзапиті лише стовпці, призначені для публікації, і,
у PostGIS 3.5 чи новішому, назвати ключовий стовпець, щоб він став «id» об'єкта:

```sql
SELECT json_build_object(
  'type', 'FeatureCollection',
  'features', json_agg(ST_AsGeoJSON(m.*, id_column => 'id')::json)
)
FROM (SELECT id, name, geom FROM meters) AS m;
```

## Що почитати далі

- [RFC 7946: The GeoJSON Format](https://www.rfc-editor.org/rfc/rfc7946) —
  стандарт IETF із типами об'єктів, порядком координат, правилом обходу,
  порадами щодо точності й міркуваннями безпеки.
- [RFC 8142: GeoJSON Text Sequences](https://www.rfc-editor.org/rfc/rfc8142) —
  супутній формат, що ділить великий набір даних на окремі тексти GeoJSON, по
  одному на запис.
- [ST_AsGeoJSON](https://postgis.net/docs/ST_AsGeoJSON.html) — сторінка
  посібника PostGIS про експорт геометрії та цілих рядків у GeoJSON, з
  параметрами точності й опціями.
- [EPSG:4326 WGS 84](https://epsg.org/crs_4326/WGS-84.html) — запис реєстру, у
  якому порядок осей ставить широту першою, протилежно до GeoJSON.
- [OGC API – Features – Part 1: Core](https://docs.ogc.org/is/17-069r4/17-069r4.html)
  — стандарт OGC, що рекомендує GeoJSON для надання об'єктів через HTTP.

<https://engineer.company/uk/glossary/geojson/>
