Map Items

The HERE SDK allows you to add several types of items to the map, such as map polylines and markers. They are explained in detail in the sections below. Currently, the following map items are supported:

  • Map polylines: Non-moveable rendered lines.
  • Map polygons: Non-moveable rendered shapes.
  • Map circles: Non-moveable rendered circles that can be defined as geo polygons.
  • Map markers: Images that can be pinned to 'mark' specific spots on the map.
  • Map markers 3D: 3D shapes rendered on the map at the specified geographic coordinates.
  • Map view pins: A convenient way to show native iOS UIView layouts on the map.

Polylines, polygons and circles, will adjust their size based on the current zoom level, while markers and pins remain unchanged when zooming.

All map items provide a convenient way to pick them from the map.

Add Map Polylines

Polylines can be useful to render, for example, a route geometry on the map. They can be created as shown below:

private func createMapPolyline() -> MapPolyline {
    let coordinates = [GeoCoordinates(latitude: 52.53032, longitude: 13.37409),
                       GeoCoordinates(latitude: 52.5309, longitude: 13.3946),
                       GeoCoordinates(latitude: 52.53894, longitude: 13.39194),
                       GeoCoordinates(latitude: 52.54014, longitude: 13.37958)]

    // We are sure that the number of vertices is greater than two, so it will not crash.
    let geoPolyline = try! GeoPolyline(vertices: coordinates)
    let lineColor = Color(red: 0x00, green: 0x90, blue: 0x8A, alpha: 0xA0)
    let mapPolyline = MapPolyline(geometry: geoPolyline,
                                  widthInPixels: 30,
                                  color: lineColor)
    return mapPolyline
}

A MapPolyline consists of three elements:

  • A list of two or more geographic coordinates that define where to place the polyline on the map.
  • A GeoPolyline that contains this list of coordinates.
  • Style parameters to define how to visualize the polyline.

Since a geometric line is defined by two or more points, you need to create an ArrayList, which must contain at least two GeoCoordinates. Otherwise, an exception will be thrown. To change the look of the line, its thickness in pixels and color can be set. See for an example the screenshot.

After you have created one or more map polylines, you can add them to a map scene with:

mapScene = mapView.mapScene

mapPolyline = createMapPolyline()
mapScene.addMapPolyline(mapPolyline!)

If a map polyline instance is already attached to a map scene, any further attempt to add it again will be ignored.

Screenshot: Showing a polyline.

Note, a map view allows only one scene and all map items are placed directly on it. If you want to group your map items, you may want to organize them by using an array and add or remove them individually.

You can remove a mapPolyline from the map immediately by calling:

mapScene.removeMapPolyline(line)

Note: MapPolyline items are pickable and it is possible to store the Metadata that can be retrieved when picking the item. For an example, see the section below on map markers.

Add Map Polygons

A MapPolygon is a shape that consists of at least three coordinates, otherwise it cannot be rendered. Similar to MapPolyline, the coordinates are connected. Polygons can be useful to highlight an area on the map.

Note: The order of the coordinates do matter.

See the example below on how a polygon can be created. The coordinates are connected based on their clockwise order in the list. The resulting shape can be filled with a color:

private func createMapPolygon() -> MapPolygon {
    // Note that a polygon requires a clockwise order of the coordinates.
    let coordinates = [GeoCoordinates(latitude: 52.54014, longitude: 13.37958),
                       GeoCoordinates(latitude: 52.53894, longitude: 13.39194),
                       GeoCoordinates(latitude: 52.5309, longitude: 13.3946),
                       GeoCoordinates(latitude: 52.53032, longitude: 13.37409)]

    // We are sure that the number of vertices is greater than three, so it will not crash.
    let geoPolygon = try! GeoPolygon(vertices: coordinates)
    let fillColor = Color(red: 0x00, green: 0x90, blue: 0x8A, alpha: 0xA0)
    let mapPolygon = MapPolygon(geometry: geoPolygon, color: fillColor)

    return mapPolygon
}

