1. Предисловие

В первых двух статьях мы разобрали основы спецификации STAC (SpatioTemporal Asset Catalog), её объектную модель и философию, превращающую разрозненные архивы геоданных в единую, машиночитаемую «библиотеку». Мы увидели, как STAC описывает каталоги (catalog), коллекции (collection), элементы (item) и их ресурсы (assets), создавая универсальный язык для работы с геопространственной информацией.

В комментариях были затронуты две темы, которые просили рассмотреть в новых статьях — семантическая паутина и универсальные браузеры, которые требуют постепенного перехода от теории к практике. Действительно, какая польза от идеально структурированного каталога, если с ним неудобно или невозможно работать? Поэтому, прежде чем углубиться в онтологии, мы рассмотрим инструменты взаимодействия с STAC.

Эта статья посвящена клиентской стороне экосистемы — STAC-браузерам, а также ключевому аспекту их работы в корпоративной среде — безопасному доступу к данным через STAC-API. Мы разберём, как устроен универсальный браузер, и представим частную реализацию стека STAC-сервера с распределённой системой управления доступом IAM (Identity and Access Management), где каждый запрос, от просмотра метаданных до скачивания тайла, проходит через цепочку авторизации.

Оглавление

  1. Предисловие

  2. STAC-браузер как окно в каталог

  3. Потребность в контроле доступа

  4. Реализация Identity and Access Management (IAM) для STAC

  5. Детали под микроскопом

  6. Кэширование - скорость vs безопасность

  7. Заключение

  8. P.S. Требуется помощь! Ищем владельцев доменов cosmomayak.com и cosmomayak.ru

  9. Цикл статей про STAC

2. STAC-браузер как окно в каталог

По своей сути, STAC-браузер — это веб-приложение, которое визуализирует содержимое STAC-каталога. Его основная задача — предоставить интуитивно понятный интерфейс для поиска и обнаружения данных: по карте, по дате, по свойствам (properties). Он не хранит сами данные, а является «умным клиентом», который взаимодействует с каталогом через его корневой catalog.json или, что чаще, через STAC-API.

Принцип универсальности любого STAC-браузера строится на двух китах:

  1. Жёсткое следование базовой спецификации. Браузер опирается на гарантированно присутствующие в любом валидном каталоге поля: idgeometrydatetimebbox, ссылки на assets. Это его «несгораемый минимум» для отображения.

  2. Всеядность по отношению к расширениям. Если каталог использует расширения (например, eosarproj), браузер не должен ломаться. Вместо этого он должен уметь показывать дополнительные поля из properties в виде читаемой таблицы. Универсальный браузер не «зашивает» в себя логику обработки конкретных расширений, а динамически отображает все найденные метаданные.

Типичный рабочий процесс браузера (например, stac-browser от Radiant Earth):

  • пользователь вводит URL корня каталога или API;

  • браузер запрашивает корневой каталог (/), получает список дочерних коллекций (/collections);

  • при выборе коллекции загружаются её описание и, часто через поисковый запрос к API (/search), первые элементы (items);

  • элементы отображаются, в том числе на временной шкале и карте (чаще используются OpenLayers);

  • Клик по элементу открывает детальную панель со всеми его свойствами (properties) и списком assets (GeoTIFF, JSON, PNG и т.д.), которые можно просмотреть или скачать.

Такой подход позволяет одним и тем же браузером исследовать каталог спутниковых снимков Landsat, архив погодных радарных данных и коллекцию 3D-моделей города — при условии, что все они соответствуют базовой спецификации STAC.

И тут важно отметить, что большинство STAC-браузеров реализует всю свою логику преимущественно через STAC-API. Т.е. все, что вы делаете в браузере может быть воспроизведено на уровне машинного доступа через API STAC-каталога, при разработке и реализации автоматов поиска и обработки данных или производства высокоуровневых информационно-аналитических продуктов из большего количества разнородных входных данных. О таком перспективном направлении как "мат��матика на метаданных" мы поговорим отдельно чуть позже.

3. Потребность в контроле доступа

Публичные каталоги, подобные Earth Search, прекрасно работают с классическими открытыми браузерами. Но в корпоративной или государственной среде данные редко бывают полностью публичными. Возникают вопросы:

  • Как скрыть определённые коллекции или элементы от одних пользователей и показать другим?

  • Как ограничить скачивание исходных данных (assets) только авторизованным группам?

  • Как интегрировать STAC-каталог в единую экосистему безопасности организации?

