Map | Mobile SDK | 2GIS Documentation

Map

To create a map, call the makeMapFactory() method and specify the required map settings as a MapOptions structure.

It is important to specify the correct PPI settings for the device. You can find them in the technical specification of the device.

You can also specify the initial camera position, zoom limits, and other settings.

// Map settings object.
var mapOptions = MapOptions.default

// PPI settings.
mapOptions.devicePPI = devicePPI

// Create a map.
let mapFactory: PlatformMapSDK.IMapFactory = sdk.makeMapFactory(options: mapOptions)

To get the view of the map, use the mapView property. To get the control of the map, use the map property.

// Map view.
let mapView: UIView & IMapView = mapFactory.mapView

// Map control.
let map = mapFactory.map

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 to a property.

self.objectsManager = MapObjectManager(map: 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.

To add a marker to the map, create a Marker object, specifying the required options (MarkerOptions), and pass it to the addObject() method of the object manager. The most important settings are the coordinates of the marker and its icon.

You can create an icon for the marker by calling the make() method and using UIImage, PNG data, or SVG markup as input.

// UIImage
let uiImage = UIImage(systemName: "umbrella.fill")!.withTintColor(.systemRed)
let icon = sdk.imageFactory.make(image: uiImage)

// SVG markup.
let icon = sdk.imageFactory.make(svgData: imageData, size: imageSize)

// PNG data.
let icon = sdk.imageFactory.make(pngData: imageData, size: imageSize)

// Marker settings.
let options = MarkerOptions(
	position: GeoPointWithElevation(
		latitude: 55.752425,
		longitude: 37.613983
	),
	icon: icon
)

// Create and add the marker to the map.
let marker = Marker(options: options)
objectManager.addObject(object: marker)

To change the hotspot of the icon, use the anchor parameter.

To draw a line on the map, create a Polyline object, specifying the required options, and pass it to the addObject() method of the object manager.

In addition to the coordinates of the line points, you can set the line width, color, stroke type, and other options (see PolylineOptions).

// Coordinates of the vertices of the polyline.
let points = [
	GeoPoint(latitude: 55.7513, longitude: value: 37.6236),
	GeoPoint(latitude: 55.7405, longitude: value: 37.6235),
	GeoPoint(latitude: 55.7439, longitude: value: 37.6506)
]

// Line settings.
let options = PolylineOptions(
	points: points,
	width: LogicalPixel(value: 2),
	color: DGis.Color.init()
)

// Create and add the line to the map.
let polyline = Polyline(options: options)
objectManager.addObject(object: polyline)

To draw a polygon on the map, create a Polygon object, specifying the required options, and pass it to the addObject() method of the object manager.

Coordinates for the polygon are specified as a two-dimensional array. The first subarray must contain the coordinates of the vertices of the polygon itself. The other subarrays are optional and can be specified to create a cutout (a hole) inside the polygon (one subarray - one polygonal cutout).

Additionally, you can specify the polygon color and stroke options (see PolygonOptions).

// Polygon settings.
let options = PolygonOptions(
	contours: [
		// Vertices of the polygon.
		[
			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)
		],
		// Cutout inside the polygon.
		[
			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)
		]
	],
	color: DGis.Color.init(),
	strokeWidth: LogicalPixel(value: 2)
)

// Create and add the polygon to the map.
let polygon = Polygon(options: options)
objectManager.addObject(object: polygon)

To add markers to the map in clustering mode, you must create a MapObjectManager object using MapObjectManager.withClustering(), specifying the map instance, distance between clusters in logical pixels, maximum value of zoom-level, when MapObjectManager in clustering mode, and user implementation of the protocol SimpleClusterRenderer. SimpleClusterRenderer is used to customize clusters in MapObjectManager.

final class SimpleClusterRendererImpl: SimpleClusterRenderer {
	private let image: DGis.Image
	private var idx = 0

	init(
		image: DGis.Image
	) {
		self.image = image
	}

	func renderCluster(cluster: SimpleClusterObject) -> SimpleClusterOptions {
		let textStyle = TextStyle(
			fontSize: LogicalPixel(15.0),
			textPlacement: TextPlacement.rightTop
		)
		let objectCount = cluster.objectCount
		let iconMapDirection = objectCount < 5 ? MapDirection(value: 45.0) : nil
		idx += 1
		return SimpleClusterOptions(
			icon: self.image,
			iconMapDirection: iconMapDirection,
			text: String(objectCount),
			textStyle: textStyle,
			iconWidth: LogicalPixel(30.0),
			userData: idx,
			zIndex: ZIndex(value: 6),
			animatedAppearance: false
		)
	}
}

self.objectManager = MapObjectManager.withClustering(
	map: map,
	logicalPixel: LogicalPixel(80.0),
	maxZoom: Zoom(19.0),
	clusterRenderer: SimpleClusterRendererImpl(image: self.icon)
)