A MapPolygon consists of three elements:

  • A list of three or more geographic coordinates that define where to place the polygon on the map.
  • A GeoPolygon that contains this list of coordinates.
  • A Color to define the fill color of the polygon area.

Since a polygon is defined by three or more points, you need to create an array list, which must contain at least three GeoCoordinates. Otherwise, an exception will be thrown. See for an example the screenshot.

Map polygons can be used to create complex filled or unfilled shapes. However, a self-intersecting polygon can lead to undesired results as the coordinates are connected in the order of the list. As an alternative you can add multiple polygons - or make sure to add the coordinates as they appear on the outline of the desired shape.

Please note that, unlike a map polyline, the outline of a map polygon is connected automatically between the last coordinate and the first coordinate of the list.

After you have created one or more map polygons, you can add them to a map scene with:

mapPolygon = createMapPolygon()
mapScene = mapView.mapScene
mapScene.addMapPolygon(mapPolygon!)

If a map polygon is already attached to a map scene, any further attempt to add it again will be ignored.

Screenshot: Showing a polygon.

Note that a map view allows only one scene and all map items are placed directly on it. If you want to group your map items, you may want to organize them by using an array and add or remove them individually.

A mapPolygon can be removed immediately from the map by calling:

mapScene.removeMapPolygon(area)

Note: MapPolygon items are pickable and it is possible to store Metadata that can be retrieved when picking the item. For an example, see the section below on map markers.

Add Map Circles

A circular shape can be useful to highlight areas on the map, draw a location accuracy halo - or to mark a distinct spot on the map. Circles are technically rendered as a sequence of triangular polygon shapes.

Consequently, circles can be created as a MapPolygon instance using a GeoCircle:

private func createMapCircle() -> MapPolygon {
    let geoCircle = GeoCircle(center: GeoCoordinates(latitude: 52.530932, longitude: 13.384915),
                              radiusInMeters: 300.0)

    let geoPolygon = GeoPolygon(geoCircle: geoCircle)
    let fillColor = Color(red: 0x00, green: 0x90, blue: 0x8A, alpha: 0xA0)
    let mapPolygon = MapPolygon(geometry: geoPolygon, color: fillColor)

    return mapPolygon
}

Since a circle is a special shape of a polygon, you can add (or remove) a circle to (or from) a map scene as already shown above in the MapPolygon section.

Screenshot: Showing a circle.

Add Map Markers

You can use map markers to precisely point to a location on the map. Map markers will always be drawn on top of anything else that is rendered on the map.

The following code will add a map marker to the map:

guard
    let image = UIImage(named: "here_car.png"),
    let imageData = image.pngData() else {
        print("Error: Image not found.")
        return
}

let mapImage = MapImage(pixelData: imageData,
                        imageFormat: ImageFormat.png)
let mapMarker = MapMarker(at: geoCoordinates,
                          image: mapImage)

mapView.mapScene.addMapMarker(mapMarker)

In this example, we create a UIImage from a PNG ("here_car.png") and convert it to a MapImage. This MapImage can then be set to a MapMarker instance.

The HERE SDK for iOS supports PNG resources with or without transparency (alpha channel) - as well as all other common bitmap resources that are natively supported by UIImage including vector graphics such as PDF. An overloaded constructor allows to load graphics in the SVG Tiny format.

To see the image, we must add the MapMarker to a map scene. Please note that the MapImage will be displayed centered on the provided geoCoordinates.

Screenshot: Showing randomly placed map images. The red map circle on top marks the location of the marker.

You can also update the geoCoordinates after the marker is added to the map: It will instantly appear at the new location once the mapMarker.coordinates property is updated.

Please note that the chronological order in which map markers are added to the map determines what marker is rendered first. This can be adjusted by setting an explicit draw order.

Anchored POI Markers

By default, each image is centered on the location provided, and you may want to change this for some types of markers. An example is the POI marker, which usually points to the location with its bottom-middle position.

Therefore, the location of where the image is rendered must be shifted. The default center lies at (0.5, 0.5). If the bottom-right corner of the view should point to the set GeoCoordinates location, then the anchor point must be set to (1, 1).