Стандарт STAC сознательно не включает механизмы аутентификации и авторизации, оставляя это на усмотрение реализаций конкретных экосистем. Это и есть точка, где заканчивается универсальность публичного браузера и начинается необходимость в кастомизированном решении.

4. Реализация Identity and Access Management (IAM) для STAC

Далее мы рассмотрим кастомный STAC-браузер, расширенный подсистемой безопасного доступа. Его архитектура построена вокруг модели Policy-Based Access Control (PBAC) и состоит из независимых сервисов, соответствующих стандартам XACML:

  1. Сервис управления данными (СУД / Policy Administration Point, PAP): Хранит политики доступа в виде правил («Роль R имеет право Чтения на Ресурс типа Коллекция с атрибутом category=secret»). Политики пишутся в декларативном формате (например, на Rego) и централизовано управляются администраторами.

  2. Сервис каталога STAC (STAC API / Policy Enforcement Point, PEP): Это наш основной сервис, предоставляющий STAC-API. Его ключевая роль — перехват всех входящих запросов. При получении запроса PEP не обрабатывает его сразу, а формирует запрос на авторизацию, включающий в себя: идентификатор пользователя (из токена), действие (readdownload), целевой ресурс (например, путь /collections/secret-sat/items или конкретный asset).

  3. Сервис авторизации (Policy Decision Point, PDP): Принимает запрос от PEP, загружает соответствующие политики из СУД, оценивает их и выносит вердикт: Permit или Deny. PDP не знает о логике приложения, он работает только с атрибутами, что делает систему гибкой.

  4. Keycloak (Identity Provider / Policy Information Point, PIP): Выступает в роли SSO. Он аутентифицирует пользователей, выдает JWT-токены с ролями и атрибутами (groupsdepartment). PEP и PDP используют эти утверждения (claims) из токена для принятия решений.

Как это работает в реальном сценарии:

  1. Пользователь логинится в браузере через Keycloak, получая токен.

  2. Браузер запрашивает у STAC-API список коллекций, подставляя токен в заголовок Authorization: Bearer <JWT>.

  3. PEP в STAC-API перехватывает запрос к /collections. Он извлекает из JWT идентификатор пользователя и его атрибуты, определяет, что запрашивается операция read над ресурсом collection_list. PEP отправляет эти данные в PDP.

  4. PDP проверяет политики и понимает, что пользователь из группы analysts не должен видеть коллекцию с тегом category=top_secret. PDP возвращает решение Permit с дополнительным атрибутом-фильтром: "visible_collection_ids": ["landsat", "open-data"].

  5. PEP, получив Permit, передаёт управление основной логике STAC-API. Та, в свою очередь, выполняет запрос к базе данных, добавив фильтр по ID коллекций из решения PDP. В результате пользователь видит в браузере только разрешённые ему коллекции. Для него система выглядит так, будто других коллекций просто не существует.

5. Детали под микроскопом

Всё начинается с того, что PBAC (Policy-Based Access Control), упомянутый ранее — это не модель, а скорее архитектурный подход или парадигма. Его суть в том, решения о доступе принимаются не на основе жёстко зашитых правил в коде, а на основе внешних, декларативных политик. Политики описывают, кому, при каких условиях и что разрешено. Где хранятся эти политики и как они выглядят — дело реализации.

ABAC (Attribute-Based Access Control) и RBAC (Role-Based Access Control) — это уже конкретные модели, определяющие логику этих политик.

  • RBAC оперирует ролями: «пользователь с ролью «аналитик» может читать коллекции».

  • ABAC оперирует атрибутами всех сущностей: «пользователь, чей clearance_level >= 3 и department = 'remote-sensing', может читать коллекции с тегом classification = 'restricted'».

В описываемой архитектуре мы используем PBAC как фундамент. У нас есть сервис PDP, который оценивает политики, и сервис PAP, где эти политики хранятся. А вот содержимое этих политик может быть как чисто ролевым (RBAC), так и атрибутивным (ABAC). Более того, мы активно комбинируем оба подхода в рамках одной политики. Например:

