Перейти к основному содержимому

4. Cameras

Раздел описывает камеры наблюдения, используемые системой как источник точных данных о занятости парковочных зон.

4.1 Совместимость с текущей реализацией

Раздел сохраняет существующие основные эндпоинты:

  • GET /cameras
  • POST /cameras/new
  • GET /cameras/<camera_id>
  • PUT /cameras/<camera_id>
  • DELETE /cameras/<camera_id>
  • GET /cameras/next
  • GET /cameras/<camera_id>/snapshot

Для сохранения совместимости:

  • существующие обязательные поля камеры сохранены;
  • существующие пути эндпоинтов сохранены;
  • новые поля добавлены как дополнительные;
  • клиенты, которые не используют новые поля, могут продолжать работать по прежней схеме.

4.2 Общие правила

Авторизация

Все эндпоинты раздела требуют авторизации.

Authorization: Bearer <access_token>

Модель доступа

Для эндпоинтов раздела используются следующие разрешения:

  • cameras.view — просмотр камер;
  • cameras.create — создание камер;
  • cameras.update — изменение камер;
  • cameras.delete — удаление камер;
  • admin.monitoring.view — просмотр снапшотов с визуализацией распознавания.

Правила видимости данных

Сервер возвращает только те камеры, которые доступны текущему пользователю в рамках его глобальных прав, членства в партнёрской организации и области доступа к данным.

Для камер партнёров рекомендуется использовать:

  • partner_id — владелец камеры;
  • created_by_user_id — пользователь, создавший камеру.

Это позволяет применять read_scope, write_scope и delete_scope к камерам партнёра.

Общие ошибки

КодТипОписание
400Bad RequestНевалидный JSON или неверные типы полей.
401UnauthorizedТокен отсутствует, невалиден, истёк или сессия завершена.
403ForbiddenУ пользователя недостаточно прав для выполнения действия.
404Not FoundКамера не найдена.
409ConflictКонфликт данных, например камера с таким title уже существует.
415Unsupported Media TypeНеверный Content-Type. Используй application/json.
422Unprocessable EntityОшибка валидации.
500Internal Server ErrorНеобработанная ошибка сервера.
503Service UnavailableСервис временно недоступен.

Пример ответа:

{
"error_description": "Unsupported Media Type: expected application/json"
}

4.3 Формат данных

Время

Все временные значения передаются в формате UTC ISO 8601:

2026-04-06T10:00:00Z

Калибровка камеры

Поле calib хранит вложенный JSON-объект с параметрами калибровки, коррекции и вытягивания изображения.

Поле:

  • может быть null;
  • сохраняется и возвращается сервером как JSON;
  • не нормализуется сервером по отдельным подполям в рамках MVP.

4.4 Модель Camera

Объект камеры в ответах API.