Anchor points provide a convenient way to specify the location where a marker should be rendered: The top-left corner equals an anchor point of (0, 0) while the bottom-right corner equals an anchor point of (1, 1). Independent of how large the view is, the point that is half the width or height will always be 0.5 - this is similar to the concept of normalized texture UV-coordinates.

Illustration: Normalized anchor points can specify any location within or outside a map marker.

If you want to shift the POI to point to the location, you can keep the default middle location (0.5), but you must shift the image upwards by 1. 1 is just as long as the height of the image. Note that you can also specify values greater than 1 or less than 0, so that you can shift the image to any possible location. 2 would represent twice the height of the image and so on.

To add an anchored POI marker to the map, see the example below:

guard
    let image = UIImage(named: "poi.png"),
    let imageData = image.pngData() else {
        print("Error: Image not found.")
        return
}

// The bottom, middle position should point to the location.
// By default, the anchor point is set to 0.5, 0.5.
let anchorPoint = Anchor2D(horizontal: 0.5, vertical: 1)
let mapMarker = MapMarker(at: geoCoordinates,
                          image: MapImage(pixelData: imageData,
                                          imageFormat: ImageFormat.png),
                          anchor: anchorPoint)

mapView.mapScene.addMapMarker(mapMarker)

For the example above, a custom POI image called "poi.png" is added in different resolutions to the project's asset catalog. The iOS platform will choose the appropriate image resolution based on the device's display density. See the accompanying example app on how this can be done.

Screenshot: Showing randomly placed map markers. The red map circle marks the anchored location of the marker.

Unlike polylines, each MapImage will keep its size - regardless of how much the map is zoomed in or out.

Pick Map Markers

After you have added the map markers onto the map, you can use a tap gesture delegate to find out if a user tapped on a map marker:

// Conform to the TapDelegate protocol.
func onTap(origin: Point2D) {
    mapView.pickMapItems(at: origin, radius: 2, completion: onMapItemsPicked)
}

// Completion handler to receive picked map items.
func onMapItemsPicked(pickedMapItems: PickMapItemsResult?) {
    guard let topmostMapMarker = pickedMapItems?.markers.first else {
        return
    }

    showDialog(title: "Map marker picked:", message: "Location: \(topmostMapMarker.coordinates)")
}

Our class must then conform the TapDelegate protocol and we need to set our class as delegate to receive tap events. Once a tap gesture is recognized, we need to set a completion handler to pick a potential item.

By convention, the HERE SDK uses protocols for reoccurring events such as gesture events. Single events, that must be handled only one time, require a callback function in form of a completion handler.

As soon as the tap gesture is detected, we can use the view coordinates of the tapped location on the screen to ask the map view for any map markers around that location. In most cases, specifying a radius of two pixels is adequate. Then the PickMapItemsResult provides access to the map items found, such as a MapPolyline or a MapMarker.

When picking items of the same type, you can compare the instances using the ===-operator.

Adding Metadata

In many cases, users may want to interact with the shown markers - for example, by tapping on a search result to see more details about a restaurant. For this purpose, a MapMarker can hold an instance of the Metadata class, so it is possible to attach various types of data to it - even custom types are supported.

Metadata can hold several key/value pairs. Below, we create a new key named "key_poi" and set a String as the value containing the information about the type of the marker:

let metadata = Metadata()
metadata.setString(key: "key_poi", value: "This is a POI.")
mapMarker.metadata = metadata

Certainly, you can set any information you may need. The moment we want to read the contents of a Metadata instance, we simply ask for the data stored for a key, which is "key_poi" in our example:

if let message = topmostMapMarker.metadata?.getString(key: "key_poi") {
    showDialog(title: "Map Marker picked", message: message);
    return
}

A MapMarker instance, by default, does not contain Metadata and topmostMapMarker.metadata may be nil. The data accessed by a key can be nil as well, if the Metadata object does not contain such information.