«Разрешить доступ, если пользователь состоит в группе analysts (роль) И уровень его допуска (clearance_level) не ниже 3 (атрибут)».

Это типичный пример гибридной модели, которую часто называют PBAC (в широком смысле) или просто ABAC с поддержкой ролей. Сами роли в ABAC могут быть просто одним из атрибутов пользователя (как у нас в JWT есть поле groups).

Ключевым элементом нашей модели IAM является контроль доступа на основе атрибутов (ABAC), который идёт дальше классического разграничения по ролям. Суть ABAC заключается в том, что решение о доступе принимается динамически на основе комбинации атрибутов субъекта (кто запрашивает), объекта (что запрашивают), действия (чтение, запись, скачивание) и окружения (время суток, уровень доверия к сети). Для STAC такой подход оказывается едва ли не «родным». Почему? Потому что объектом доступа здесь выступают не абстрактные файлы, а богатые метаданными сущности — коллекции, элементы и их свойства.
Политика может выглядеть так: «Разрешить доступ к элементу, если его datetime попадает в интервал, указанный в атрибутах пользователя, И значение eo:cloud_cover меньше 20%». Это позволяет строить гибкие, контекстно-зависимые правила без необходимости переписывать код API. В конечной реализации можно комбинировать ABAC с классическим ролевым доступом RBAC: роли (analystguestadmin) задают широкие контексты, а атрибуты уточняют разрешения внутри них. Такой гибрид даёт администратору возможность не плодить сотни ролей под каждый проект, а описать доступ декларативно, через понятные метаданные STAC.

Таким образом, мы строим систему на политиках (PBAC), а внутри политик используем и роли, и атрибуты, чтобы получить максимальную гибкость. Это позволяет нам не плодить сотни ролей под каждый проект, а описывать доступ декларативно, через понятные метаданные STAC, сохраняя при этом удобство ролевой модели для широких категорий пользователей.

Давайте рассмотрим, как токен из заголовка проходит через эту цепочку на уровне конкретных HTTP-вызовов. Возьмем стандартные эндпоинты STAC-API.

Что хранится в токене?

Токен доступа, выдаваемый Keycloak, представляет собой JWT (JSON Web Token). Помимо стандартных полей (sub — идентификатор пользователя, iss — издатель, exp — срок действия), мы расширяем его кастомными claims, которые необходимы для принятия решений об авторизации. Пример декодированного токена:

json

{
    "sub": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
    "email": "ivanov@corporation.ru",
    "preferred_username": "ivanov",
    "groups": ["analysts", "landsat-users"],
    "department": "remote-sensing",
    "clearance_level": 3
}

Здесь groups — группы пользователя в Keycloak (могут соответствовать ролям), department и clearance_level — пользовательские атрибуты, добавленные через протокол сопоставления (mappers) в Keycloak. Именно эти данные становятся основой для формирования запроса к PDP.

Keycloak выступает в роли PIP (Policy Information Point) — источника атрибутов. В идеальном случае все нужные атрибуты уже лежат в токене, и PEP не делает дополнительных вызовов. Однако при необходимости PEP может обратиться к UserInfo endpoint Keycloak, чтобы получить расширенную информацию о пользователе, не умещающуюся в токен (например, список проектов, к которым пользователь привязан). Обычно стараются минимизировать такие запросы ради производительности и чаще всего размещают ключевые атрибуты в самом JWT.

Обработка запроса в PEP на Python (FastAPI)

Ниже приведён упрощённый пример middleware для FastAPI, который перехватывает запросы к защищённым эндпоинтам STAC-API, извлекает токен, проверяет его и отправляет запрос на авторизацию в сервис PDP.

python

Code
import jwt
from fastapi import Request, HTTPException
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import httpx
import json

# Конфигурация
JWKS_URL = "https://keycloak.corporation.ru/auth/realms/stac-realm/protocol/openid-connect/certs"
PDP_URL = "http://pdp-service:8081/decision"
ALGORITHMS = ["RS256"]

security = HTTPBearer()

