Перейти к содержанию

Слои и тайлы

Слой — это горизонтальный срез карты со своим местом в порядке отрисовки. Собственные слои MapKit названы в LayerIds; свои вы добавляете рядом с ними — либо со своими тайлами, либо с отдельной коллекцией объектов карты.

Свой тайловый слой

Map.addTileLayer принимает идентификатор, настройки слоя и колбэк, который настраивает источник данных. Колбэк выполняется один раз, при создании слоя.

val layer: Layer = map.addTileLayer(
    layerId = "weather",
    layerOptions = LayerOptions(
        transparent = true,
        nightModeAvailable = false,
        cacheable = true,
    ),
) { builder ->
    builder.setTileUrlProvider(
        UrlProvider { tileId, _, _ ->
            "https://tiles.example.com/${tileId.z}/${tileId.x}/${tileId.y}.png"
        },
    )
    builder.setProjection(Projections.wgs84Mercator)
    builder.setZoomRanges(listOf(ZoomRange(zMin = 0, zMax = 19)))
    builder.setTileFormat(TileFormat.PNG)
}

layerId заодно определяет место слоя: MapKit упорядочивает слои по идентификатору вместе со своими, поэтому идентификатор, сопоставленный с LayerIds, — это способ оказаться выше или ниже слоя пробок, зданий или объектов карты.

LayerOptions

Параметр По умолчанию Что делает
active true неактивный слой ничего не рисует и не запрашивает тайлы
nightModeAvailable false у слоя есть ночной вариант
cacheable true тайлы сохраняются на диск
animateOnActivation true активация проявляется плавно
tileAppearingAnimationDuration 150.milliseconds длительность проявления тайла
overzoomMode DISABLED ENABLED / WITH_PREFETCH закрывают пропуски тайлами соседних зумов
transparent false сквозь слой видно нижние
versionSupport false слой версионируется

Отдавать тайлы самому

setTileProvider заменяет URL функцией, которая возвращает байты — так отдают тайлы, отрисованные на устройстве или прочитанные из файла в поставке:

builder.setTileProvider(
    TileProvider { tileId, version, _, etag ->
        RawTile(
            version = version,
            features = emptyMap(),
            etag = etag,
            useCache = RawTile.UseCache.YES,
            state = RawTile.State.OK,
            rawData = render(tileId),
        )
    },
)

Возвращайте RawTile.State.NOT_MODIFIED, когда etag говорит, что тайл не изменился, и RawTile.State.ERROR, когда его не удалось получить.

Управление слоем после создания

Сам Layer умеет только удаляться; всё остальное — на его DataSourceLayer:

val dataSource = layer.dataSourceLayer()

dataSource.isActive = false
dataSource.setStyle(id = 0, style = """[{"stylers": {"opacity": 0.5}}]""")
dataSource.resetStyles()
dataSource.clear()

layer.remove()

О происходящем со слоем сообщают две подписки. Обе принимают одного слушателя, а null его снимает:

private val layerLoadedListener = LayerLoadedListener { println("tiles on screen") }
private val dataSourceListener = DataSourceListener { println("data source updated") }

dataSource.setLayerLoadedListener(layerLoadedListener.asWeakRef())
dataSource.setDataSourceListener(dataSourceListener.asWeakRef())

Проекции

Projections.wgs84Mercator — то, чем пользуются тайлы Яндекса; Projections.sphericalMercator — web-mercator, который отдаёт большинство сторонних тайловых серверов. map.projection() возвращает проекцию самой карты и переводит мировые координаты в тайловые и обратно:

val xy: XYPoint = map.projection().worldToXY(Point(55.751225, 37.629540), zoom = 15)
val back: Point = map.projection().xyToWorld(xy, zoom = 15)

Слой объектов карты

Map.addMapObjectLayer возвращает RootMapObjectCollection, который не является коллекцией карты по умолчанию и находится на названном вами слое — так группу объектов помещают выше или ниже того, что рисует MapKit.

val routeObjects = map.addMapObjectLayer("my-routes")
routeObjects.addPolyline(Polyline(points))

Одна коллекция на идентификатор, на всю жизнь карты

MapKit связывает коллекцию с идентификатором слоя и не умеет её отвязывать, поэтому повторный запрос того же идентификатора бросает исключение. Создайте её один раз и держите; чтобы опустошить, используйте clear().

Compose-версия MapObjectLayer делает это за вас — она кеширует коллекцию по идентификатору и только очищает её, когда composable покидает композицию.

Пробки

class TrafficController(mapKit: MapKit, mapWindow: MapWindow) {

    private val layer = mapKit.createTrafficLayer(mapWindow)

    private val trafficListener = TrafficListener(
        onTrafficChanged = { level ->
            if (level != null) show(level.level, level.color)
        },
        onTrafficLoading = { showSpinner() },
        onTrafficExpired = { showStale() },
    )

    init {
        layer.isTrafficVisible = true
        layer.addTrafficListener(trafficListener.asWeakRef())
    }
}

TrafficLevel — это число от 0 до 10 плюс TrafficColor (GREEN, YELLOW, RED). onTrafficChanged вызывается с null, пока уровень неизвестен.

Слой принимает и JSON-стили:

layer.setTrafficStyle("""[{"stylers": {"opacity": 0.6}}]""")
layer.setTrafficStyle(id = 1, style = dimJams)
layer.resetTrafficStyles()

Оба сеттера стиля возвращают, был ли стиль принят.

В Compose

Оба вида слоёв — это composable-функции, которые снимают себя при выходе из композиции:

YandexMap(modifier = Modifier.fillMaxSize()) {
    TrafficLayer(onTrafficChanged = { level = it })

    TileLayer(
        layerId = "weather",
        options = LayerOptions(transparent = true),
        onLayerLoaded = { println("weather tiles are on screen") },
    ) { builder ->
        builder.setTileUrlProvider(
            UrlProvider { tileId, _, _ ->
                "https://tiles.example.com/${tileId.z}/${tileId.x}/${tileId.y}.png"
            },
        )
        builder.setProjection(Projections.wgs84Mercator)
        builder.setZoomRanges(listOf(ZoomRange(zMin = 0, zMax = 19)))
        builder.setTileFormat(TileFormat.PNG)
    }

    MapObjectLayer(layerId = "my-routes") {
        Polyline(state = rememberPolylineState(route))
    }
}

Параметры описаны в Обзоре Compose.