If it does, we look for the String stored for our key "key_poi" and call a helper method to present the contained String to the user. You can choose any string as a key based on your preference, but use a unique key, or you will otherwise overwrite the content stored for a different data item. To see the full example's source code, please check the MapMarker example app.

You can also store custom objects into the Metadata using the CustomMetadataValue interface. An example can be found in the Search section where a search result data object is stored as a whole.

Add Map View Pins

While map markers offer a seamless way to place items on the map, map pins can be used to pin a native UIView to the map. This can be useful to show information bubbles, annotations, or customized controls. For example, when tapping on a map marker you can show a map overlay with additional information about the marker location.

Map pins can be composed out of multiple UIViews, and each of them can be used just like any other UIView, following user-defined Auto-Layout constraints, animations, and even native gesture handling, allowing the use of, for example, UITapGestureRecognizers.

As is the custom, you can define your views in an Interface Builder layout file and connect them to your ViewController - or generate the desired view content programmatically. For example:

let textView = UITextView(frame: CGRect(x: 0, y: 0, width: 200, height: 40))
textView.textAlignment = .center
textView.isEditable = false
textView.backgroundColor = UIColor(red: 72/255, green: 218/255, blue: 208/255, alpha: 1)
textView.textColor = .white
textView.font = .systemFont(ofSize: 17)
textView.text = "Centered ViewPin"

The UITextView is a descendant of UIView, it can be used directly as a pin. Now, all we need to do is to add our view to the map. Note that pins can be added directly to the map - unlike other map items such as polygons or markers:

let viewPin = mapView.pinView(view: textView, at: geoCoordinates)

Note that this method returns a proxy object that can be used to control the pinning, for example, to specify an anchor point.

Once added to the mapView, the view - including all nested sub-views - will appear centered on the provided geoCoordinates location and its location will automatically adjust to the given geographical location when the map is moved.

Try to remove the line starting with textView.isEditable. What will happen? Since the ViewPin makes sure that the real view is rendered on the provided location, this will enable the default behavior of an UITextView and therefore become editable.

Screenshot: A map pin showing an UITextView. By default, the view is centered on the provided location.

It is also possible to apply animations to the view or attach some other interaction listeners. After a view is attached to the map view, it will behave like any other iOS UIView - except that it stays fixed on the map and moves along with it when panning or zooming. Note, the ViewPin does not rotate when you rotate the map - similar to map polylines and map markers.

You can add as many map pins as you like, but you should take performance into consideration. For example, if you want to indicate multiple search results on the map, then map pins are less performant than map markers.

To remove a pin from the map, simply call:

mapView.unpinView(view: textView)

If you have added multiple ViewPin instances, you can access all pins from the mapView object by calling:

let viewPins = mapView.viewPins

Usually, map pins are the best choice for showing additional dynamic content for a specific location on the map.

Anchored Map Pins

By default, the map pins will be centered on the provided location. But if you want to use a pin without covering the area underneath, what can you do?

For this purpose, you can specify an anchor point - just like you do for any UIView:

textView.layer.anchorPoint = CGPoint(x: 0.5, y: 1)

This will place the view centered horizontally on the location, while the bottom of the view is matching the provided coordinate. As visualized in the screenshot below, the map pin sits on a map circle object that indicates the provided center location of the map pin.

Screenshot: An anchored map pin.

Anchor points provide a convenient way to specify the location where the view should be rendered: Independent of how large the view is, the point that is half the width or height will always be 0.5 - this is similar to the concept of normalized texture UV-coordinates.

By default, the anchor point is (0.5, 0.5), which will render the view centered on the location. Since a view can be of any size, the maximum width and height will have a value of 1. The dimension of the view is calculated after it is fully inflated. If you know the exact dimensions of your view, you can easily calculate a specific point inside the view in relation to the maximum value of 1.

Note: While offsets allows you to specify the translation along the x- and y-axis, an anchor point defines the relative position of the top-left corner of a rectangle such as a view. In relation to the view's boundaries, it defines the point where the view is centered on the provided GeoCoordinates location.

results matching ""

    No results matching ""