async def verify_token(credentials: HTTPAuthorizationCredentials) -> dict:
    token = credentials.credentials
    # В реальности здесь должна быть загрузка и кэширование JWKS,
    # проверка подписи, срока и т.д. Опустим детали для краткости.
    # Используем библиотеку python-jose или PyJWT с JWKS.
    # Допустим, мы получили payload:
    payload = jwt.decode(token, options={"verify_signature": False})  # только для демо!
    # В бою нужно проверять подпись!
    return payload

async def authorize(subject: dict, action: str, resource: dict) -> dict:
    """
    Отправляет запрос в PDP и возвращает решение с возможными фильтрами.
    """
    async with httpx.AsyncClient() as client:
        response = await client.post(
            PDP_URL,
            json={
                "subject": subject,
                "action": action,
                "resource": resource,
                "context": {"source_ip": "..."}  # можно добавить атрибуты окружения
            }
        )
        response.raise_for_status()
        return response.json()

@app.middleware("http")
async def authorization_middleware(request: Request, call_next):
    # Пропускаем публичные эндпоинты (например, /docs, /.well-known/health)
    if request.url.path in ["/docs", "/openapi.json", "/health"]:
        return await call_next(request)

    # Извлечение токена из заголовка Authorization
    auth_header = request.headers.get("Authorization")
    if not auth_header or not auth_header.startswith("Bearer "):
        raise HTTPException(status_code=401, detail="Missing or invalid token")
    
    token = auth_header.split(" ")[1]
    try:
        payload = await verify_token(token)  # проверка подписи опущена
    except Exception as e:
        raise HTTPException(status_code=401, detail="Invalid token")

    # Формируем субъект с атрибутами из токена
    subject = {
        "id": payload.get("sub"),
        "groups": payload.get("groups", []),
        "department": payload.get("department"),
        "clearance_level": payload.get("clearance_level")
    }

    # Определяем действие и ресурс на основе запроса
    # Здесь логика парсинга пути - для примера упростим
    if request.method == "GET" and request.url.path.startswith("/collections/"):
        # Например: /collections/landsat/items
        path_parts = request.url.path.strip("/").split("/")
        if len(path_parts) >= 2 and path_parts[1] == "items":
            collection_id = path_parts[0]  # landsat
            action = "list_items"
            resource = {"type": "collection_items", "collection_id": collection_id}
        else:
            # Запрос к конкретной коллекции или списку коллекций
            # ...
            action = "read"
            resource = {"type": "collection", "collection_id": path_parts[1]} if len(path_parts) > 1 else {"type": "collections_list"}
    else:
        # Другие эндпоинты
        action = request.method.lower()
        resource = {"type": "unknown"}

    # Запрос в PDP
    decision = await authorize(subject, action, resource)

    if decision["effect"] != "Permit":
        raise HTTPException(status_code=403, detail="Access denied")

    # Если PDP вернула фильтры (например, список разрешённых коллекций),
    # их можно добавить в request.state для использования в роутах
    request.state.auth_filters = decision.get("filters", {})

    response = await call_next(request)
    return response

Что происходит в PDP?

Сервис PDP может быть реализован на любом стеке. Для простоты представим его на Python с использованием простого движка правил (хотя в продакшене чаще используют OPA с языком Rego). Пример функции, принимающей запрос и возвращающей решение:

python

Code
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List, Optional

app = FastAPI()

class Subject(BaseModel):
    id: str
    groups: List[str] = []
    department: Optional[str] = None
    clearance_level: Optional[int] = None

class Resource(BaseModel):
    type: str
    collection_id: Optional[str] = None
    # другие атрибуты

class DecisionRequest(BaseModel):
    subject: Subject
    action: str
    resource: Resource
    context: dict = {}

@app.post("/decision")
def decide(request: DecisionRequest):
    subject = request.subject
    action = request.action
    resource = request.resource

    # Пример политики ABAC: разрешить доступ к коллекции только если clearance_level >= 3
    if resource.type == "collection_items" and resource.collection_id == "secret-sat":
        if subject.clearance_level and subject.clearance_level >= 3:
            return {"effect": "Permit", "filters": {}}
        else:
            return {"effect": "Deny"}

    # Политика: analysts могут видеть список коллекций, но не все коллекции
    if resource.type == "collections_list" and action == "read":
        if "analysts" in subject.groups:
            # Возвращаем фильтр: видеть только коллекции landsat и open-data
            return {
                "effect": "Permit",
                "filters": {"allowed_collections": ["landsat", "open-data"]}
            }
        else:
            return {"effect": "Deny"}

    # Базовая политика: все остальное разрешено (для демо)
    return {"effect": "Permit", "filters": {}}