You can control the camera by accessing the map.camera property. See the Camera class for a full list of available methods and properties.

You can change the position of the camera by calling the move() method, which initiates a flight 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 TimeInterval).
  • animationType - type of animation to use (CameraAnimationType).

The call will return a Future object, which can be used to handle the animation finish event.

// New position for camera.
let newCameraPosition = CameraPosition(
	point: GeoPoint(latitude: 55.752425, longitude: 37.613983),
	zoom: Zoom(value: 16)
)

// Start the flight animation.
let future = map.camera.move(
	position: newCameraPosition,
	time: 0.4,
	animationType: .linear
)

// Handle the animation finish event.
let cancellable = future.sink { _ in
	print("Camera flight finished.")
} failure: { error in
	print("An error occurred: \(error.localizedDescription)")
}

The current state of the camera (i.e., whether the camera is currently in flight) can be obtained using the state property. See CameraState for a list of possible camera states.

let currentState = map.camera.state

You can subscribe to changes of camera state using the stateChannel.sink property.

// Subscribe to camera state changes.
let connection = map.camera.stateChannel.sink { state in
	print("Camera state has changed to \(state)")
}

// Unsubscribe when it's no longer needed.
connection.cancel()

The current position of the camera can be obtained using the position property (see the CameraPosition structure).

let currentPosition = map.camera.position
print("Coordinates: \(currentPosition.point)")
print("Zoom level: \(currentPosition.zoom)")
print("Tilt: \(currentPosition.tilt)")
print("Rotation: \(currentPosition.bearing)")

You can subscribe to changes of camera position using the positionChannel.sink property.

// Subscribe to camera position changes.
let connection = map.camera.positionChannel.sink { position in
	print("Camera position has changed (coordinates, zoom level, tilt, or rotation).")
}

// Unsubscribe when it's no longer needed.
connection.cancel()

You can add a special marker to the map that will be automatically updated to reflect the current location of the device. To do this, create a data source MyLocationMapObjectSource() function and pass it to the addSource() method of the map.

// Create a data source.
let source = MyLocationMapObjectSource(
	context: sdk.context,
	directionBehaviour: MyLocationDirectionBehaviour.followMagneticHeading
)

// Add the data source to the map.
map.addSource(source: source)

To remove the marker, call the removeSource() method. You can get the list of active data sources by using the map.sources property.

map.removeSource(source)

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. 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 (an array of RenderedObjectInfo).

An example of a function that takes tap coordinates and passes them to getRenderedObjects():

private func tap(location: CGPoint) {
	let scale = UIScreen.main.nativeScale
	let point = ScreenPoint(x: Float(location.x * scale), y: Float(location.y * scale))
	self.getRenderedObjectsCancellable?.cancel()
	let cancel = self.map.getRenderedObjects(centerPoint: point).sink(
		receiveValue: {
			infos in
			// First array object is the closest to the coordinates.
			guard let info = infos.first(
				where: {
					$0.item.source is DgisSource
					&& $0.item.item is DgisMapObject
				}
			) else { return }

			// Process the result in the main thread.
			let source = info.item.source as! DgisSource
			let id = (info.item.item as! DgisMapObject).id

			// Select object and entrances.
			let future = searchManager.searchByDirectoryObjectId(objectId: id)

			self.getDirectoryObjectCancellable = future.sinkOnMainThread(
				receiveValue: {
					[weak self] directoryObject in
					guard let self = self else { return }
					guard let directoryObject = directoryObject else { return }
					guard let objectId = directoryObject.id else { return }

					var selectedObjectIds = [objectId]
					directoryObject.buildingEntrances.forEach { entrance in
						selectedObjectIds.append(entrance.id)
					}

					source.setHighlighted(directoryObjectIds: selectedObjectIds, highlighted: true)
				},
				failure: { ... }
			)
		},
		failure: { error in
			print("Error retrieving information: \(error)")
		}
	)
	// Save the result to a property to prevent garbage collection.
	self.getRenderedObjectsCancellable = cancel
}

You can also set the MapObjectTappedCallback callback for tap or long tap in the IMapView using the addObjectTappedCallback and addObjectLongPressCallback methods. This callback will receive RenderedObjectInfo for the object that is closest to the touch point.

...
let mapObjectTappedOrLongPress = MapObjectTappedCallback(callback: { [weak self] objectInfo in
	print("Some object's data: \(objectInfo.item.item.userData)")
})
...

self.mapView.addObjectTappedCallback(callback: mapObjectTappedOrLongPress)
self.mapView.addObjectLongPressCallback(callback: mapObjectTappedOrLongPress)

To customize the map gesture recognizer, you need to set the IMapGestureView implementation in IMapView or IMapGestureViewFactory implementation in MapOptions. If no implementations are specified, the default implementations will be used. An example of such recognizer is available here.