Map | Mobile SDK | Urbi Documentation
Flutter SDK

Map

To display a map, add a MapWidget to your tree:

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

class SimpleMapScreen extends StatelessWidget {
  const SimpleMapScreen({super.key});

  @override
  Widget build(BuildContext context) {
    final sdkContext = sdk.DGis.initialize();

    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: sdk.MapWidget(
        sdkContext: sdkContext,
        mapOptions: sdk.MapOptions(),
      ),
    );
  }
}

The main object for managing the map is a MapWidgetController, which is private in the MapWidget. To get access to the MapWidgetController, create it manually and pass it to the MapWidget as a parameter:

class SimpleMapScreen extends StatelessWidget {
  const SimpleMapScreen({super.key});

  @override
  Widget build(BuildContext context) {
    final sdk.Context sdkContext = sdk.DGis.initialize();
    final sdk.MapWidgetController mapWidgetController = sdk.MapWidgetController();

    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: sdk.MapWidget(
        sdkContext: sdkContext,
        mapOptions: sdk.MapOptions(),
        controller: mapWidgetController,
      ),
    );
  }
}

To get a Map object, you can call the getMapAsync() method. This method works asynchronously, so it is safe to call it at any point in the lifecycle, starting with the initState().

This is the first time the map is available, so you can perform different operations with the map within the getMapAsync() method. All operations described later in this section imply that the map has already been initialized.

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

sdk.Map? sdkMap = null;

@override
initState() {
  super.initState();
  mapWidgetController.getMapAsync((map) {
    sdkMap = map;
  });
}

In some cases, to add objects to the map, you need to create 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 or remove objects from the data source.

There are different types of data sources: moving markers, routes that display current traffic, custom geometric shapes, etc. Each type of data source has a corresponding class.

The general workflow of working with data sources looks like this:

// Create a data source
final sdk.MyLocationMapObjectSource locationSource = sdk.MyLocationMapObjectSource(sdkContext);

// Add the data source to the map
map?.addSource(locationSource);

To remove the created data source and all the objects associated with it, call the removeSource() method:

map?.removeSource(locationSource);

You can get a list of all active data sources using the map.sources property.

To add dynamic objects to the map (such as markers, lines, circles, and polygons), create a MapObjectManager, specifying the map object. Deleting an object manager removes all the associated objects from the map, so save it in activity.

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

final sdk.MapObjectManager mapObjectManager = sdk.MapObjectManager(map)

You can add objects to the map using the addObject() and the addObjects() methods. For each dynamic object, you can specify a userData field to store arbitrary data. After their creation, object settings can be changed.

To remove objects from the map, use the removeObject() and the removeObjects() methods. To remove all objects, use the removeAll() method.

To add a marker to the map, create a Marker object, specifying the required options, and pass it to the addObject() method of the object manager.

Specify marker coordinates in the options (position parameter):

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