Здесь PDP использует атрибуты субъекта (clearance_levelgroups) и ресурса (collection_id) для принятия решения. Заметьте, что PDP может также запрашивать дополнительные атрибуты из PIP — например, уровень секретности самой коллекции из метаданных (хранящихся в СУД), если они не были переданы в запросе. Для этого PDP может обращаться к базе данных коллекций или к сервису метаданных.

Базовые эндпоинты и вызовы:

  1. GET / (корень API): Возвращает описание API (/conformance/collections и т.д.). Доступ обычно публичный, но PEP может проверить валидность токена.

    bash

    # Запрос с токеном
    curl -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
         https://stac-api.corporation.ru/
  2. GET /collections: Список коллекций. Здесь PEP активно взаимодействует с PDP.

    bash

    curl -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
         https://stac-api.corporation.ru/collections

    Внутри PEP: Формируется запрос к PDP: { "subject": {"id": "user123", "groups": ["analysts"]}, "action": "list", "resource": {"type": "collection"}}. Ответ PDP с фильтром интегрируется в SQL-запрос: SELECT * FROM collections WHERE id IN ('landsat', 'open-data').

  3. GET /collections/{collectionId}/items: Поиск элементов в коллекции.

    bash

    curl -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
         "https://stac-api.corporation.ru/collections/secret-sat/items?limit=10"

    Внутри PEP: PDP проверяет, может ли user123 читать коллекцию secret-sat. Если решение Deny, API немедленно возвращает 403 Forbidden. Если Permit, запрос выполняется, и PDP может дополнительно вернуть фильтр по полям datetime или bbox (например, "можно видеть только данные старше 2 лет").

  4. Доступ к assets — ключевой момент: Прямые ссылки на данные в assets (например, "href": "s3://bucket/secret/image.tif") также защищены. Браузер не получает прямую ссылку на S3. Вместо этого в asset.href подставляется URL прокси-сервиса "s3" (который также является PEP) внутри STAC-API:

    json

    {
      "type": "image/tiff",
      "href": "https://stac-api.corporation.ru/collections/secret-sat/items/item-001/assets/data/proxy",
      "title": "Сами данные"
    }

    Когда браузер пытается скачать этот asset, запрос идёт через наш API, где PEP снова запрашивает у PDP решение для действия download на ресурс asset. Только после разрешения файл потоково передаётся пользователю.

6. Кэширование - скорость vs безопасность

Очевидно, что постоянные запросы к PDP на каждый вызов API создают задержки. Для ускорения работы необходимо внедрить кэширование решений PDP на уровне PEP.

Механизм работы

  1. При первом запросе пользователя user123 к /collections/secret-sat/items PEP формирует уникальный ключ кэша, например, хэш от конкатенации: user123 + collectionId + action + "list_items".

  2. PEP проверяет локальный (in-memory) или распределённый (Redis) кэш по этому ключу. Если решение отсутствует, запрос идёт к PDP.

  3. Полученное решение Permit (с фильтрами) кэшируется на заданное время жизни (TTL), например, 60 секунд.

  4. Все последующие запросы этого пользователя к элементам той же коллекции в течение 60 секунд будут обслуживаться из кэша, без обращения к PDP.

Важные нюансы кэширования

Инвалидация
При изменении политик в СУД администратор отправляет широковещательный сигнал всем PEP об очистке кэша.

Контекст
Кэшируется не просто Permit/Deny, а целое решение с атрибутами (фильтрами по дате, географии). Это критически важно для корректной работы.

Запросы на запись (POST, PUT)
Для них TTL кэша устанавливается в ноль или близко к нулю, чтобы каждое изменение проходило свежую авторизацию.

Такая схема позволяет снизить нагрузку на PDP и задержку для пользователя до минимума, сохраняя при этом высокий уровень безопасности. Для конечного аналитика работа в браузере остаётся быстрой и отзывчивой, в то время как каждый его клик безопасно контролируется на уровне инфраструктуры.

Кэширование решений в PEP

