Map
Creating a map
To create a map, add a container for MapView to the activity layout and create and configure the map programmatically.
- For SDK version 14.0.0 or later
- For SDK version 13.x
Create a map using DefaultMapControllerViewModel and pass the initial settings to MapControllerOptions.
<FrameLayout
android:id="@+id/mapContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
private lateinit var mapViewModel: DefaultMapControllerViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val sdkContext = DGis.initialize(applicationContext, apiKeys)
setContentView(R.layout.activity_main)
mapViewModel = DefaultMapControllerViewModel(
sdkContext,
MapControllerOptions(
position = CameraPosition(
point = GeoPoint(55.740444, 37.619524),
zoom = Zoom(16.0f)
)
)
)
val mapView = MapView(this).also { view ->
view.setMapControllerViewModel(mapViewModel)
}
findViewById<FrameLayout>(R.id.mapContainer).addView(mapView)
lifecycleScope.launch {
val map = mapViewModel.awaitMap()
// Actions with the map
val camera = map.camera
}
}
override fun onDestroy() {
mapViewModel.close()
super.onDestroy()
}
<ru.dgis.sdk.map.MapView
android:id="@+id/mapView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:dgis_cameraTargetLat="55.740444"
app:dgis_cameraTargetLng="37.619524"
app:dgis_cameraZoom="16.0"
/>
You can specify initial coordinates (cameraTargetLat for latitude and cameraTargetLng for longitude) and zoom level (cameraZoom).
MapView can also be created programmatically. In that case, you can specify settings as a MapOptions object.
To get the Map object, call the getMapAsync() method:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val sdkContext = DGis.initialize(applicationContext, apiKeys)
setContentView(R.layout.activity_main)
val mapView = findViewById<MapView>(R.id.mapView)
lifecycle.addObserver(mapView)
mapView.getMapAsync { map ->
// Actions with the map
val camera = map.camera
}
}
Map data sources
In some cases, to add objects to the map, you need to create a special object - a data source. Data sources act as object managers: instead of adding objects to the map directly, you add a data source to the map and add/remove objects from the data source.
There are different types of data sources: moving markers, routes that display current traffic condition, custom geometric shapes, etc. Each data source type has a corresponding class.
The general workflow of working with data sources looks like this:
// Create a data source
val source = MyMapObjectSource(
sdkContext,
...
)
// Add the data source to the map
map.addSource(source)
// Add and remove objects from the data source
source.addObject(...)
source.removeObject(...)
To remove a data source and all objects associated with it from the map, call the removeSource() method:
map.removeSource(source)
You can get the list of all active data sources using the map.sources property.
Connecting a raster map
By default, the SDK uses a vector map with tiles provided by the Map Tiles API. If you want to use a raster map, you can connect it in two ways:
- Overlay raster tiles from an external source on top of the standard vector map: for example, to enrich the map with additional data.
- Connect the Raster Tiles API to use a Urbi raster map instead of a vector one: for example, to display the map on a low-performance device.
To connect a raster map:
-
If you plan to use the Urbi raster map, make sure the Raster Tiles API is activated for your key.
-
Create a style layer:
-
Open the Style editor.
-
Open the required style or create a new one.
-
In the Layers section, click
icon.
-
Scroll down, enable the Dynamic layers toggle, and select the Raster layer type.
-
On the Data tab, select JSON — add manually.
-
Add a
db_sublayerattribute with a unique layer identifier (e.g.,my_raster_layer) by inserting the following code into the text field:["match", ["get", "db_sublayer"], ["my_raster_layer"], true, false]This identifier will be used later to reference the style layer from code.
-
Configure other style parameters in the corresponding tabs.
-
-
Create a RasterUrlTemplate object and pass the raster tile source URL as a DefaultRasterUrlTemplate or WmsRasterUrlTemplate object (for the WMS standard). Example of connecting the Raster Tiles API:
val sourceTemplate = RasterUrlTemplate(
DefaultRasterUrlTemplate("https://tile{n}.maps.2gis.com/v2/tiles/{tileset}/{z}/{x}/{y}.png?key={key}")
) -
Create a data source using RasterTileSource, specify the style layer identifier in the
sublayerNameparameter and the createdRasterUrlTemplateobject in thesourceTemplateparameter:val rasterSource = RasterTileSource(
sdkContext,
"my_raster_layer",
sourceTemplate
) -
Add the source to the map:
map.addSource(rasterSource)
Offline mode
To configure the offline mode of the map:
-
Complete preparation steps to enable the map to work with preloaded data.
-
Add a map data source. Use the createDgisSource() function and set one of the following values to the
workingModeparameter:OFFLINE- to always use preloaded data only.HYBRID_ONLINE_FIRST- to primarily use online data from Urbi servers. Preloaded data is used only if it matches online data or data cannot be obtained from the servers.HYBRID_OFFLINE_FIRST- to primarily use preloaded data. Online data from Urbi servers is used only if preloaded data is missing.
sourcesList = listOf(
DgisSource.createDgisSource(sdkContext, workingMode = DgisSourceWorkingMode.OFFLINE)
) -
When creating a map, specify the created source in map options:
- For SDK version 14.0.0 or later
- For SDK version 13.x
val mapViewModel = DefaultMapControllerViewModel(
sdkContext,
MapControllerOptions(
sources = sourceList
)
)
// Create a map with the specified settings
mapView = MapView(this).also { view ->
view.setMapControllerViewModel(mapViewModel)
}
mapContainer.addView(mapView)val mapOptions = MapOptions().apply {
sources = sourceList
}
// Create a map with the specified settings
mapView = MapView(this, mapOptions)
mapContainer.addView(mapView)
Adding objects
To add dynamic objects to the map (such as markers, lines, circles, and polygons), you must first create a MapObjectManager object, specifying the map instance. Deleting an object manager removes all associated objects from the map, so do not forget to save it in activity.
mapObjectManager = MapObjectManager(map)
After you have created an object manager, you can add objects to the map using the addObject() and addObjects() methods. For each dynamic object, you can specify a userData field to store arbitrary data. Object settings can be changed after their creation.
To remove objects from the map, use removeObject() and removeObjects(). To remove all objects, call the removeAll() method.
MapObjectManager is an object container. As long as objects must be presented on the map, MapObjectManager must be stored on the class level.
Marker
To add a marker to the map, create a Marker object, specifying the required options in MarkerOptions, and pass it to the addObject() method of the object manager.
The only required parameter is the coordinates of the marker (position).
val marker = Marker(
MarkerOptions(
position = GeoPointWithElevation(
latitude = 55.752425,
longitude = 37.613983
)
)
)
mapObjectManager.addObject(marker)
To change the marker icon, specify an Image object as the icon parameter. You can create Image using the following functions:
- imageFromAsset()
- imageFromBitmap()
- imageFromCanvas()
- imageFromResource()
- imageFromSvg()
- imageFromLottieJSON()
val icon = imageFromResource(sdkContext, R.drawable.ic_marker)
val marker = Marker(
MarkerOptions(
position = GeoPointWithElevation(
latitude = 55.752425,
longitude = 37.613983
),
icon = icon
)
)
To update settings of an already created marker, set new values to the Marker object parameters: see the full list of available parameters in the Marker description.
// Change marker coordinates
marker.position = GeoPointWithElevation(GeoPoint(55.74460317215391, 37.63435363769531))
// Change the icon
marker.icon = imageFromAsset(context, "icon.json")
// Change the icon anchor point
marker.anchor = Anchor(x=0.5f, 0.5f)
// Change the icon opacity
marker.iconOpacity = Opacity(value = 1.0f)
// Change the marker label
marker.text = "text"
// Change the label style
marker.textStyle = TextStyle(textHorizontalAlignment = textHorizontalAlignment)
// Change the marker draggability flag
marker.draggable = false
// Change the target marker width
marker.iconWidth = LogicalPixel(value = 1.0f)
// Change the marker rotation angle relative to the north direction
marker.iconMapDirection = MapDirection(0.5)
// Change the flag of animating the marker appearance
marker.iconAnimationMode = AnimationMode.LOOP
Line
To draw a line (polyline) on the map, create a Polyline object, specify line settings in PolylineOptions, and pass the object to the addObject() method of the object manager.
You can specify the coordinates of the line vertices and set the line width, color, and other parameters.
// Coordinates of the line vertices
val points = listOf(
GeoPoint(latitude = 55.7513, longitude = 37.6236),
GeoPoint(latitude = 55.7405, longitude = 37.6235),
GeoPoint(latitude = 55.7439, longitude = 37.6506)
)
// Create a line
val polyline = Polyline(
PolylineOptions(
points = points,
width = 2.lpx, // The .lpx property converts an integer to a LogicalPixel object
color = Color(255, 0, 0)
)
)
// Add the line to the map
mapObjectManager.addObject(polyline)
To change settings of a created line, set new values for the Polyline object parameters: see the full list of available parameters in the Polyline description.
// Change the coordinates of the line vertices
val newPoints = listOf(
GeoPoint(latitude = 54.7513, longitude = 36.6236),
GeoPoint(latitude = 54.7405, longitude = 36.6235),
GeoPoint(latitude = 54.7439, longitude = 36.6506)
)
polyline.points = newPoints
// Change the line width
polyline.width = 2.lpx
// Change the line color
polyline.color = Color(255, 0, 0)
// Erase part of the line
polyline.erasedPart = 0.4
// Change dashed line parameters
polyline.dashedPolylineOptions = DashedPolylineOptions(
dashLength = 5.lpx,
dashSpaceLength = 5.lpx
)
The line color will smoothly transition from one to another. To create a gradient line, specify the gradient options in the GradientPolylineOptions property of the Polyline object.
For example, a line of three points forms two segments. To color the first segment red and the second green, create a palette of three colors (red, yellow, and green) and specify the color indices for each line segment (0 for red, 2 for green). The number of indices must equal the number of segments (the number of points minus 1). The transition between segments will be a smooth gradient through yellow, since it lies between red and green in the palette.
The transition length between colors is set using the gradientLength parameter: the higher the value, the smoother the transition between colors.
// Color palette for the gradient
val colors = listOf(
Color(255, 0, 0), // Red (index 0)
Color(255, 255, 0), // Yellow (index 1)
Color(0, 255, 0) // Green (index 2)
)
// Color indices for each line segment
val colorIndices = listOf<Byte>(0, 2)
// Change gradient line parameters
polyline.gradientPolylineOptions = GradientPolylineOptions(
borderWidth = 10.lpx, // Width of the line border
secondBorderWidth = 5.lpx, // Width of the second line border
gradientLength = 10.lpx, // Length of the gradient transition between colors
borderColor = Color(255, 0, 0), // Color of the line border
secondBorderColor = Color(0, 255, 0), // Color of the second line border
colors = colors, // Line color palette
colorIndices = colorIndices.toByteArray() // Line color indices
)
Polygon
To draw a polygon on the map, create a Polygon object, specifying the required options in PolygonOptions, and pass it to the addObject() method of the object manager.
Coordinates for the polygon are specified as a two-dimensional list. The first sublist must contain the coordinates of the vertices of the polygon itself. The other sublists are optional and can be specified to create a cutout (a hole) inside the polygon (one sublist - one polygonal cutout).
val polygon = Polygon(
PolygonOptions(
contours = listOf(
// Vertices of the polygon
listOf(
GeoPoint(latitude = 55.72014932919687, longitude = 37.562599182128906),
GeoPoint(latitude = 55.72014932919687, longitude = 37.67555236816406),
GeoPoint(latitude = 55.78004852149085, longitude = 37.67555236816406),
GeoPoint(latitude = 55.78004852149085, longitude = 37.562599182128906),
GeoPoint(latitude = 55.72014932919687, longitude = 37.562599182128906)
),
// Coordinates for cutout inside the polygon
listOf(
GeoPoint(latitude = 55.754167897761, longitude = 37.62422561645508),
GeoPoint(latitude = 55.74450654680055, longitude = 37.61238098144531),
GeoPoint(latitude = 55.74460317215391, longitude = 37.63435363769531),
GeoPoint(latitude = 55.754167897761, longitude = 37.62422561645508)
)
),
borderWidth = 1.lpx
)
)
mapObjectManager.addObject(polygon)
To update settings of an already created polygon, set new values to the Polygon object parameters: see the full list of available parameters in the Polygon description.
// Change the coordinates of the polygon vertices
val points = mutableListOf<GeoPoint>()
polygon.contours = listOf(points)
// Change the polygon fill color
polygon.color = Color(255,0,0)
// Change the polygon stroke width
polygon.strokeWidth = 10.lpx
// Change the polygon stroke color
polygon.strokeColor = Color(255,0,0)
Circle
To draw a circle on the map, create a Circle object, specifying the required options in CircleOptions, and pass it to the addObject() method of the object manager.
// Circle parameters
val circle = Circle(
CircleOptions(
position = GeoPoint(55.754167897761, 37.62422561645508),
radius = 100.meter
)
)
// Create and add a circle
mapObjectManager.addObject(circle)
To update settings of an already created polygon, set new values to the Circle object parameters: see the full list of available parameters in the Circle description.
// Change the circle center
circle.position = GeoPoint(53.64564532, 43.5654345)
// Change the circle radius
circle.radius = 10.meter
// Change the circle fill color
circle.color = Color(255,0,0)
// Change the circle stroke width
circle.width = 10.lpx
// Change the circle stroke color
circle.strokeColor = Color(255,0,0)
3D models
Requirements and recommendations
For optimal map performance and correct display of 3D models on the map, follow the requirements and recommendations.
Mandatory requirements:
-
Format: glTF/GLB (
.gltf,.glbfiles). -
Mesh: it is preferred that the model contains one mesh (a geometric object on the scene). Using models with multiple meshes reduces map performance.
-
Texture:
- A mesh can have a texture or a fill with one color.
- The texture resolution must be a power of two (2, 4, 8, 16, 32, 64, 128, and 256). High resolution textures reduce map performance.
- The supported image formats for textures are
.png,.jpg, and.bmp. - Texture compression is not supported.
-
Instantiation: extensions for instantiation are not supported.
Usage and performance recommendations:
- Size: the optimal size for unique models is less than 10,000 vertices; for typical models (e.g., trees), it is from 200 to 2000 vertices.
- Texture: the optimal texture size for unique models is 256×256; for typical models, it is 64×64.
- Colors: if you do not customize your own map styles and only load models, it is recommended to match the texture colors of the models with the colors of the Urbi map.
- Compression: the Draco algorithm is recommended for model compression.
- Matrix animations: supported, but using them reduces map performance.
- PBR lighting model: supported, but using them reduces map performance.
- Local transformations: local transformations (translations, rotations, and scaling) are supported but reduce map performance.
Adding models
To add a 3D model to the map:
-
Transform your model to a ModelData object to be used in SDK using the modelDataFromAsset() method:
val modelData = modelDataFromAsset(sdkContext, "model.glb") -
Create a ModelSize object and specify the model size in one of the following ways:
-
To set a constant model size that will not change when the map scale changes, use the
logicalPixel()method and specify the size in logical pixels:val size = ModelSize.logicalPixel(LogicalPixel(256, 256)) -
To set a size that will depend on the map scale (when zooming out, the model will become smaller, and vice versa), use the
scale()method and specify the scaling factor as a ModelScale object:val modelSize = ModelSize.scale(ModelScale(0.5f))
-
-
Create a ModelMapObject specifying the required options in ModelMapObjectOptions and pass it to the
addObject()method of the object manager:// Configure 3D model parameters
val model = ModelMapObject(
ModelMapObjectOptions(
position = GeoPointWithElevation(GeoPoint(55.74, 37.63)),
data = modelData,
size = modelSize,
mapDirection = MapDirection(0.5),
opacity = Opacity(1.0f),
visible = true,
)
)
// Create and add a 3D model to the map
mapObjectManager.addObject(model)
To change the settings of an already added model, set new values for the parameters of the ModelMapObject: see the full list of available parameters in the description of ModelMapObject.
// Change coordinates of the 3D model center
model.position = GeoPointWithElevation(GeoPoint(55.74460317215391, 37.63435363769531))
// Change the opacity of the 3D model
model.opacity = Opacity(0.5f)
// Change the constant size of the 3D model
model.size = ModelSize.logicalPixel(LogicalPixel(128, 128))
// Change the scaling factor of the 3D model
model.size = ModelSize.scale(1.0f)
// Change the rotation angle of the 3D model
model.mapDirection = MapDirection(0.5)
// Change the animation settings of the 3D model
model.animationSettings = AnimationSettings()
Adding multiple objects
Do not add a collection of objects to the map using the addObject method in a loop for the whole collection: this might lead to performance losses. To add a collection of objects, prepare the whole collection and add it using the addObjects method:
// Prepare the object collection
val markers = mutableListOf<Marker>()
val markerOptions = listOf(
MarkerOptions(<params>),
MarkerOptions(<params>),
MarkerOptions(<params>),
MarkerOptions(<params>),
...)
markerOptions.forEach({
markers.add(Marker(it))
})
// Add the object collection to the map
MapObjectManager.addObjects(markers)
Clustering
Clustering is the visual grouping of closely located objects (markers) into a single cluster as you zoom out the map. The grouping occurs gradually: the lower the zoom level, the fewer clusters are formed. A cluster is displayed as a marker with a number indicating the count of objects in the cluster.
To add markers to the map in the clustering mode, create an object manager (MapObjectManager) using the MapObjectManager.withClustering() method and specify the following properties:
- The map instance (
map). - The minimum distance between markers in logical pixels at zoom levels at which clustering is active (
logicalPixel). - The zoom level at which and above only individual markers are visible, without clusters (
maxZoom). - The zoom level at which and below no new clusters are formed (
minZoom). - A custom implementation of the SimpleClusterRenderer protocol, which is used to customize clusters in MapObjectManager.
val clusterRenderer = object : SimpleClusterRenderer {
override fun renderCluster(cluster: SimpleClusterObject): SimpleClusterOptions {
val textStyle = TextStyle(
fontSize = LogicalPixel(15.0f),
textPlacement = TextPlacement.RIGHT_TOP
)
val objectCount = cluster.objectCount
val iconMapDirection = if (objectCount < 5) MapDirection(45.0) else null
return SimpleClusterOptions(
icon,
iconWidth = LogicalPixel(30.0f),
text = objectCount.toString(),
textStyle = textStyle,
iconMapDirection = iconMapDirection,
userData = objectCount.toString()
)
}
}
mapObjectManager = MapObjectManager.withClustering(
map = map,
logicalPixel = LogicalPixel(80.0f),
maxZoom = Zoom(18.0f),
minZoom = Zoom(8.0f),
clusterRenderer = clusterRenderer
)
Once an object manager with clustering is created, you can add markers as usual using addObject() or addObjects().
Generalization
Generalization is the visual grouping of closely located objects (markers) such that, as you zoom out the map, a single "key" object is displayed instead of several markers. The grouping occurs gradually: the lower the zoom level, the fewer groups are formed.
To add markers to the map in the generalization mode, create an object manager (MapObjectManager) using the MapObjectManager.withGeneralization() method and specify the following properties:
- The map instance (
map). - The minimum distance between markers in logical pixels at zoom levels at which generalization is active (
logicalPixel). - The zoom level at which and above only individual markers are visible, without groups (
maxZoom). - The zoom level at which and below no new groups are formed (
minZoom).
mapObjectManager = MapObjectManager.withGeneralization(
map = map,
logicalPixel = LogicalPixel(80.0f),
maxZoom = Zoom(18.0f),
minZoom = Zoom(8.0f)
)
Once an object manager with generalization is created, you can add markers as usual using addObject() or addObjects().
Custom geolocation marker
You can replace the default geolocation marker on the map with a custom 3D model. See the requirements for uploaded models in the 3D models section.
Set a custom geolocation marker in one of the following ways:
- Via API
- In the Style editor
-
Convert the prepared 3D model into a ModelData object using the modelDataFromAsset() method:
val modelData = modelDataFromAsset(sdkContext, "model.glb") -
Add a geolocation data source to the map using the MyLocationMapObjectSource object and set the marker type to
MODELin themarkerTypeparameter:val locationSource = MyLocationMapObjectSource(
sdkContext,
MyLocationControllerSettings(),
MyLocationMapObjectMarkerType.MODEL)
map.addSource(locationSource) -
Set the 3D model using the setModelData() method:
locationSource.item.setModelData(modelData)
-
Open the Style editor.
-
Open the style or create a new one.
-
Upload the prepared 3D model:
-
In the top menu, open the Models tab and select Upload models.
-
Click Choose files and upload one or more model files.
-
Wait for the files to upload and click OK.
Now your models will be available for use in the Style editor.
-
-
In the list of configured layers, select the layer in the Dynamic objects folder:
- Foot GPS model - style for the geolocation marker. By default, the marker is a colored dot.
- Foot GPS model degraded - style for the geolocation marker with low location accuracy. By default, the marker is a gray dot.
- Foot GPS direction model - style for the direction indicator, used together with the geolocation marker. By default, the indicator is a colored cone.
-
Go to the Style tab and in the Model source dropdown, select the uploaded model.
-
If necessary, repeat steps 4–5 to set models for other layers.
Styling objects
You can configure a complex style for a dynamic object using the Style editor. For example, configure the maximum and minimum zoom level on which the object must be displayed.
-
Create a style layer for an object:
-
Open the Style editor.
-
Open the required style or create a new one.
-
In the Layers section, click
icon.
-
Select the layer type depending on the object type. For the current task, only Polygon, Line, and Point types are supported. See more about layers in the Layer types for the Mobile SDK article.
-
On the Data tab, scroll down and select JSON — add manually.
-
Add the
db_sublayerattribute with a unique layer identifier (for example,my_object_layer) by inserting the following code into the text field:["match", ["get", "db_sublayer"], ["my_object_layer"], true, false]This identifier will be later used in the code to refer to the style layer.
-
Configure other style parameters on corresponding tabs.
-
-
Create a dynamic object using GeometryMapObjectBuilder and specify the identifier of the created style layer in the
setObjectAttribute()method. For example, to add an object of the point type:geometryObject = GeometryMapObjectBuilder()
.setGeometry(PointGeometry(point)) // Point geometry
.setObjectAttribute("db_sublayer", "my_object_layer")
.createObject() // Create an object -
To display the object on the map, add it to the data source:
-
Create a source:
val geometrySource = GeometryMapObjectSourceBuilder(sdkContext).createSource() -
Add the source to the map:
- For SDK version 14.0.0 or later
- For SDK version 13.x
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
lifecycleScope.launch {
val map = mapViewModel.awaitMap()
map.addSource(geometrySource)
}
}override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
binding.mapView.getMapAsync {
it.addSource(geometrySource)
}
} -
Add the created object to the source:
geometrySource.addObject(geometryObject)
-
Selecting objects
Configuring styles
To make objects on the map visually react to selection, configure the styles to use different layer look using the "Add state dependency" function for all required properties (icon, font, color, and others):
To configure properties of the selected object, go to the "Selected state" tab:
Highlight selected objects on tap
Obtain information about the objects falling into the tap region using the getRenderedObjects() method like in the example of Getting objects using screen coordinates.
To highlight objects, call the setHighlighted() method that takes a list of IDs from the variable objects directory DgisObjectId. Inside the getRenderedObjects() method, you can get all data required to use this method, for example, the source of objects data and their IDs:
- For SDK version 14.0.0 or later
- For SDK version 13.x
lifecycleScope.launch {
val mapController = mapViewModel.awaitController()
val map = mapController.map
tapConnection = mapController.gestureRecognizer.tap.connect { point ->
map.getRenderedObjects(point).onResult { renderedObjects ->
// Get the closest object to the tap spot inside the specified radius
val dgisObject = renderedObjects
.firstOrNull { it.item.source is DgisSource && it.item.item is DgisMapObject }
?: return@onResult
// Save the data source of the object and its ID
val source = dgisObject.item.source as DgisSource
val id = (dgisObject.item.item as DgisMapObject).id
searchManager.searchByDirectoryObjectIds(listOf(id))
.onResult onDirectoryObjectReady@{ objects ->
val obj = objects.firstOrNull() ?: return@onDirectoryObjectReady
val entrancesIds = obj.entrances.map { entranceInfo ->
entranceInfo.id
}.toMutableList()
entrancesIds.add(id)
// Remove the highlight from the previously selected objects
source.setHighlighted(source.highlightedObjects, false)
// Highlight the obtained object and entrances
source.setHighlighted(entrancesIds, true)
}
}
}
}
override fun onTap(point: ScreenPoint) {
map.getRenderedObjects(point).onResult { renderedObjects ->
// Get the closest object to the tap spot inside the specified radius
val dgisObject = renderedObjects
.firstOrNull { it.item.source is DgisSource && it.item.item is DgisMapObject }
?: return@onResult
// Save the data source of the object and its ID
val source = dgisObject.item.source as DgisSource
val id = (dgisObject.item.item as DgisMapObject).id
searchManager.searchByDirectoryObjectId(id)
.onResult onDirectoryObjectReady@{ obj ->
obj ?: return@onDirectoryObjectReady
val entrancesIds = obj.entrances.map { entranceInfo ->
entranceInfo.id
}.toMutableList()
entrancesIds.add(id)
// Remove the highlight from the previously selected objects
source.setHighlighted(source.highlightedObjects, false)
// Highlight the obtained objects and entrances
source.setHighlighted(entrancesIds, true)
}
}
}
Controlling the camera
You can control the camera by accessing the map.camera property. See the Camera object for a full list of available methods and properties.
Changing camera position
You can change the position of the camera by calling the move() method, which initiates a flying animation. This method has three parameters:
position- new camera position (coordinates and zoom level). Additionally, you can specify the camera tilt and rotation (see CameraPosition).time- flight duration in seconds as a Duration object.animationType- type of animation to use (CameraAnimationType).
The call will return a Future object, which can be used to handle the animation finish event.
- For SDK version 14.0.0 or later
- For SDK version 13.x
lifecycleScope.launch {
val map = mapViewModel.awaitMap()
val cameraPosition = CameraPosition(
point = GeoPoint(latitude = 55.752425, longitude = 37.613983),
zoom = Zoom(16.0),
tilt = Tilt(25.0),
bearing = Arcdegree(85.0)
)
map.camera.move(cameraPosition, Duration.ofSeconds(2), CameraAnimationType.LINEAR).onResult {
Log.d("APP", "Camera flight finished.")
}
}
val mapView = findViewById<MapView>(R.id.mapView)
mapView.getMapAsync { map ->
val cameraPosition = CameraPosition(
point = GeoPoint(latitude = 55.752425, longitude = 37.613983),
zoom = Zoom(16.0),
tilt = Tilt(25.0),
bearing = Arcdegree(85.0)
)
map.camera.move(cameraPosition, Duration.ofSeconds(2), CameraAnimationType.LINEAR).onResult {
Log.d("APP", "Camera flight finished.")
}
}
To specify the duration of the flight, use the .seconds extension:
map.camera.move(cameraPosition, 2.seconds, CameraAnimationType.LINEAR)
For more precise control over the flight, you can create a flight controller that will determine the camera position at any given moment. To do this, implement the CameraMoveController interface and pass the created object to the move() method instead of the three parameters described previously.
Getting camera state
The current state of the camera (i.e., whether the camera is currently flying) can be obtained using the state property. See CameraState for a list of possible camera states.
val currentState = map.camera.state
You can subscribe to changes of camera state using the statefulChanges property:
- For SDK version 13.0.0 or later
- For SDK version 12.x
// Subscribe
val connection = map.camera.statefulChanges(CameraChangeReason.STATE) {
map.camera.state
}.connect { state ->
Log.d("APP", "Camera state has changed to ${state}")
}
// Unsubscribe
connection.close()
// Subscribe
val connection = map.camera.stateChannel.connect { state ->
Log.d("APP", "Camera state has changed to ${state}")
}
// Unsubscribe
connection.close()
Getting camera position
To obtain the current position of the camera, use the position property (see CameraPosition):
val currentPosition = map.camera.position
Log.d("APP", "Coordinates: ${currentPosition.point}")
Log.d("APP", "Zoom level: ${currentPosition.zoom}")
Log.d("APP", "Tilt: ${currentPosition.tilt}")
Log.d("APP", "Rotation: ${currentPosition.bearing}")
To subscribe to changes of camera position (and tilt or rotation angle), use the positionChannel property:
// Subscribe
val connection = map.camera.positionChannel.connect { position ->
Log.d("APP", "Camera position or tilt or rotation angle has changed.")
}
// Unsubscribe when it's no longer needed
connection.close()
Calculating camera position
To display an object or a group of objects on the map, you can use the calcPosition method to calculate camera position:
// To "see" two markers on the map:
// Create a geometry that covers both objects
val geometry = ComplexGeometry(listOf(PointGeometry(point1), PointGeometry(point2)))
// Calculate the required position
val position = calcPosition(map.camera, geometry)
// Use the calculated position
map.camera.move(position)
The example above returns a result similar to:
The markers are cut in half, because the method does not have any information about objects, only geometry. In this example, the marker is in its center. The method calculates the position to embed marker centers in the active area. The active area is shown as a red rectangle along the screen edges. To display markers in full, you can set the active area.
For example, set paddings from the top and bottom of the screen:
val geometry = ComplexGeometry(listOf(PointGeometry(point1), PointGeometry(point2)))
// Set top and bottom paddings so that markers are displayed in full
map.camera.padding = Padding(top = 100, bottom = 100)
val position = calcPosition(map.camera, geometry)
map.camera.move(position)
Result:
You can also set specific parameters for position calculation only. For example, you can set paddings only inside the position calculation method and get the same result.
val geometry = ComplexGeometry(listOf(PointGeometry(point1), PointGeometry(point2)))
// Set an active area only for the position calculation
val position = calcPosition(map.camera, geometry, screenArea = Padding(top = 100, bottom = 100))
map.camera.move(position)
Result:
You can see that the active area is not changed, but the markers are fully embedded. This approach may cause unexpected behavior, because the camera position specifies a geocoordinate that must be within the camera position spot (a red circle in the screen center). Parameters like padding, positionPoint, and size impact the location of this spot.
If parameters that shift the camera position spot are passed to a method during the position calculation, the result may lead to unexpected behavior. For example, if you set an asymmetric active area, the picture can shift a lot.
Example of setting the same position for different paddings:
The easiest solution is to pass all required settings to the camera and use only the camera and geometry to calculate position. If you use additional parameters that are not passed to the camera, edit the result to shift the picture in the right direction.
Configuring camera position point and camera viewpoint
You can control the map display on the screen, for example, change the size of the map viewport, while keeping the view position. To do this, use screen points: the camera position point BaseCamera.positionPoint and the camera viewpoint BaseCamera.viewPoint.
The camera position point (BaseCamera.positionPoint) is the screen point to which the camera is anchored with the set paddings (BaseCamera.padding). The point is set relative to the map viewport:
val cameraPositionPoint = CameraPositionPoint(0.5f, 0.5f)
map.camera.positionPoint = cameraPositionPoint
When the camera position point changes, the map viewport changes and the observation point CameraPosition.Point shifts. It is a terrain point in geographic coordinates that is located at the camera position point. The tilt angle CameraPosition.Tilt and the camera rotation angle CameraPosition.Bearing do not change:
The camera viewpoint (BaseCamera.viewPoint) is the screen point the camera is looking at. The point is set relative to the map viewport:
val cameraViewPoint = CameraViewPoint(0.5f, 0.5f)
map.camera.viewPoint = cameraViewPoint
When the camera viewpoint changes, the direction of view relative to the observation point changes. The observation point CameraPosition.Point does not shift. Also, the tilt angle CameraPosition.Tilt and the camera rotation angle CameraPosition.Bearing do not change:
visibleArea and visibleRect
Camera has two properties that both describe geometry of a visible area but in different ways. visibleRect has the GeoRect and is always a rectangle. visibleArea is an arbitrary geometry. You can tell the difference easily by examples of different camera tilt angles (relatively to the map):
-
With 45° tilt,
visibleRectandvisibleAreaare not equal: in this case,visibleRectis larger because it must be a rectangle containingvisibleArea.visibleAreais displayed in blue,visibleRect- in red.
-
With 0° tilt,
visibleAreaиvisibleRectoverlap, as you can tell from the color change.
Detecting if an object falls into the camera coverage area
Using the visibleArea property, you can get the map area covered by the camera as Geometry. Using the intersects() method, you can get the intersection of the camera coverage area with the required geometry:
// Check if a marker is into the visible map area
// val marker: Marker
val markerGeometry: Geometry = PointGeometry(marker.position)
val intersects: Boolean = map.camera.visibleArea.intersects(markerGeometry)
Floor plans
With the SDK, you can display floor plans of a building on the map and switch between floors. Detailed floor plans are available only for certain groups of buildings (for example, shopping malls). To get started, purchase access to Places API and additionally to information about floor plans. See the Getting started section for details.
The main object for working with floor plans is the IndoorManager class, which is accessible via the indoorManager map property.
To get information about the floor plans of the building currently displayed on the map, use the focusedBuilding property of the IndoorManager class or subscribe to the focusedBuildingChannel.
// Get information about the current building with floor plans
val currentBuilding = map.indoorManager.focusedBuilding
// Subscribe to changes of the current building
val connection = map.indoorManager.focusedBuildingChannel.connect { building ->
if (building != null) {
Log.d("APP", "Displaying building with ID: ${building.id}")
Log.d("APP", "Number of floors: ${building.levels.size}")
Log.d("APP", "Default floor: ${building.defaultLevelIndex}")
} else {
Log.d("APP", "No floor plans are displayed")
}
}
// Unsubscribe from notifications
connection.close()
Showing and hiding floor plans
To control the display of floor plans, use the setIndoorState() method of the IndoorManager class:
// Show floor plans
map.indoorManager.setIndoorState(IndoorManagerState.ENABLED)
// Hide floor plans
map.indoorManager.setIndoorState(IndoorManagerState.DISABLED)
Switching between floors
To switch between floors, specify the required floor index in the activeLevelIndex property of the IndoorBuilding object:
// Get information about the current building
val building = map.indoorManager.focusedBuilding
if (building != null) {
// Get information about all floors
val levels = building.levels
Log.d("APP", "Available floors:")
levels.forEachIndexed { index, levelInfo ->
Log.d("APP", "Floor $index: ${levelInfo.name}")
}
// Switch to the first floor (index 0)
building.activeLevelIndex = 0
// Switch to the last floor
building.activeLevelIndex = (levels.size - 1).toLong()
// Subscribe to changes of the active floor
val connection = building.activeLevelIndexChannel.connect { levelIndex ->
val levelName = levels[levelIndex.toInt()].name
Log.d("APP", "Active floor changed to: $levelName")
}
}
Creating a UI for floor control
To create a UI element for floor control, use the IndoorControlModel:
- For SDK version 14.0.0 or later
- For SDK version 13.x
class IndoorControlActivity : AppCompatActivity() {
private lateinit var mapViewModel: DefaultMapControllerViewModel
private var indoorModel: IndoorControlModel? = null
private var levelNamesConn: AutoCloseable? = null
private var activeLevelConn: AutoCloseable? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_indoor_control_model_example)
val sdkContext = initializeDGis(applicationContext)
mapViewModel = DefaultMapControllerViewModel(sdkContext, MapControllerOptions())
val mapView = MapView(this).also { view ->
view.setMapControllerViewModel(mapViewModel)
}
findViewById<FrameLayout>(R.id.mapContainer).addView(mapView)
lifecycleScope.launch {
val map = mapViewModel.awaitMap()
map.indoorManager.setIndoorState(IndoorManagerState.ENABLED)
setupIndoorModel(map)
}
}
private fun setupIndoorModel(map: Map) {
indoorModel = IndoorControlModel(map).also { model ->
levelNamesConn = model.levelNamesChannel.connect { names ->
if (names.isNotEmpty()) showLevels(names) else hideLevels()
}
activeLevelConn = model.activeLevelIndexChannel.connect { idx ->
idx?.toInt()?.let { highlightActive(it) }
}
}
}
private fun showLevels(levelNames: List<String>) {
val container = findViewById<LinearLayout>(R.id.levelsContainer)
container.visibility = View.VISIBLE
container.removeAllViews()
levelNames.forEachIndexed { index, name ->
val btn = Button(this).apply {
text = name
isAllCaps = false
setOnClickListener {
indoorModel?.activeLevelIndex = index.toLong()
}
}
container.addView(btn)
}
indoorModel?.activeLevelIndex?.toInt()?.let { highlightActive(it) }
}
private fun highlightActive(activeIndex: Int) {
val container = findViewById<LinearLayout>(R.id.levelsContainer)
for (i in 0 until container.childCount) {
val levelButton = container.getChildAt(i) as? Button ?: continue
val isSelectedLevel = (i == activeIndex)
levelButton.isSelected = isSelectedLevel
levelButton.alpha = if (isSelectedLevel) 1f else 0.55f
}
}
private fun hideLevels() {
findViewById<LinearLayout>(R.id.levelsContainer).apply {
removeAllViews()
visibility = View.GONE
}
}
override fun onDestroy() {
levelNamesConn?.close()
activeLevelConn?.close()
indoorModel = null
mapViewModel.close()
super.onDestroy()
}
}
class IndoorControlActivity : AppCompatActivity() {
private var map: Map? = null
private var indoorModel: IndoorControlModel? = null
private var levelNamesConn: AutoCloseable? = null
private var activeLevelConn: AutoCloseable? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_indoor_control_model_example)
initializeDGis(applicationContext)
val mapView = findViewById<MapView>(R.id.mapView)
lifecycle.addObserver(mapView)
mapView.getMapAsync {
map = it
it.indoorManager.setIndoorState(IndoorManagerState.ENABLED)
setupIndoorModel(it)
}
}
private fun setupIndoorModel(map: Map) {
indoorModel = IndoorControlModel(map).also { model ->
levelNamesConn = model.levelNamesChannel.connect { names ->
if (names.isNotEmpty()) showLevels(names) else hideLevels()
}
activeLevelConn = model.activeLevelIndexChannel.connect { idx ->
idx?.toInt()?.let { highlightActive(it) }
}
}
}
private fun showLevels(levelNames: List<String>) {
val container = findViewById<LinearLayout>(R.id.levelsContainer)
container.visibility = View.VISIBLE
container.removeAllViews()
levelNames.forEachIndexed { index, name ->
val btn = Button(this).apply {
text = name
isAllCaps = false
setOnClickListener {
indoorModel?.activeLevelIndex = index.toLong()
}
}
container.addView(btn)
}
indoorModel?.activeLevelIndex?.toInt()?.let { highlightActive(it) }
}
private fun highlightActive(activeIndex: Int) {
val container = findViewById<LinearLayout>(R.id.levelsContainer)
for (i in 0 until container.childCount) {
val levelButton = container.getChildAt(i) as? Button ?: continue
val isSelectedLevel = (i == activeIndex)
levelButton.isSelected = isSelectedLevel
levelButton.alpha = if (isSelectedLevel) 1f else 0.55f
}
}
private fun hideLevels() {
findViewById<LinearLayout>(R.id.levelsContainer).apply {
removeAllViews()
visibility = View.GONE
}
}
override fun onDestroy() {
super.onDestroy()
levelNamesConn?.close()
activeLevelConn?.close()
indoorModel = null
map?.close()
}
}
Highlighting floors
To highlight a specific floor (for example, with important objects), use the markedLevels property of the IndoorControlModel:
// Set marked floors
val markedLevels = setOf(
LevelId("level_1"),
LevelId("level_3")
)
indoorControlModel.markedLevels = markedLevels
// Check if a floor is marked
val isMarked = indoorControlModel.isLevelMarked(1) // true for the floor with index 1
Traffic jams on the map
To display the traffic jams layer on the map, create a TrafficSource and pass it to the addSource() method of the map.
val trafficSource = TrafficSource(sdkContext)
map.addSource(trafficSource)
Road events on the map
You can configure the display of road events from Urbi data on the map, as well as add your own events.
Displaying events on the map
To display the road events layer on the map, create a data source RoadEventSource and add it to the map using the addSource() method of the map:
val roadEventSource = RoadEventSource(sdkContext)
map.addSource(roadEventSource)
To remove the created data source and all associated objects, call the removeSource() method of the map:
map.removeSource(roadEventSource)
Adding an event
You can add your own road event to the map, which will be visible to all Urbi map users. You can place an event on the map only within a 2 km radius of your current location.
-
Create an instance of the object manager RoadEventManager:
- For SDK version 13.0.0 or later
- For SDK version 12.x
val roadEventManager = RoadEventManager.instance(context)val roadEventManager = RoadEventManager(context) -
Add an event of one of the types below (each type has its own icon on the map):
-
Car accident. Call the createAccident() method and specify the event coordinates (GeoPoint), affected lanes (Lane), and a text description of the event:
val accidentLocation = GeoPoint(55.751244, 37.618423)
val accidentLanes = EnumSet.of(Lane.LEFT, Lane.CENTER) // left and center lanes are affected
val accidentFuture = roadEventManager.createAccident(
location = accidentLocation,
lanes = accidentLanes,
description = "Accident on the left lane"
)
// Handle the result
accidentFuture.onResult { result ->
if (result.isEvent) {
println("Accident event successfully created")
} else {
println("Error: ${result.match(event = { "" }, error = { it.toString() })}")
}
} -
Traffic camera. Call the createCamera() method and specify the event coordinates (GeoPoint) and a text description:
val cameraLocation = GeoPoint(55.752220, 37.615560)
val cameraFuture = roadEventManager.createCamera(
location = cameraLocation,
description = "Speed control camera"
)
// Handle the result
cameraFuture.onResult { result ->
if (result.isEvent) {
println("Camera event successfully created")
} else {
println("Error: ${result.match(event = { "" }, error = { it.toString() })}")
}
} -
Road closure. Call the createRoadRestriction() method and specify the event coordinates (GeoPoint) and a text description:
val restrictionLocation = GeoPoint(55.753930, 37.620795)
val restrictionFuture = roadEventManager.createRoadRestriction(
location = restrictionLocation,
description = "Road closed for a festival"
)
// Handle the result
restrictionFuture.onResult { result ->
if (result.isEvent) {
println("Road closure event successfully created")
} else {
println("Error: ${result.match(event = { "" }, error = { it.toString() })}")
}
} -
Roadworks. Call the createRoadWorks() method and specify the event coordinates (GeoPoint), affected lanes (Lane), and a text description of the event:
val roadWorksLocation = GeoPoint(55.754800, 37.621000)
val roadWorksLanes = EnumSet.of(Lane.RIGHT) // right lane is affected
val roadWorksFuture = roadEventManager.createRoadWorks(
location = roadWorksLocation,
lanes = roadWorksLanes,
description = "Road surface repair"
)
// Handle the result
roadWorksFuture.onResult { result ->
if (result.isEvent) {
println("Road works event successfully created")
} else {
println("Error: ${result.match(event = { "" }, error = { it.toString() })}")
}
} -
Comment. Call the createComment() method and specify the event coordinates (GeoPoint) and a text description:
val commentLocation = GeoPoint(55.755000, 37.622000)
val commentFuture = roadEventManager.createComment(
location = commentLocation,
description = "Caution, icy road!"
)
// Handle the result
commentFuture.onResult { result ->
if (result.isEvent) {
println("Comment successfully created")
} else {
println("Error: ${result.match(event = { "" }, error = { it.toString() })}")
}
} -
Other event. Call the createOther() method and specify the event coordinates (GeoPoint), affected lanes (Lane), and a text description of the event:
val otherLocation = GeoPoint(55.756000, 37.623000)
val otherLanes = EnumSet.of(Lane.CENTER, Lane.RIGHT) // center and right lanes are affected
val otherFuture = roadEventManager.createOther(
location = otherLocation,
lanes = otherLanes,
description = "Obstacle on the road"
)
// Handle the result
otherFuture.onResult { result ->
if (result.isEvent) {
println("Other event successfully created")
} else {
println("Error: ${result.match(event = { "" }, error = { it.toString() })}")
}
}
-
Getting objects using screen coordinates
You can get information about map objects using pixel coordinates. For this, call the getRenderedObjects() method of the map and specify the pixel coordinates and the radius in screen millimeters (not more than 30). The method will return a deferred result (Future) containing information about all found objects within the specified radius on the visible area of the map (a list of RenderedObjectInfo).
An example of a function that takes tap coordinates and passes them to the getRenderedObjects() method:
- For SDK version 14.0.0 or later
- For SDK version 13.x
lifecycleScope.launch {
val mapController = mapViewModel.awaitController()
val map = mapController.map
tapConnection = mapController.gestureRecognizer.tap.connect { point ->
map.getRenderedObjects(point, ScreenDistance(5f)).onResult { renderedObjectInfos ->
// The first object in the list is the closest to the coordinates
for (renderedObjectInfo in renderedObjectInfos) {
Log.d("APP", "Some object's data: ${renderedObjectInfo.item.item.userData}")
}
}
}
}
override fun onTap(point: ScreenPoint) {
map.getRenderedObjects(point, ScreenDistance(5f)).onResult { renderedObjectInfos ->
// The first object in the list is the closest to the coordinates
for (renderedObjectInfo in renderedObjectInfos) {
Log.d("APP", "Some object's data: ${renderedObjectInfo.item.item.userData}")
}
}
}
You can subscribe to map object interaction events and get RenderedObjectInfo for the object that is closest to the tap spot.
- For SDK version 14.0.0 or later
- For SDK version 13.x
Use MapRenderedObjectObserver in MapController:
...
fun onObjectTappedOrLongTouch(objInfo: RenderedObjectInfo) {
Log.d("APP", "Custom data of the object: ${objInfo.item.item.userData}")
}
...
lifecycleScope.launch {
val mapController = mapViewModel.awaitController()
objectTappedConnection = mapController.renderedObjectObserver.objectTapped.connect { objects ->
objects.firstOrNull()?.let(::onObjectTappedOrLongTouch)
}
objectLongTouchConnection = mapController.renderedObjectObserver.objectLongTouched.connect { objects ->
objects.firstOrNull()?.let(::onObjectTappedOrLongTouch)
}
}
In addition to implementing the TouchEventsObserver interface, you can set a callback function MapObjectTappedCallback for tap or long tap in MapView using the methods addObjectTappedCallback and addObjectLongTouchCallback.
...
fun onObjectTappedOrLongTouch(objInfo: RenderedObjectInfo) {
Log.d("APP", "Custom data of the object: ${objInfo.item.item.userData}")
}
...
mapView.addObjectTappedCallback(::onObjectTappedOrLongTouch)
mapView.addObjectLongTouchCallback(::onObjectTappedOrLongTouch)
Placing a view on the map
You can place native views on the map anchored to a specific position. Use SnapToMapLayout that contains a custom implementation of the LayoutParams class that allows you to set a view position on the map.
Add SnapToMapLayout to the layout:
<ru.dgis.sdk.map.MapView>
...
<ru.dgis.sdk.map.SnapToMapLayout
android:id="@+id/snapToMapLayout"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</ru.dgis.sdk.map.MapView>
...
To anchor a view to a position on the map, add this view to SnapToMapLayout. You need to build LayoutParams with a geographic coordinate GeoPointWithElevation and other parameters:
val params = SnapToMapLayout.LayoutParams(
// Width, WRAP_CONTENT is used
width = ViewGroup.LayoutParams.WRAP_CONTENT,
// Height, WRAP_CONTENT is used
height = ViewGroup.LayoutParams.WRAP_CONTENT,
// Point on the map to which the view is anchored
position = GeoPointWithElevation(55.7, 37.6),
// Point on the view to which the view is anchored
// In this case, the upper-left corner of the view
anchor = Anchor(0.0f, 0.0f),
// Offset in in x- and y-axes relative to the upper and left borders respectively
offsetX = -15 * context.resources.displayMetrics.density,
offsetY = -15 * context.resources.displayMetrics.density
)
These parameters must be passed, for example, during the call to addView().
Working with map control gestures
You can customize working wth gestures in one of the following ways:
- Configure existing gestures.
- Implement your own mechanism of gesture recognition.
Configuring gestures
You can control the map using the following standard gestures:
- shift the map in any direction with one finger
- shift the map in any direction with multiple fingers
- rotate the map with two fingers
- scale the map with two fingers (pinch)
- zoom the map in by double-tapping
- zoom the map out with two fingers
- scale the map with the tap-tap-swipe gesture sequence using one finger
- tilt the map by swiping up or down with two fingers
By default, all gestures are enabled. If needed, you can disable selected gestures using the GestureManager.
You can get the GestureManager from the map gesture recognizer.
- For SDK version 14.0.0 or later
- For SDK version 13.x
lifecycleScope.launch {
val mapController = mapViewModel.awaitController()
gestureManager = mapController.gestureRecognizer.gestureManager!!
}
mapView.getMapAsync {
gestureManager = mapView.gestureManager!!
}
To activate gestures, use the enableGesture() method.
To deactivate gestures, use the disableGesture() method.
To check whether a gesture is enabled or not, use the gestureEnabled() method.
To change the settings or get information about multiple gestures at once, use the enabledGestures property.
A specific gesture is set using TransformGesture properties. The SCALING property is responsible for the whole group of map scaling gestures. You cannot disable these gestures one by one.
Example of disabling scaling the map with one finger:
- For SDK version 13.0.0 or later
- For SDK version 12.x
gestureManager.disableGesture(TransformGesture.SHIFT)
Log.d("GestureExample", "${gestureManager.enabledGestures}") // result 'D/GestureExample: [SCALING, ROTATION, MULTI_TOUCH_SHIFT, TILT]'
gestureManager.disableGesture(Gesture.SHIFT)
Log.d("GestureExample", "${gestureManager.enabledGestures}") // result 'D/GestureExample: [SCALING, ROTATION, MULTI_TOUCH_SHIFT, TILT]'
Some gestures have specific lists of settings:
- MultiTouchRecognizeSettings for shifting the map with multiple fingers
- RotationRecognizeSettings for rotation
- ScalingRecognizeSettings for scaling
- TiltRecognizeSettings for tilting
For more information about settings, see the documentation. Objects of these settings are accessible via GestureManager properties.
You can also configure the behavior of map scaling and rotation. You can set the point relatively to which map rotation and scaling will be done. By default, these operations are done relatively to the "center of mass" of the finger placement points. You can change this behavior using EventsProcessingSettings. To implement the setting, use the setSettingsAboutMapPositionPoint.
To control simultaneous activation of multiple gestures, use the setMutuallyExclusiveGestures() method.
Implementing custom gesture recognition
You can replace the standard mechanism of gesture processing with a custom one. To do it, pass the implementation of the MapGestureRecognitionEngine interface to the useCustomGestureRecognitionEngine method of MapView.
The interface has four methods:
- resetRecognitionState(): when called, resets the engine state to initial. Called when some events (for example, camera position change) finish working with map control events.
- onDevicePpiChanged(): notifies about PPI change. The value can be used to convert the distance from screen points to millimeters.
- setMapEventSender(): called to provide the engine with an object through which generated map control events will be sent.
- processMotionEvent() called to provide the engine with screen touch points in the standard format.
// Simplified implementation of a gesture recognizer, which can control map shift only
class ExampleGestureRecognitionEngine : MapGestureRecognitionEngine {
private var mapEventSender: MapEventSender? = null
private var oldTouchPoint: ScreenPoint? = null
private var origin: ScreenPoint? = null
private fun findTouchPoint(event: MotionEvent): ScreenPoint? =
if (event.actionMasked == MotionEvent.ACTION_CANCEL) {
// Cancel a gesture
null
} else if (event.actionMasked == MotionEvent.ACTION_UP && event.pointerCount == 1) {
// The last finger is risen
null
} else if (event.pointerCount <= 0) {
// No touch registered
null
} else {
ScreenPoint(event.getX(0), event.getY(0))
}
override fun processMotionEvent(event: MotionEvent) : Boolean{
val newTouchPoint = findTouchPoint(event)
if (newTouchPoint != null) {
if (oldTouchPoint != null) {
mapEventSender!!.sendEvent(
DirectMapShiftEvent(
ScreenShift(
newTouchPoint.x - oldTouchPoint!!.x,
newTouchPoint.y - oldTouchPoint!!.y
),
origin!!,
Duration.now()
)
)
} else {
origin = newTouchPoint
mapEventSender!!.sendEvent(DirectMapControlBeginEvent())
}
} else {
origin = null
mapEventSender!!.sendEvent(DirectMapControlEndEvent(Duration.now()))
}
oldTouchPoint = newTouchPoint
return true
}
override fun resetRecognitionState() {
origin = null
oldTouchPoint = null
}
override fun onDevicePpiChanged(devicePpi: DevicePpi) {}
override fun setMapEventSender(mapEventSender: MapEventSender) {
this.mapEventSender = mapEventSender
}
override fun close() {}
}
The engine task is to convert screen touches to map control events. The engine works with direct map control events only. All such events are represented by six classes with names starting with DirectMap:
- DirectMapControlBeginEvent
- DirectMapControlEndEvent
- DirectMapShiftEvent
- DirectMapRotationEvent
- DirectMapScalingEvent
- DirectMapTiltEvent
You can get more information about an event from its description. Remember that to work with direct map control events, you need to use signal events for the beginning and end of an event sequence (even if only one event is generated).