Базовые поля

  • camera_id (integer) — уникальный идентификатор камеры.
  • title (string) — человекочитаемое название или описание камеры.
  • source (string) — URL видеопотока или строка подключения (rtsp://..., .m3u8 и т.п.).
  • image_width (integer) — ширина изображения видеопотока в пикселях.
  • image_height (integer) — высота изображения видеопотока в пикселях.
  • calib (json | null) — параметры калибровки камеры.
  • latitude (float, -90..90) — широта камеры.
  • longitude (float, -180..180) — долгота камеры.
  • created_at (string, ISO 8601) — дата создания.
  • updated_at (string, ISO 8601) — дата последнего обновления.

Дополнительные поля

  • partner_id (integer | null) — идентификатор партнёрской организации-владельца камеры.
  • created_by_user_id (integer | null) — идентификатор пользователя, создавшего камеру.
  • is_active (boolean) — активна ли камера.

Поля partner_id, created_by_user_id и is_active являются дополнительными и не обязательны для старых клиентов.

Пример

{
"camera_id": 1,
"title": "Кронверкский просп., парковка напротив ИТМО",
"source": "rtsp://...",
"image_width": 1920,
"image_height": 1080,
"calib": {
"image_width": 1920,
"image_height": 1080,
"K": [
[1739.237279181759, 0.0, 947.5335576199107],
[0.0, 2244.705015334057, 564.6946579168148],
[0.0, 0.0, 1.0]
],
"D": [-0.37062084436192333, 0.05057465862770827, 0.033198096980616335, 0.012812747166936252],
"balance": 0.0,
"model": "opencv_fisheye_k1k2k3k4"
},
"latitude": 59.955976,
"longitude": 30.309426,
"partner_id": 10,
"created_by_user_id": 123,
"is_active": true,
"created_at": "2026-04-06T10:00:00Z",
"updated_at": "2026-04-06T10:30:00Z"
}

4.5 Модель CameraMapItem

Облегчённая модель камеры для отображения на карте.

  • camera_id (integer) — уникальный идентификатор камеры.
  • title (string) — название камеры.
  • latitude (float) — широта камеры.
  • longitude (float) — долгота камеры.
  • partner_id (integer | null) — идентификатор партнёра-владельца.
  • is_active (boolean) — активна ли камера.

Пример

{
"camera_id": 1,
"title": "Кронверкский просп., парковка напротив ИТМО",
"latitude": 59.955976,
"longitude": 30.309426,
"partner_id": 10,
"is_active": true
}

4.6 GET /cameras

Возвращает список камер, доступных текущему пользователю.

Требуемые разрешения

  • cameras.view

Query-параметры (необязательные)

  • q (string) — фильтр по подстроке в title.
  • partner_id (integer) — фильтр по партнёру-владельцу камеры.
  • is_active (boolean) — фильтр по активности камеры.
  • bbox (string) — пространственный фильтр в формате <min_longitude>,<min_latitude>,<max_longitude>,<max_latitude>.
  • view (string) — режим ответа. Поддерживаемые значения:
    • full — полная модель камеры (по умолчанию);
    • map — облегчённая модель для отображения на карте.

Примеры bbox

30.30,59.92,30.35,59.97

Параметр bbox рекомендуется использовать при загрузке камер для карты, чтобы не запрашивать все камеры сразу.

Пример запроса — полная модель

GET /api/v1/cameras?q=просп HTTP/1.1
Host: api.parktrack.live
Authorization: Bearer <token>

Пример запроса — режим карты

GET /api/v1/cameras?bbox=30.30,59.92,30.35,59.97&view=map HTTP/1.1
Host: api.parktrack.live
Authorization: Bearer <token>

Response (200)

  • при view=full — массив объектов Camera;
  • при view=map — массив объектов CameraMapItem.

Пример ответа (200) — view=full

[
{
"camera_id": 1,
"title": "Кронверкский просп., парковка напротив ИТМО",
"source": "rtsp://...",
"image_width": 1920,
"image_height": 1080,
"calib": {
"image_width": 1920,
"image_height": 1080,
"K": [
[1739.237279181759, 0.0, 947.5335576199107],
[0.0, 2244.705015334057, 564.6946579168148],
[0.0, 0.0, 1.0]
],
"D": [-0.37062084436192333, 0.05057465862770827, 0.033198096980616335, 0.012812747166936252],
"balance": 0.0,
"model": "opencv_fisheye_k1k2k3k4"
},
"latitude": 59.955976,
"longitude": 30.309426,
"partner_id": 10,
"created_by_user_id": 123,
"is_active": true,
"created_at": "2026-04-06T10:00:00Z",
"updated_at": "2026-04-06T10:30:00Z"
}
]

Пример ответа (200) — view=map

[
{
"camera_id": 1,
"title": "Кронверкский просп., парковка напротив ИТМО",
"latitude": 59.955976,
"longitude": 30.309426,
"partner_id": 10,
"is_active": true
},
{
"camera_id": 2,
"title": "Ломоносова, 9 — двор",
"latitude": 59.927366,
"longitude": 30.338487,
"partner_id": 10,
"is_active": true
}
]

Ошибки

КодТипОписание
401UnauthorizedТокен отсутствует, невалиден или истёк.
403ForbiddenНедостаточно прав для просмотра камер.
422Unprocessable EntityВалидация не пройдена, например bbox имеет неверный формат или диапазон.

4.7 POST /cameras/new

Создаёт новую камеру.

Эндпоинт сохранён без изменения пути для совместимости с текущим кодом.

Требуемые разрешения

  • cameras.create

Требуемая область доступа

  • write_scope должен разрешать создание камеры в выбранной области данных.

Headers

  • Authorization: Bearer <access_token>
  • Content-Type: application/json

Request body (required)

Базовые поля

  • title (string, 1..200) — описание камеры.
  • source (string) — URL видеопотока или строка подключения.
  • image_width (integer) — ширина изображения.
  • image_height (integer) — высота изображения.
  • calib (json | null) — калибровка камеры.
  • latitude (float, -90..90) — широта камеры.
  • longitude (float, -180..180) — долгота камеры.

Дополнительные поля

  • partner_id (integer, optional) — партнёр-владелец камеры.

Если partner_id не передан, сервер может определить владельца по текущему контексту доступа пользователя.

Пример запроса

POST /api/v1/cameras/new HTTP/1.1
Host: api.parktrack.live
Content-Type: application/json
Authorization: Bearer <token>

{
"title": "Кронверкский просп., парковка напротив ИТМО",
"source": "https://example.com/stream.m3u8",
"image_width": 1920,
"image_height": 1080,
"calib": null,
"latitude": 59.955976,
"longitude": 30.309426,
"partner_id": 10
}

Response (201)

  • camera_id (integer) — идентификатор созданной камеры.

Пример ответа (201)

{
"camera_id": 1
}

Ошибки

КодТипОписание
400Bad RequestНевалидное тело запроса.
401UnauthorizedТокен отсутствует, невалиден или истёк.
403ForbiddenНедостаточно прав для создания камеры.
404Not FoundУказанный partner_id не найден.
409ConflictКамера с таким title уже существует.
415Unsupported Media TypeНеверный Content-Type.
422Unprocessable EntityОшибка валидации, например title пустой или координаты вне диапазона.

Пример ответа (409)

{
"error_description": "Camera with this title already exists"
}

4.8 GET /cameras/<camera_id>

Возвращает подробную информацию о камере.

Path-параметры

  • camera_id (integer, required) — идентификатор камеры.

Требуемые разрешения

  • cameras.view

Требуемая область доступа

  • read_scope должен включать целевую камеру.

Headers

  • Authorization: Bearer <access_token>

Пример запроса

GET /api/v1/cameras/1 HTTP/1.1
Host: api.parktrack.live
Authorization: Bearer <token>

Response (200) — объект Camera

Пример ответа (200)

{
"camera_id": 1,
"title": "Кронверкский просп., парковка напротив ИТМО",
"source": "https://example.com/stream.m3u8",
"image_width": 1920,
"image_height": 1080,
"calib": null,
"latitude": 59.955976,
"longitude": 30.309426,
"partner_id": 10,
"created_by_user_id": 123,
"is_active": true,
"created_at": "2026-04-06T10:00:00Z",
"updated_at": "2026-04-06T10:10:00Z"
}

Ошибки

КодТипОписание
401UnauthorizedТокен отсутствует, невалиден или истёк.
403ForbiddenНедостаточно прав для просмотра камеры.
404Not FoundКамера не найдена.

4.9 PUT /cameras/<camera_id>

Обновляет данные камеры.

Допускается частичное обновление.

Path-параметры

  • camera_id (integer, required) — идентификатор камеры.

Требуемые разрешения

  • cameras.update

Требуемая область доступа

  • write_scope должен включать целевую камеру.

Headers

  • Authorization: Bearer <access_token>
  • Content-Type: application/json

Request body

  • title (string, 1..200, optional) — описание камеры.
  • source (string, optional) — URL видеопотока или строка подключения.
  • image_width (integer, optional) — ширина изображения.
  • image_height (integer, optional) — высота изображения.
  • calib (json | null, optional) — калибровка камеры.
  • latitude (float, optional) — широта камеры.
  • longitude (float, optional) — долгота камеры.
  • partner_id (integer, optional) — владелец камеры.
  • is_active (boolean, optional) — активность камеры.

Поля camera_id, created_at, updated_at, created_by_user_id изменяются сервером и игнорируются в теле запроса.

Пример запроса

PUT /api/v1/cameras/1 HTTP/1.1
Host: api.parktrack.live
Content-Type: application/json
Authorization: Bearer <token>

{
"title": "Кронверкский просп., камера №1 (в сторону парка)",
"latitude": 59.955980,
"longitude": 30.309430
}

Пример запроса — частичное обновление калибровки

PUT /api/v1/cameras/1 HTTP/1.1
Host: api.parktrack.live
Content-Type: application/json
Authorization: Bearer <token>

{
"calib": {
"image_width": 1920,
"image_height": 1080,
"model": "opencv_fisheye_k1k2k3k4"
}
}

Response (200) — объект Camera

Пример ответа (200)

{
"camera_id": 1,
"title": "Кронверкский просп., камера №1 (в сторону парка)",
"source": "https://example.com/stream.m3u8",
"image_width": 1920,
"image_height": 1080,
"calib": null,
"latitude": 59.95598,
"longitude": 30.30943,
"partner_id": 10,
"created_by_user_id": 123,
"is_active": true,
"created_at": "2026-04-06T10:00:00Z",
"updated_at": "2026-04-06T12:00:00Z"
}

Ошибки

КодТипОписание
400Bad RequestНевалидное тело запроса.
401UnauthorizedТокен отсутствует, невалиден или истёк.
403ForbiddenНедостаточно прав для изменения камеры.
404Not FoundКамера или указанный partner_id не найдены.
409ConflictКонфликт уникальности title.
422Unprocessable EntityОшибка валидации, например title пустой или координаты вне диапазона.

4.10 DELETE /cameras/<camera_id>

Удаляет камеру.

Эндпоинт сохранён для совместимости с текущей реализацией.

Path-параметры

  • camera_id (integer, required) — идентификатор камеры.

Требуемые разрешения

  • cameras.delete

Требуемая область доступа

  • delete_scope должен включать целевую камеру.

Headers

  • Authorization: Bearer <access_token>

Response (204)
Тело ответа отсутствует.

Пример запроса

DELETE /api/v1/cameras/1 HTTP/1.1
Host: api.parktrack.live
Authorization: Bearer <token>

Ошибки

КодТипОписание
401UnauthorizedТокен отсутствует, невалиден или истёк.
403ForbiddenНедостаточно прав для удаления камеры.
404Not FoundКамера не найдена.
409ConflictУдаление невозможно из-за активных зависимостей, если сервер не выполняет каскадное удаление.

Пример ответа (404)

{
"error_description": "Camera not found"
}

4.11 GET /cameras/next

Возвращает следующую камеру для очередного цикла обработки.

Эндпоинт предназначен для внутренних сервисов обработки видеопотока и совместим с текущей реализацией.

Требуемые разрешения

  • cameras.view

Headers

  • Authorization: Bearer <access_token>

Response (200)

Минимальный объект камеры для обработки:

  • camera_id (integer)
  • source (string)
  • image_width (integer)
  • image_height (integer)
  • calib (json | null)

Сервер может дополнительно вернуть:

  • partner_id (integer | null)
  • is_active (boolean)

Пример запроса

GET /api/v1/cameras/next HTTP/1.1
Host: api.parktrack.live
Authorization: Bearer <token>

Пример ответа (200)

{
"camera_id": 1,
"source": "https://example.com/stream.m3u8",
"image_width": 1920,
"image_height": 1080,
"calib": null,
"partner_id": 10,
"is_active": true
}

Ошибки

КодТипОписание
401UnauthorizedТокен отсутствует или невалиден.
403ForbiddenНедостаточно прав.
404Not FoundНе добавлено ни одной камеры.

Пример ответа (404)

{
"error_description": "No cameras added"
}

4.12 GET /cameras/<camera_id>/snapshot

Возвращает кадр из видеопотока камеры.

По умолчанию эндпоинт возвращает обычный снапшот.

При наличии соответствующих прав эндпоинт может вернуть снапшот с визуализацией распознавания.

Path-параметры

  • camera_id (integer, required) — идентификатор камеры.

Требуемые разрешения

  • для обычного снапшота: cameras.view
  • для снапшота с визуализацией: admin.monitoring.view

Требуемая область доступа

  • read_scope должен включать целевую камеру.

Headers

  • Authorization: Bearer <access_token>

Query-параметры (необязательные)

  • annotated (boolean) — если true, сервер пытается вернуть снапшот с визуализацией распознавания.
  • last_detection (boolean) — если true и annotated=false, сервер возвращает снапшот, сделанный в момент последней детекции, вместо попытки получить новый. При annotated=true значение аргумента не влияет ни на что. По умолчанию, false.
  • fallback_to_raw (boolean) — если true, сервер может вернуть обычный снапшот, если снапшот с визуализацией недоступен.

Пример запроса — обычный снапшот

GET /api/v1/cameras/1/snapshot HTTP/1.1
Host: api.parktrack.live
Authorization: Bearer <token>

Пример запроса — снапшот с визуализацией

GET /api/v1/cameras/1/snapshot?annotated=true&fallback_to_raw=true HTTP/1.1
Host: api.parktrack.live
Authorization: Bearer <token>

Response (200)

Тело ответа содержит изображение.

Возможные Content-Type:

  • image/jpeg
  • image/png
  • image/webp

Ошибки

КодТипОписание
401UnauthorizedТокен отсутствует, невалиден или истёк.
403ForbiddenНедостаточно прав для просмотра снапшота или визуализации.
404Not FoundКамера не найдена или снапшот недоступен.
503Service UnavailableСервис получения снапшота временно недоступен.

Пример ответа (404)

{
"error_description": "Camera snapshot not available"
}

4.13 Требования к другим разделам API

Если эндпоинт другого раздела работает с камерой как с ресурсом партнёра, рекомендуется использовать поля:

  • camera_id (integer)
  • partner_id (integer | null)
  • created_by_user_id (integer | null)

Если эндпоинт другого раздела ссылается на снапшоты или результаты распознавания по камере, рекомендуется использовать camera_id как основной внешний ключ.