Объекты карты¶
Всё, что вы рисуете на карте, живёт в коллекции. map.mapObjects — корневая, это
RootMapObjectCollection, а addCollection() создаёт вложенные.
val objects = map.mapObjects
val pois = objects.addCollection()
Вкладывать стоит: коллекция сама является объектом карты, поэтому её сокрытие прячет всю группу,
zIndex действует на всё содержимое, а clear() опустошает её одним вызовом.
Метка¶
val placemark = objects.addPlacemark().apply {
geometry = Point(55.751225, 37.629540)
setIcon(icon)
setIconStyle(IconStyle(anchor = PointF(0.5f, 1.0f), scale = 1.2f))
userData = poi
}
addPlacemark() не принимает аргументов
MapKit объявил перегрузки addPlacemark(point, icon) устаревшими; объект сначала создаётся, а
настраивается потом. Исключение — PlacemarkCreatedCallback.
geometry, direction и opacity — свойства. setIcon(image, style) задаёт иконку и её стиль
сразу, а setScaleFunction(points) делает размер зависимым от зума: каждый PointF — это пара
(зум, масштаб):
placemark.setScaleFunction(listOf(PointF(10f, 0.5f), PointF(16f, 1f)))
Текст¶
placemark.setText(
text = "Красная площадь",
style = TextStyle(
size = 10f,
color = Color.fromArgb(0xFF212121.toInt()),
placement = TextStyle.Placement.BOTTOM,
offset = 4f,
),
)
placemark.text возвращает живой PlacemarkText, у которого text и style можно менять позже.
Объекты представления¶
setIcon — это сокращение поверх представления метки. Если запросить представление напрямую, можно
больше: составную иконку из нескольких именованных частей, 3D-модель или анимацию.
val icon: Icon = placemark.useIcon()
icon.setImage(image, IconStyle(zIndex = 10f))
icon.style = IconStyle(scale = 2f)
val composite: CompositeIcon = placemark.useCompositeIcon()
composite.setIcon(name = "pin", image = pinImage, style = IconStyle(anchor = PointF(0.5f, 1f)))
composite.setIcon(name = "badge", image = badgeImage, style = IconStyle(zIndex = 1f))
composite.setIconStyle("badge", IconStyle(isVisible = false))
composite.removeIcon("badge")
val model: Model = placemark.useModel()
model.modelStyle = ModelStyle(
scale = 1f,
unitType = ModelStyle.UnitType.NORMALIZED,
renderMode = ModelStyle.RenderMode.USER_MODEL,
)
model.setData(gltfDataProvider, Callback { println("model loaded") })
val animation: PlacemarkAnimation = placemark.useAnimation()
animation.setIcon(animatedImage, IconStyle())
animation.isReversed = false
animation.play(Callback { println("finished") })
animation.pause()
animation.resume()
animation.stop()
Вызов useIcon() после useModel() заменяет представление — у метки оно ровно одно.
Ломаная¶
val polyline = objects.addPolyline(Polyline(points))
polyline.style = LineStyle(
strokeWidth = 5f,
outlineWidth = 1f,
outlineColor = Color.fromArgb(0xFF1B5E20.toInt()),
dashLength = 8f,
gapLength = 4f,
)
polyline.setStrokeColor(Color.fromArgb(0xFF4CAF50.toInt()))
Раскраска сегментов¶
У ломаной из n точек n − 1 сегментов, и каждый сегмент несёт индекс палитры, а не цвет. Сначала задайте палитру, потом раздайте индексы:
polyline.setPaletteColor(0, Color.fromArgb(0xFF4CAF50.toInt()))
polyline.setPaletteColor(1, Color.fromArgb(0xFFFFC107.toInt()))
polyline.setPaletteColor(2, Color.fromArgb(0xFFF44336.toInt()))
polyline.setStrokeColors(listOf(0, 1, 2, 1))
getStrokeColor возвращает индекс
getStrokeColor(segmentIndex) отдаёт индекс палитры этого сегмента; разрешите его через
getPaletteColor(colorIndex). setStrokeColor(color) — другое дело: это настоящий цвет, и он
записывает нулевой элемент палитры, которым по умолчанию пользуются все сегменты.
Перегрузка с весами распределяет цвета по ломаной пропорционально длине, а не по сегментам:
polyline.setStrokeColors(paletteIndices = listOf(0, 2), weights = listOf(0.7, 0.3))
Выделение и сокрытие участков¶
val part = Subpolyline(
begin = PolylinePosition(segmentIndex = 0, segmentPosition = 0.0),
end = PolylinePosition(segmentIndex = 4, segmentPosition = 0.5),
)
polyline.select(Color.fromArgb(0xFF2196F3.toInt()), part)
polyline.hide(part)
Стрелки¶
val arrow = polyline.addArrow(
position = PolylinePosition(segmentIndex = 2, segmentPosition = 0.0),
length = 12f,
fillColor = Color.fromArgb(0xFF212121.toInt()),
)
arrow.outlineWidth = 1f
arrow.isVisible = true
polyline.arrows перечисляет их.
Полигон¶
val polygon = objects.addPolygon(
Polygon(
outerRing = LinearRing(outerPoints),
innerRings = listOf(LinearRing(holePoints)),
),
).apply {
fillColor = Color.fromArgb(0x334CAF50)
strokeColor = Color.fromArgb(0xFF4CAF50.toInt())
strokeWidth = 2f
isGeodesic = true
}
Заливкой может быть повторяющееся изображение, статичное или анимированное:
polygon.setPattern(hatchImage, scale = 1f)
polygon.setPattern(animatedHatch, scale = 1f)
polygon.resetPattern()
Окружность¶
objects.addCircle(Circle(center = Point(55.751225, 37.629540), radius = 500f)).apply {
fillColor = Color.fromArgb(0x332196F3)
strokeColor = Color.fromArgb(0xFF2196F3.toInt())
strokeWidth = 2f
}
Радиус — в метрах.
Общее для всех объектов¶
mapObject.isVisible = false
mapObject.zIndex = 10f
mapObject.isDraggable = true
mapObject.userData = poi
parent — коллекция, которой принадлежит объект, а isValid становится false, как только объект
удалён.
Сокрытие можно анимировать, с колбэком по завершении анимации:
mapObject.setVisible(
visible = false,
animation = Animation(Animation.Type.SMOOTH, 300.milliseconds),
onFinished = Callback { println("hidden") },
)
userData¶
userData — это обычный Any?, который MapKit хранит рядом с объектом и отдаёт обратно в каждом
колбэке. getCastedUserData<T>() читает его типизированно:
data class Poi(val id: String, val title: String)
placemark.userData = Poi("1", "Красная площадь")
val tapListener = MapObjectTapListener { mapObject, _ ->
val poi = mapObject.getCastedUserData<Poi>() ?: return@MapObjectTapListener false
open(poi)
true
}
Нажатия и перетаскивание¶
class ObjectsController(private val objects: MapObjectCollection) {
private val tapListener = MapObjectTapListener { mapObject, point ->
select(mapObject)
true
}
private val dragListener = MapObjectDragListener(
onMapObjectDragStart = { it.opacity = 0.5f },
onMapObjectDrag = { _, point -> preview(point) },
onMapObjectDragEnd = { it.opacity = 1f },
)
fun add(point: Point, icon: ImageProvider) {
objects.addPlacemark().apply {
geometry = point
setIcon(icon)
isDraggable = true
addTapListener(tapListener.asWeakRef())
setDragListener(dragListener.asWeakRef())
}
}
}
Вернуть true из слушателя нажатия — остановить событие; false пропускает его дальше, до
InputListener карты. Оба слушателя лежат в полях, потому что подписки слабые — см.
Слушатели и WeakRef.
PlacemarkMapObject.opacity — свойство метки; пример выше использует его, чтобы притушить объект во
время перетаскивания.
Работа с коллекцией¶
objects.remove(mapObject)
objects.clear()
addListener сообщает, что добавляется и удаляется — MapKit добавляет объекты асинхронно, когда в
дело вступает кластеризация:
private val collectionListener = MapObjectCollectionListener(
onMapObjectAdded = { println("added $it") },
onMapObjectRemoved = { println("removed $it") },
)
objects.addListener(collectionListener.asWeakRef())
placemarksStyler задаёт функцию масштаба, общую для всех меток коллекции, вместо того чтобы
задавать её каждой по отдельности:
objects.placemarksStyler.setScaleFunction(listOf(PointF(10f, 0.5f), PointF(16f, 1f)))
Обход дерева¶
traverse обходит всё содержимое коллекции, спускаясь во вложенные, когда стартовый колбэк
возвращает true:
private val visitor = MapObjectVisitor(
onPlacemarkVisited = { println("placemark at ${it.geometry}") },
onPolylineVisited = { },
onPolygonVisited = { },
onCircleVisited = { },
onCollectionVisitStart = { true },
onCollectionVisitEnd = { },
onClusterizedCollectionVisitStart = { true },
onClusterizedCollectionVisitEnd = { },
)
objects.traverse(visitor.asWeakRef())
Конфликты подписей¶
Корневая коллекция решает, что происходит при наложении подписей:
map.mapObjects.conflictResolutionMode = ConflictResolutionMode.MAJOR
Кластеризация¶
ClusterizedPlacemarkCollection группирует близкие метки в одну иконку. ClusterListener
вызывается для каждого кластера, который строит MapKit, и именно там задаётся иконка кластера.
class ClusterController(
private val objects: MapObjectCollection,
private val clusterIcon: ImageProvider,
) {
private val clusterTapListener = ClusterTapListener { cluster ->
zoomTo(cluster.placemarks.map { it.geometry })
true
}
private val clusterListener = ClusterListener { cluster ->
cluster.appearance.setIcon(clusterIcon)
cluster.appearance.setText("${cluster.size}")
cluster.addClusterTapListener(clusterTapListener.asWeakRef())
}
private val collection = objects.addClusterizedPlacemarkCollection(clusterListener.asWeakRef())
fun show(points: List<Point>, icon: ImageProvider) {
collection.addPlacemarks(points, icon, IconStyle())
collection.clusterPlacemarks(clusterRadius = 60.0, minZoom = 15)
}
}
clusterPlacemarks нужно вызывать заново после изменения содержимого — MapKit не перекластеризует
сам. clusterRadius измеряется в размерах метки, minZoom — зум, выше которого кластеризация
прекращается.
cluster.appearance — это PlacemarkMapObject, поэтому к нему применимо всё с этой страницы.
Массовое создание меток¶
addEmptyPlacemarks(points) добавляет объекты без иконок, что заметно дешевле на большом наборе; их
вы заполняете сами.
val placemarks = collection.addEmptyPlacemarks(points)
placemarks.forEachIndexed { index, placemark ->
placemark.setIcon(icons[index])
placemark.userData = pois[index]
}
PlacemarkCreatedCallback — второй массовый путь: MapKit сам создаёт объект и зовёт вас с ним, что
позволяет ему отложить работу.
private val createdCallback = PlacemarkCreatedCallback { placemark ->
placemark.setIcon(icon)
}
collection.addPlacemark(createdCallback)
Анимированные изображения¶
AnimatedImageProvider питает анимированные иконки и заливки полигонов. Он собирается либо из
данных, либо из кадров:
val fromData = AnimatedImageProvider.fromByteArray(bytes)
val fromFile = AnimatedImageProvider.fromFile(path)
val built = AnimatedImageProvider.fromAnimatedImage(
AnimatedImage(loopCount = 0).apply {
addFrame(frameOne, 100.milliseconds)
addFrame(frameTwo, 100.milliseconds)
},
)
loopCount = 0 означает бесконечный цикл. Откуда берётся статичный ImageProvider, описано на
странице Изображения.