Чтобы избежать повторных вызовов PDP для одинаковых запросов одного пользователя, PEP использует кэш. Ключ формируется как хэш от комбинации идентификатора пользователя, действия и уникального идентификатора ресурса (или его типа). В нашем примере на FastAPI можно использовать @lru_cache или Redis:

python

from functools import lru_cache

@lru_cache(maxsize=1024)
def get_cached_decision(cache_key: str) -> dict:
    # В реальности здесь обращение к Redis
    pass

# При обработке запроса:
cache_key = f"{subject['id']}:{action}:{resource.get('type')}:{resource.get('collection_id','')}"
decision = get_cached_decision(cache_key)
if not decision:
    decision = await authorize(subject, action, resource)
    # сохраняем в кэш с TTL 60 секунд

Кэш инвалидируется при изменении политик (PEP получает сигнал от СУД) или по истечении TTL.

Таким образом, благодаря интеграции с Keycloak и распределённой архитектуре PDP/PEP мы получаем систему, где каждый HTTP-вызов к STAC-API автоматически проверяется на соответствие политикам, а все решения кэшируются для высокой производительности.

7. Заключение

STAC — это не просто формат описания данных. Это основа для построения целых экосистем взаимодействия с пространствено-временной информацией. Универсальные браузеры демонстрируют силу стандартизации, позволяя исследовать любые каталоги через единый интерфейс.

Однако настоящая сила стандарта раскрывается, когда он ��тановится не целью, а фундаментом. Реализация STAC-API с интегрированной системой управления доступом на базе PDP/PEP показывает, как открытые спецификации могут быть адаптированы для строгих корпоративных требований. Мы получаем гибкость декларативных политик, независимость сервисов авторизации и прозрачное для пользователя кэширование.

В следующей, четвёртой части цикла, мы наконец обратимся к теме, интересовавшей многих читателей с самого начала — семантической паутине (Linked Data) и онтологиям. Мы разберём, как расширение STAC sat (Scientific Annotation Tool) позволяет превращать сухие JSON-метаданные в семантическую сеть взаимосвязанных понятий, как аннотировать данные связями с внешними онтологиями (например, OGC Definitions Server) и какие новые возможности поиска и анализа это открывает в части более широкой вселенной — машиночитаемого знания.

P.S.

Уважаемые читатели!

Мы ищем текущих владельцев русского и английского сайтов проекта Маяк, для реанимации и поддержки ресурсов. Нужна ваша помощь!

Требуется помощь!

В 2017 году мы с командой убежденных единомышленников запустили с космодрома Байконур первый и до сих пор единственный в России полностью частный космический аппарат формата cubesat - "Маяк", разработанный исключительно на средства, собранные путем нескольких краудфандинговых компаний. Целью проекта была - популяризация космонавтики и космических исследований в России, а также повышение привлекательности научно-технического образования в среде молодежи.

Проект состоялся, Маяк был запущен, и спустя четыре года уже даже сошел с орбиты.

К сожалению, за прошедшие годы судьба развела членов команды Маяка по разным другим проектам, и поддержка двух основных интернет-ресурсов cosmomayak.com и cosmomayak.ru была заброшена...

В 2025 г., после торжественной передачи второго рабочего экземпляра спутника Маяк в Музей космонавтики мы решили вновь объединить усилия и реанимировать наши два сайта для истории. А может быть и для возобновлении работы)

К сожалению, пока не удается найти контакты текущих владельцев доменов (а их кто-то регулярно оплачивает). Предполагаем, это кто-то из разработчиков, из старой команды Алекандра Панова, нашего PR-директора. Англоязычный сайт еще частично работает, а вот русскоязычный закрылся парковочной страницей какого-то интернет-магазина.

Будем признательны любой информации и контактам, чтобы выйти на текущих владельцев доменов, с целью выкупа и реанимации ресурсов и контента. Также очень интересуют действующие архивы русского и английского сайтов Маяка, если таковые у кого-то из старых веб-мастеров остались.

Заранее спасибо, друзья!

Цикл статей про STAC

  1. Часть 1. STAC — знакомство: Новая эпоха в работе с данными о Земле

  2. Часть 2. STAC — знакомство: Универсальный язык для геоинформационных систем и не только

  3. Часть 3. STAC — знакомство: Браузеры, API и управление доступом к пространственным данным