Map Items
The HERE SDK for iOS allows you to add several types of items to the map, such as map polylines, polygons, circles, markers and map overlays. They are explained in detail in the sections below.
- Map polylines: Non-moveable rendered lines.
- Map polygons: Non-moveable rendered filled shapes.
- Map circles: Non-moveable rendered filled circles.
- Map markers: Images that can be pinned to 'mark' specific spots on the map.
- Map overlays: A convenient way to show native
UIView
layouts on the map.
Polylines, polygons and circles, will adjust their size based on the current zoom level, while markers and overlays remain unchanged when zooming.
All map items provide a convenient way to pick them from the map. For polylines, polygons, circles and markers, this can be done by calling mapView.pickMapItems(at:radius:completion:)
, while a MapOverlayLite
allows native click handling - as it is for any standard iOS UIView
.
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() -> MapPolylineLite {
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)]
let geoPolyline = try! GeoPolyline(vertices: coordinates)
let mapPolylineStyle = MapPolylineStyleLite()
mapPolylineStyle.setWidthInPixels(inPixels: 20.0)
mapPolylineStyle.setColor(0x00908AA0, encoding: .rgba8888)
let mapPolyline = MapPolylineLite(geometry: geoPolyline, style: mapPolylineStyle)
return mapPolyline
}
A MapPolylineLite
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. - A
MapPolylineStyleLite
to define how to visualize the polyline. Make sure to set at least a color, otherwise the MapPolylineLite
will remain invisible.
Since a geometrical line is defined by two or more points, you need to create an array list, which must contain at least two GeoCoordinates
. Otherwise, an exception will be thrown. By using the MapPolylineStyleLite
class, attributes such as draw order, thickness in pixels and color of the line can be defined. See for an example the screenshot.
Setting a draw order can be useful to define what map item should be rendered topmost. Higher values will be drawn on top of lower values. Note that map markers will be always drawn on top of anything else that is rendered on the map, while map polylines are always drawn below extruded buildings, labels and other map polygons and map circles. The most common use case for map polylines is to render a route on the map, while map markers are expected to indicate a location, so they should not be hidden.
- The draw order for map polylines is effective only in relation to other map polylines.
- The draw order for map polygons and map circles is only effective in relation to other map polygons and map circles.
Note
The chronological order in which the map items are added to the map does not influence what item is rendered first. The default draw order value for map items such as MapPolylineLite
, MapPolygonLite
and MapCircleLite
is 0. The draw order for other map elements, such as streets or rivers, depends on the current map style you have in use - it can be adapted for custom map styles defined in a YAML file, see the section on custom map styles for more information.
After you have created one or more map polylines, you can add them to a map scene with:
let mapPolyline = createMapPolyline()
let mapScene = mapView.mapScene
mapView.mapScene.addMapPolyline(mapPolyline)
If a map polyline 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:
mapView.mapScene.removeMapPolyline(mapPolyline)
Note: MapPolylineLite
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.
Update Polyline Style
As you have seen above, line color and other properties such as the line width can be specified using a MapPolylineStyleLite
instance. It is possible to dynamically change already applied properties. For that, you do not need to create a new MapPolylineStyleLite
instance.
Instead, you can reuse the current style after a map polyline has been added to a map scene, modify some of its properties, and call the updateStyle()
method on the map polyline instance - you just have to pass the updated style instance as a parameter. The appearance on the map will change instantly. For example, you can create animated map polylines in this way.
Add Map Polygons
A MapPolygonLite
is a shape that consists of at least three coordinates, otherwise it cannot be rendered. Similar to MapPolylineLite
, 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 order in the list. The resulting shape can be filled with a color using a new MapPolygonStyleLite
instance:
private func createMapPolygon() -> MapPolygonLite {
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)]
let geoPolygon = try! GeoPolygon(vertices: coordinates)
let mapPolygonStyle = MapPolygonStyleLite()
mapPolygonStyle.setFillColor(0x00908AA0, encoding: .rgba8888)
let mapPolygon = MapPolygonLite(geometry: geoPolygon, style: mapPolygonStyle)
return mapPolygon
}
A MapPolygonLite
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
MapPolygonStyleLite
to define how to visualize the polygon. Make sure to set at least a color, or else the MapPolygonLite
will remain invisible.
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. By using the MapPolygonStyleLite
class, attributes such as draw order and color can be defined. This can be a fill color, like shown above, or a stroke color (and optionally a stroke width) to specify the outline - or both: This way you can create filled and unfilled map polygons. See for an example the screenshot.
Setting a draw order can be useful to define what map item should be rendered topmost. Higher values will be drawn on top of lower values. Note that map markers will be always drawn on top of anything else that is rendered on the map, while map polylines are always drawn below extruded buildings, labels and other map polygons and map circles. The most common use case for map polylines is to render a route on the map, while map markers are expected to indicate a location, so they should not be hidden.
- The draw order for map polylines is effective only in relation to other map polylines.
- The draw order for map polygons and map circles is only effective in relation to other map polygons and map circles.
Note
The chronological order in which the map items are added to the map does not influence what item is rendered first. The default draw order value for map items such as MapPolylineLite
, MapPolygonLite
and MapCircleLite
is 0. The draw order for other map elements, such as streets or rivers, depends on the current map style you have in use - it can be adapted for custom map styles defined in a YAML file, see the section on custom map styles for more information.
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:
let mapPolygon = createMapPolygon()
let 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, 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(mapPolygon)
Note: MapPolygonLite
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.
Update Polygon Style
As you have seen above, fill color and other properties such as the outline stroke width can be specified using a MapPolygonStyleLite
instance. It is possible to dynamically change already applied properties. For that, you do not need to create a new MapPolygonStyleLite
instance.
Instead, you can reuse the current style after a map polygon has been added to a map scene, modify some of its properties, and call the updateStyle()
method on the map polygon instance - you just have to pass the updated style instance as a parameter. The appearance on the map will change instantly. For example, you can create animated map polygons in this way.
Add Map Circles
A MapCircleLite
is a circular shape that can be useful to highlight areas on the map - or to mark a distinct spot on the map.
func createMapCircle() -> MapCircleLite {
let geoCircle = GeoCircle(center: GeoCoordinates(latitude: 52.530932, longitude: 13.384915),
radiusInMeters: 300.0)
let mapCircleStyle = MapCircleStyleLite()
mapCircleStyle.setFillColor(0x00908AA0, encoding: .rgba8888)
let mapCircle = MapCircleLite(geometry: geoCircle, style: mapCircleStyle)
return mapCircle
}
A MapCircleLite
consists of three elements:
- A set of geographic coordinates that defines where to place the circle's center on the map.
- A
GeoCircle
that contains these coordinates and the radius of the circle in meters. - A
MapCircleStyleLite
to define how to visualize the circle. Make sure to set at least a color, or else the MapCircleLite
will remain invisible.
By using the MapCircleStyleLite
class, attributes such as draw order and color can be defined. This can be a fill color, like shown above, or a stroke color (and optionally a stroke width) to specify the outline - or both: This way you can create filled and unfilled map circles. See the screenshot for an example.
Setting a draw order can be useful to define what map item should be rendered topmost. Higher values will be drawn on top of lower values. Note that map markers will be always drawn on top of anything else that is rendered on the map, while map polylines are always drawn below extruded buildings, labels and other map polygons and map circles. The most common use case for map polylines is to render a route on the map, while map markers are expected to indicate a location, so they should not be hidden.
- The draw order for map polylines is effective only in relation to other map polylines.
- The draw order for map polygons and map circles is only effective in relation to other map polygons and map circles.
Note
The chronological order in which the map items are added to the map does not influence what item is rendered first. The default draw order value for map items such as MapPolylineLite
, MapPolygonLite
and MapCircleLite
is 0. The draw order for other map elements, such as streets or rivers, depends on the current map style you have in use - it can be adapted for custom map styles defined in a YAML file, see the section on custom map styles for more information.
After you have created one or more map circles, you can add them to a map scene with:
let mapCircle = createMapCircle()
let mapScene = mapView.mapScene
mapScene.addMapCircle(mapCircle)
If a map circle is already attached to a map scene, any further attempt to add it again will be ignored.
Screenshot: Showing a circle.
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 one by one.
A mapCircle
can be removed immediately from the map by calling:
mapScene.removeMapCircle(mapCircle)
Note: MapCircleLite
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.
Update Circle Style
As you have seen above, fill color and other properties such as the outline stroke width can be specified using a MapCircleStyleLite
instance. It is possible to dynamically change already applied properties. For that you do not need to create a new MapCircleStyleLite
instance.
Instead, you can reuse the current style after a map circle has been added to a map scene, modify some of its properties, and call the updateStyle()
method on the map circle instance - you just have to pass the updated style instance as a parameter. The appearance on the map will change instantly. For example, you can create animated map circles in this way.
Add Map Markers
You can use map markers to precisely point to a location on the map. Map markers will be always drawn on top of anything else that is rendered on the map.
The following method will add a custom map marker to the map:
let geoCoordinates = GeoCoordinates(latitude: 52.518043, longitude: 13.405991)
let mapMarker = MapMarkerLite(at: geoCoordinates)
let image = UIImage(named: "here_car.png")
let mapImage = MapImageLite(image!)
mapMarker.addImage(mapImage!, style: MapMarkerImageStyleLite())
mapView.mapScene.addMapMarker(mapMarker)
In this example, we create a UIImage
from a PNG ("here_car.png") and convert it to a MapImageLite
. This MapImageLite
can then be added to a MapMarkerLite
instance.
Note
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.
To see the image, we must add the MapMarkerLite
to a map scene. Please note that the MapImageLite
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.
By default, any new MapMarkerLite
instance is invisible, unless we add one or more map images. A MapMarkerLite
can be composed out of multiple map images - and each map image may use a different anchor point.
Update Image Style
Anchor points and other image properties such as scale and rotation angle can be specified using a MapMarkerImageStyleLite
instance. It is possible to dynamically change already applied properties. For that you do not need to create a new MapMarkerImageStyleLite
instance.
Instead, you can reuse the current style after a map marker has been added to a map scene, modify some of its properties, and call the updateImageStyle()
method on the map marker instance - you just have to pass the updated style instance as a parameter. The appearance on the map will change instantly. For example, you can create animated map markers in this way.
You can read more about anchor points in the following section below.
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:
let mapMarker = MapMarkerLite(at: geoCoordinates)
let image = UIImage(named: "poi.png")
let mapImage = MapImageLite(image!)
let mapMarkerImageStyle = MapMarkerImageStyleLite()
mapMarkerImageStyle.setAnchorPoint(Anchor2D(horizontal: 0.5, vertical: 1))
mapMarker.addImage(mapImage!, style: mapMarkerImageStyle)
mapView.mapScene.addMapMarker(mapMarker)
You can add multiple map images to a single map marker. When you move the location of a map marker by updating the mapMarker.coordinates
property, all of its added images will move along with it, keeping their relative positions.
Note: For the example, 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.
You can also change the size of a MapImageLite
programmatically by setting a scale factor in MapMarkerImageStyleLite
. Values below 1 will shrink width/height proportionally, while values greater than 1 will increase the size of the image. The default scale factor is 1, leaving the image unscaled.
Unlike map circles or polylines, each MapImageLite
will keep its size - regardless of how much the map is zoomed out or in.
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:
func onTap(origin: Point2D) {
mapView.pickMapItems(at: origin, radius: 2, completion: onMapItemsPicked)
}
func onMapItemsPicked(pickedMapItems: PickMapItemsResultLite?) {
guard let topmostMapMarker = pickedMapItems?.topmostMarker else {
return
}
showDialog(title: "Map marker picked:", message: "Location: \(topmostMapMarker.coordinates)")
}
Our class must then conform to the TapDelegate
protocol. For this, we set our class as delegate (mapView.gestures.tapDelegate = self
). 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. Once a tap event is received, we add a completion handler to check the optional PickMapItemsResultLite
result that provides access to the map items found, such as a MapCircleLite
, a MapPolylineLite
, a MapPolygonLite
or a MapMarkerLite
.
Note
When picking items of the same type, you can compare the instances using the ===
-operator.
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 MapMarkerLite
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 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 MapMarkerLite
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.
Note
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.
Flat Map Markers
By default, map markers do not rotate with the map - nor get tilted when the map is tilted. This can be changed by setting the MapMarkerLite
to become flat. Use the following code to change the corresponding MapMarkerImageStyleLite
:
mapMarkerImageStyle.setFlat(true)
The result can be seen in the screenshot below.
Flat map markers - rotated and tilted together with the map.
Flat map markers rotate along with the rotated map - and the map marker will also be tilted when the map is tilted, allowing for a 3D-like effect. Please note that, by default, flat map markers will not change their scale value together with the zoom level of the map: For example, when zooming out the map, a map marker will still be visible - same as for an unflattened map marker.
Add Map Overlays
While map markers offer a seamless way to place items on the map, map overlays 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 overlays can hold 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 MapOverlay"
The UITextView
is a descendant of UIView
, it can be used directly as an overlay. All we need to do is to add this to a new MapOverlayLite
instance and show it on the map:
let mapOverlay = MapOverlayLite(view: textView, geoCoordinates: geoCoordinates)
mapView.addMapOverlay(mapOverlay)
Once added to the mapView
object, 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.
Note
Map overlays are always drawn on top of other map items.
Try to remove the line starting with textView.isEditable
. What will happen? Since the MapOverlayLite
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 overlay 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 a MapOverlayLite
object, 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 MapOverlayLite
does not rotate when you rotate the map - similar to map polylines and map markers.
You can add as many map overlays 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 overlays are less performant than map markers.
To remove an overlay from the map, simply call:
mapView.removeMapOverlay(mapOverlay)
If you have added multiple MapOverlayLite
instances, you can access all overlays from the mapView
object by calling:
let mapOverlays = mapView.overlays
Usually, map overlays are the best choice for showing additional dynamic content for a specific location on the map.
Anchored Map Overlays
By default, the map overlay will be centered on the provided location. But if you want to use an overlay 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 overlay sits on a map circle object that indicates the provided center location of the map overlay.
Screenshot: An anchored map overlay.
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.