final sdk.Marker marker = sdk.Marker(
    sdk.MarkerOptions(
        position: sdk.GeoPointWithElevation(
          latitude: sdk.Latitude(55.752425),
          longitude: sdk.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:

  • loadLottieFromAsset()
  • loadLottieFromFile()
  • loadPngFromAsset()
  • loadPngFromFile()
  • loadSVGFromAsset()
  • loadSVGFromFile()
import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

final sdk.ImageLoader loader = sdk.ImageLoader(_sdkContext);
final sdk.Image icon = loader.loadSVGFromAsset("assets/icons/bridge.svg");

final marker = sdk.Marker(
  sdk.MarkerOptions(
    position: sdk.GeoPointWithElevation(
      latitude: sdk.Latitude(55.752425),
      longitude: sdk.Longitude(37.613983),
      icon = icon
    )
  ),
);

To change the hotspot of the icon (the position of the icon relative to the coordinates on the map), use the anchor parameter.

You can also set the text for the marker and other options (see MarkerOptions).

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, dotted line, stroke type, and other options (see PolylineOptions).

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

// Coordinates of the vertices of the polyline
final sdk.GeoPoint point1 = sdk.GeoPoint(latitude: sdk.Latitude(55.7), longitude: sdk.Longitude(37.6));
final sdk.GeoPoint point2 = sdk.GeoPoint(latitude: sdk.Latitude(55.8), longitude: sdk.Longitude(37.7));
final sdk.GeoPoint point3 = sdk.GeoPoint(latitude: sdk.Latitude(55.9), longitude: sdk.Longitude(37.8));
final sdk.GeoPoint geopoints = <sdk.GeoPoint>[point1,point2,point3]

// Create a polyline
final sdk.Polyline polyline = sdk.Polyline(
  sdk.PolylineOptions(
    points: geopoints,
    width: sdk.LogicalPixel(2),
  ),
);

// Add the polyline to the map
mapObjectManager.addObject(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.

The coordinates for the polygon are specified as a two-dimensional list. The first sublist contains the coordinates of the vertices of the polygon. The other sublists are optional and can be specified to create a cutout inside the polygon (one sublist per polygonal cutout).

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

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

final sdk.GeoPoint point1 = sdk.GeoPoint(latitude: sdk.Latitude(55.7), longitude: sdk.Longitude(37.6));
final sdk.GeoPoint point2 = sdk.GeoPoint(latitude: sdk.Latitude(55.8), longitude: sdk.Longitude(37.7));
final sdk.GeoPoint point3 = sdk.GeoPoint(latitude: sdk.Latitude(55.9), longitude: sdk.Longitude(37.8));
final sdk.GeoPoint countor = <sdk.GeoPoint>[point1,point2,point3];
final sdk.GeoPoint point4 = sdk.GeoPoint(latitude: sdk.Latitude(55.7), longitude: sdk.Longitude(37.6));
final sdk.GeoPoint point5 = sdk.GeoPoint(latitude: sdk.Latitude(55.8), longitude: sdk.Longitude(37.7));
final sdk.GeoPoint point6 = sdk.GeoPoint(latitude: sdk.Latitude(55.9), longitude: sdk.Longitude(37.8));
final sdk.GeoPoint points = <sdk.GeoPoint>[point4,point5,point6];

final sdk.Polygon polygon = sdk.Polygon(
  sdk.PolygonOptions(
    contours: [countor,points],
    strokeWidth: sdk.LogicalPixel(5),
  ),
);
mapObjectManager.addObject(polygon);

Do not add a collection of objects to the map using the addObject method in a loop for the entire collection. This leads to performance losses. To add a collection of objects, prepare the entire collection and add it using the addObjects method:

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

// Prepare a collection of objects
final List<sdk.Marker> markers = [];
final sdk.MarkerOptions markerOptions = sdk.MarkerOptions(
    <params>
  );

markers.add(sdk.Marker(markerOptions));

// Add the collection of objects to the map
mapObjectManager.addObjects(markers)

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

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

class SimpleClusterRendererImpl implements sdk.SimpleClusterRenderer {
  final sdk.Image image;
  int idx = 0;

  SimpleClusterRendererImpl({
    required this.image,
  });

  @override
  sdk.SimpleClusterOptions renderCluster(sdk.SimpleClusterObject cluster) {
    final int objectCount = cluster.objectCount;
    final sdk.MapDirection? iconMapDirection =
        objectCount < 5 ? const sdk.MapDirection(45) : null;
    idx += 1;

    const double baseSize = 30.0;
    final double sizeMultiplier = 1.0 + (objectCount / 50.0);
    final double iconSize = min(baseSize * sizeMultiplier, 100);

    return sdk.SimpleClusterOptions(
      icon: image,
      iconMapDirection: iconMapDirection,
      text: objectCount.toString(),
      iconWidth: sdk.LogicalPixel(iconSize),
      userData: idx,
      zIndex: const sdk.ZIndex(1),
    );
  }
}

clusterRenderer ??= SimpleClusterRendererImpl(
  image: loader.loadSVGFromAsset("assets/icons/bridge.svg"),
);

mapObjectManager = sdk.MapObjectManager.withClustering(map, logicalPixel, maxZoom, clusterRenderer)

To make objects on the map react to selection, configure the styles to use different layer looks using the Add state dependency function for all required properties (icon, font, color, and others).

To configure the properties of the selected object, go to the Selected state tab:

First, get information about the objects falling into the tap region using the getRenderedObjects() method, as in the example Getting objects using screen coordinates.

To highlight objects, call the setHighlighted() method, which takes a list of IDs from the variable objects directory DgisObjectId. Within the getRenderedObjects() method, you can get all the data required to use this method, e.g., a data source of objects and their IDs:

mapWidgetController
    .addObjectTappedCallback((sdk.RenderedObjectInfo info) async {
  // Get the closest object to the tap spot inside the specified radius
  final sdk.RenderedObject obj = info.item;

  // In this example, we want to search for information about the selected object in the directory.
  // To do this, make sure that the type of this object can be found.
  if (obj.source is sdk.DgisSource && obj.item is sdk.DgisMapObject) {
    final source = obj.source as sdk.DgisSource;

    // Search
    final foundObject =
        await sdk.SearchManager.createOnlineManager(sdkContext)
            .searchByDirectoryObjectId((obj.item as sdk.DgisMapObject).id)
            .value;

    final entranceIds =
        foundObject?.entrances.map((entrance) => entrance.id).toList() ??
            List.empty();

    // Remove the highlight from the previously selected objects
    source
      ..setHighlighted(source.highlightedObjects, false)
      // Highlight the required objects and entrances
      ..setHighlighted(entranceIds, true);
  }
});

You can control the camera by accessing the map.camera property of the Camera object.

You can change the position of the camera by calling the move() method, which initiates a flight animation. Specify parameters:

  • position - new camera position (coordinates and zoom level). Additionally, you can specify the camera tilt and rotation (CameraPosition).
  • time - flight duration in seconds (Duration).
  • animationType - animation type (CameraAnimationType).

The move() function returns a Future object, which you can use to process the event of the animation stop.

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

final sdk.Map map = mapWidgetController.getMapAsync((map) {
  sdkMap = map;
});
final sdk.CameraPosition cameraPosition = const sdk.CameraPosition(
    point: sdk.GeoPoint(
      latitude: sdk.Latitude(55.759909),
      longitude: sdk.Longitude(37.618806),
    ),
    zoom: sdk.Zoom(15),
    tilt: sdk.Tilt(15),
    bearing: sdk.Bearing(115),
  );

map.camera.moveToCameraPosition(
    position,
    const Duration(seconds: 3),
    sdk.CameraAnimationType.linear,
  );

For more precise control over the flight animation, you can use a flight controller, which 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 parameters described previously.

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

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

final sdk.CameraState currentState = map.camera.state;

You can subscribe to changes in camera state using the stateChannel property:

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

map.camera.stateChannel.listen((sdk.CameraState cameraState) {
  // Processing camera status changes
});

You can obtain the current position of the camera using the position property. See a CameraPosition object.

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

final sdk.CameraPosition currentPosition = sdkMap.camera.position;

You can subscribe to changes in camera position (and the camera tilt and rotation) using the positionChannel property:

sdkMap.camera.positionChannel.listen((position) {
  // Processing camera position changes
});

To display an object or a group of objects on the map, you can use the calcPositionForObjects or the calcPositionForGeometry method to calculate camera position:

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

// We want to display two markers on the map.
// Create a geometry that covers both objects
final List<sdk.SimpleMapObject> objects = [marker1, marker2, marker3];
// Calculate the position
final position = sdk.calcPositionForObjects(camera, objects, styleZoomToTiltRelation, screenArea, tilt, bearing, size)

// Use the calculated position
map.camera.moveToCameraPosition(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 at 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, you can set paddings from the top and bottom of the screen:

sdkCamera.padding = sdk.Padding(top: 10, bottom: 100);

final position = sdk.calcPositionForObjects(camera, objects, styleZoomToTiltRelation, screenArea, tilt, bearing, size);
map.camera.moveToCameraPosition(position);

Result:

You can also set specific parameters for the position calculation only. For example, you can set paddings only inside the position calculation method and get the same result.

The camera position specifies a geocoordinate that must be within the camera position spot (a red circle in the screen center).

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

final sdk.Geometry geometry = sdk.ComplexGeometry([sdk.PointGeometry(point1), sdk.PointGeometry(point2),])
// Set an active area only for the position calculation
val position = sdk.calcPositionForGeometry(map.camera, geometry, styleZoomToTiltRelation, sdk.Padding({top:100, bottom:100}), tilt, bearing, size,)
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. 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.

Camera has two properties that both describe the geometry of the visible area but in different ways. visibleRect has a GeoRect type and is always a rectangle. visibleArea is an arbitrary geometry. You can see the difference easily with examples of different camera tilt angles (relative to the map):

  • With 45° tilt, visibleRect and visibleArea are not equal: in this case, visibleRect is larger because it is a rectangle containing visibleArea.

    visibleArea is displayed in blue, visibleRect – in red.

  • With 0° tilt, visibleArea и visibleRect overlap, as you can see from the color change.

Using the visibleArea property, you can get the map area covered by the camera as a Geometry. Using the intersects() method, you can get the intersection of the camera coverage area with the required geometry:

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

camera.visibleArea.intersects(geometry)

To get information about map objects using pixel coordinates, you can call the getRenderedObjects() method of the map, specifying pixel coordinates and a radius in screen millimeters. This method returns a deferred result containing information about all the found objects within the specified radius on the visible area of the map (a List<RenderedObjectInfo> list).

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

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

map.getRenderedObjects(sdk.ScreenPoint()).then((List<sdk.RenderedObjectInfo> info) {
  // Callback
});

In addition to using it directly, you can set a callback for tap (addObjectTappedCallback) or long tap (addObjectLongTouchCallback) in the MapWidgetController, as you can see in the example with object selection by tap on the map.

You can customize working wth gestures in one of the following ways:

  • Configure existing gestures.
  • Implement your own mechanism of gesture recognition.

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. You can disable selected gestures using the GestureManager.

You can get the GestureManager directly from the MapWidgetController:

mapWidgetController.getMapAsync((map) {
  gestureManager = mapWidgetController.gestureManager;
})

To manage gestures, use the following methods:

  • enableGesture for gestures activation;
  • disableGesture for gestures deactivation;
  • gestureEnabled for checking whether a gesture is enabled or not.

To change the settings or get information about multiple gestures at once, use the enabledGestures property.

A specific gesture is set using Gesture properties. The scaling property is responsible for the entire group of map scaling gestures. You cannot disable these gestures one by one.

Some gestures have specific lists of settings:

  • MultiTouchShiftSettings for shifting the map with multiple fingers.
  • RotationSettings for rotation.
  • ScalingSettings for scaling.
  • TiltSettings for tilting.

For more information about settings, see the documentation. Objects of these settings are accessible via GestureManager.

You can also configure the behavior of map scaling and rotation. You can specify the point relative to which map rotation and scaling will be performed. By default, these operations are done relative to the "center of mass" of the finger placement points. You can change this behavior using the EventsProcessingSettings setting. To implement the setting, use the setSettingsAboutMapPositionPoint() method.

To control simultaneous activation of multiple gestures, use the setMutuallyExclusiveGestures method.