4. Cameras
Раздел описывает камеры наблюдения, используемые системой как источник точных данных о занятости парковочных зон.
4.1 Совместимость с текущей реализацией
Раздел сохраняет существующие основные эндпоинты:
GET /camerasPOST /cameras/newGET /cameras/<camera_id>PUT /cameras/<camera_id>DELETE /cameras/<camera_id>GET /cameras/nextGET /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 к камерам партнёра.
Общие ошибки
| Код | Тип | Описание |
|---|---|---|
| 400 | Bad Request | Невалидный JSON или неверные типы полей. |
| 401 | Unauthorized | Токен отсутствует, невалиден, истёк или сессия завершена. |
| 403 | Forbidden | У пользователя недостаточно прав для выполнения действия. |
| 404 | Not Found | Камера не найдена. |
| 409 | Conflict | Конфликт данных, например камера с таким title уже существует. |
| 415 | Unsupported Media Type | Неверный Content-Type. Используй application/json. |
| 422 | Unprocessable Entity | Ошибка валидации. |
| 500 | Internal Server Error | Необработанная ошибка сервера. |
| 503 | Service 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
}
]
Ошибки
| Код | Тип | Описание |
|---|---|---|
| 401 | Unauthorized | Токен отсутствует, невалиден или истёк. |
| 403 | Forbidden | Недостаточно прав для просмотра камер. |
| 422 | Unprocessable 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
}
Ошибки
| Код | Тип | Описание |
|---|---|---|
| 400 | Bad Request | Невалидное тело запроса. |
| 401 | Unauthorized | Токен отсутствует, невалиден или истёк. |
| 403 | Forbidden | Недостаточно прав для создания камеры. |
| 404 | Not Found | Указанный partner_id не найден. |
| 409 | Conflict | Камера с таким title уже существует. |
| 415 | Unsupported Media Type | Неверный Content-Type. |
| 422 | Unprocessable 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"
}
Ошибки
| Код | Тип | Описание |
|---|---|---|
| 401 | Unauthorized | Токен отсутствует, невалиден или истёк. |
| 403 | Forbidden | Недостаточно прав для просмотра камеры. |
| 404 | Not 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"
}
Ошибки
| Код | Тип | Описание |
|---|---|---|
| 400 | Bad Request | Невалидное тело запроса. |
| 401 | Unauthorized | Токен отсутствует, невалиден или истёк. |
| 403 | Forbidden | Недостаточно прав для изменения камеры. |
| 404 | Not Found | Камера или указанный partner_id не найдены. |
| 409 | Conflict | Конфликт уникальности title. |
| 422 | Unprocessable 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>
Ошибки
| Код | Тип | Описание |
|---|---|---|
| 401 | Unauthorized | Токен отсутствует, невалиден или истёк. |
| 403 | Forbidden | Недостаточно прав для удаления камеры. |
| 404 | Not Found | Камера не найдена. |
| 409 | Conflict | Удаление невозможно из-за активных зависимостей, если сервер не выполняет каскадное удаление. |
Пример ответа (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
}
Ошибки
| Код | Тип | Описание |
|---|---|---|
| 401 | Unauthorized | Токен отсутствует или невалиден. |
| 403 | Forbidden | Недостаточно прав. |
| 404 | Not 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/jpegimage/pngimage/webp
Ошибки
| Код | Тип | Описание |
|---|---|---|
| 401 | Unauthorized | Токен отсутствует, невалиден или истёк. |
| 403 | Forbidden | Недостаточно прав для просмотра снапшота или визуализации. |
| 404 | Not Found | Камера не найдена или снапшот недоступен. |
| 503 | Service 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 как основной внешний ключ.