Draw UNL Grid with Mapbox Maps SDK for iOS

This example demonstrates how UNL Core library can be used in an iOS app with Mapbox Maps SDK for iOS library to draw a UNL grid. In this example, we are going to generate the Unl grid lines and display them on the map by creating and adding a new layer to the map. In addition, we will add the option to show/hide this layer.

Project setup#

  1. Create a "Single view App" from XCode. Make sure to select the "Storyboard" option from the field "User interface":
  1. Open a new terminal window, cd into your project and run pod init to generate a podfile.

If you don't have CocoaPods installed or want to read more on this topic, you can check out the official documentation.

  1. Install Mapbox Maps SDK for iOS
  • Sign up to Mapbox and go to your Account page
  • Create an access token
  • Create a .netrc file with the following content:
machine api.mapbox.com
login mapbox
password <INSERT SECRET API TOKEN>
  • Open your project's Info.plist file and add a MGLMapboxAccessToken key whose value is your public access token.

  • Add the dependency to your Podfile:

use_frameworks!
target 'TargetNameForYourApp' do
pod 'Mapbox-iOS-SDK'
end
  • Run pod install

You can read the complete installation guide here.

  1. Install UnlCore

To add UnlCore as a dependency to your iOS project you must:

  • Add the following dependency in the podfile of your project:

pod 'unl-core'

  • Run pod install

You can read more about the installation and usage of the Unl Core library here.

Display Mapbox map#

We will display the map at a default location and zoom:

class ViewController: UIViewController {
private static let initialLocation = CLLocationCoordinate2D(latitude: 52.3676, longitude: 4.9041)
private static let initialZoom = 17.0
var mapView: MGLMapView!
override func viewDidLoad() {
super.viewDidLoad()
let mapView = MGLMapView(frame: view.bounds)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.setCenter(ViewController.initialLocation, zoomLevel: ViewController.initialZoom, animated: false)
view.addSubview(mapView);
}
}

Display UNL Grid on the map#

We will restrict the zoom level at which the unl grid layer will be visible. Also define a color for the lines:

private static let minGridZoom = 17.0;

In order to display the grid lines, we need to create a new layer with an associated source once the map is loaded. We use the UnlCore library to generate the grid lines based on the visible map bounds:

var gridLinesSource: MGLShapeSource?
var gridLinesLayer: MGLStyleLayer?
func getGridLinesPolylineFeature() -> MGLMultiPolyline {
var polylines: [MGLPolyline] = [];
if(mapView.zoomLevel < ViewController.minGridZoom) {
return MGLMultiPolyline(polylines: polylines)
}
let visibleBounds = Bounds(n: mapView.visibleCoordinateBounds.ne.latitude, e: mapView.visibleCoordinateBounds.ne.longitude, s: mapView.visibleCoordinateBounds.sw.latitude, w: mapView.visibleCoordinateBounds.sw.longitude)
let gridLineCoordinates: [[[Double]]] = try! UnlCore.gridLines(bounds: visibleBounds);
for coords in gridLineCoordinates {
let polylineCoordinates: [CLLocationCoordinate2D] = coords.map({
CLLocationCoordinate2D(latitude: $0[1], longitude: $0[0])
})
let polyline: MGLPolyline = MGLPolyline(coordinates: polylineCoordinates, count: UInt(polylineCoordinates.count))
polylines.append(polyline)
}
return MGLMultiPolyline(polylines: polylines)
}
func addLayer(to style: MGLStyle) {
gridLinesSource = MGLShapeSource(identifier: "unl-grid", shape: getGridLinesPolylineFeature())
let layer = MGLLineStyleLayer(identifier: "unl-grid", source: gridLinesSource!)
layer.sourceLayerIdentifier = "unl-grid-layer"
layer.lineColor = NSExpression(forConstantValue: UIColor.gray)
layer.lineWidth = NSExpression(forConstantValue: 1.0)
style.addSource(gridLinesSource!)
style.addLayer(layer)
self.gridLinesLayer = layer
showGridLines()
}
// Wait until the style is loaded before adding the new layer
func mapView(_ mapView: MGLMapView, didFinishLoading style: MGLStyle) {
self.mapView = mapView
addLayer(to: style)
}

Now whenever the map bounds change, we update the source with its new shape:

func mapView(_ mapView: MGLMapView, regionDidChangeAnimated animated: Bool) {
gridLinesSource?.shape = getGridLinesPolylineFeature()
}

Show/Hide the UnlGrid#

Add the functions for changing the grid lines layer visibility:

func showGridLines() {
self.gridLinesLayer?.isVisible = true
}
func hideGridLines() {
self.gridLinesLayer?.isVisible = false
}

Add the toggle button:

func addToggleButton() {
let button = UIButton(type: .system)
button.setTitle("Toggle Unl Grid", for: .normal)
button.isSelected = true
button.sizeToFit()
button.center.x = self.view.center.x
button.frame = CGRect(origin: CGPoint(x: button.frame.origin.x, y: self.view.frame.size.height - button.frame.size.height - 5), size: button.frame.size)
button.addTarget(self, action: #selector(toggleLayer(sender:)), for: .touchUpInside)
self.view.addSubview(button)
if #available(iOS 11.0, *) {
let safeArea = view.safeAreaLayoutGuide
button.translatesAutoresizingMaskIntoConstraints = false
let constraints = [
button.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor, constant: -5),
button.centerXAnchor.constraint(equalTo: safeArea.centerXAnchor)]
NSLayoutConstraint.activate(constraints)
} else {
button.autoresizingMask = [.flexibleTopMargin, .flexibleLeftMargin, .flexibleRightMargin]
}
}

Complete source code of the ViewController#

import UIKit
import Mapbox
import unl_core
class ViewController: UIViewController, MGLMapViewDelegate {
private static let initialLocation = CLLocationCoordinate2D(latitude: 52.3676, longitude: 4.9041)
private static let initialZoom = 17.0;
private static let minGridZoom = 17.0;
var mapView: MGLMapView!
var gridLinesSource: MGLShapeSource?
var gridLinesLayer: MGLStyleLayer?
override func viewDidLoad() {
super.viewDidLoad()
let mapView = MGLMapView(frame: view.bounds)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.setCenter(ViewController.initialLocation, zoomLevel: ViewController.initialZoom, animated: false)
view.addSubview(mapView)
addToggleButton()
mapView.delegate = self
}
func getGridLinesPolylineFeature() -> MGLMultiPolyline {
var polylines: [MGLPolyline] = [];
if(mapView.zoomLevel < ViewController.minGridZoom) {
return MGLMultiPolyline(polylines: polylines)
}
let visibleBounds = Bounds(n: mapView.visibleCoordinateBounds.ne.latitude, e: mapView.visibleCoordinateBounds.ne.longitude, s: mapView.visibleCoordinateBounds.sw.latitude, w: mapView.visibleCoordinateBounds.sw.longitude)
let gridLineCoordinates: [[[Double]]] = try! UnlCore.gridLines(bounds: visibleBounds);
for coords in gridLineCoordinates {
let polylineCoordinates: [CLLocationCoordinate2D] = coords.map({
CLLocationCoordinate2D(latitude: $0[1], longitude: $0[0])
})
let polyline: MGLPolyline = MGLPolyline(coordinates: polylineCoordinates, count: UInt(polylineCoordinates.count))
polylines.append(polyline)
}
return MGLMultiPolyline(polylines: polylines)
}
func addLayer(to style: MGLStyle) {
gridLinesSource = MGLShapeSource(identifier: "unl-grid", shape: getGridLinesPolylineFeature())
let layer = MGLLineStyleLayer(identifier: "unl-grid", source: gridLinesSource!)
layer.sourceLayerIdentifier = "unl-grid-layer"
layer.lineColor = NSExpression(forConstantValue: UIColor.gray)
layer.lineWidth = NSExpression(forConstantValue: 1.0)
style.addSource(gridLinesSource!)
style.addLayer(layer)
self.gridLinesLayer = layer
showGridLines()
}
// Wait until the style is loaded before adding the new layer
func mapView(_ mapView: MGLMapView, didFinishLoading style: MGLStyle) {
self.mapView = mapView
addLayer(to: style)
}
func mapView(_ mapView: MGLMapView, regionDidChangeAnimated animated: Bool) {
gridLinesSource?.shape = getGridLinesPolylineFeature()
}
@objc func toggleLayer(sender: UIButton) {
sender.isSelected = !sender.isSelected
if sender.isSelected {
showGridLines()
} else {
hideGridLines()
}
}
func showGridLines() {
self.gridLinesLayer?.isVisible = true
}
func hideGridLines() {
self.gridLinesLayer?.isVisible = false
}
func addToggleButton() {
let button = UIButton(type: .system)
button.setTitle("Toggle Unl Grid", for: .normal)
button.isSelected = true
button.sizeToFit()
button.center.x = self.view.center.x
button.frame = CGRect(origin: CGPoint(x: button.frame.origin.x, y: self.view.frame.size.height - button.frame.size.height - 5), size: button.frame.size)
button.addTarget(self, action: #selector(toggleLayer(sender:)), for: .touchUpInside)
self.view.addSubview(button)
if #available(iOS 11.0, *) {
let safeArea = view.safeAreaLayoutGuide
button.translatesAutoresizingMaskIntoConstraints = false
let constraints = [
button.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor, constant: -5),
button.centerXAnchor.constraint(equalTo: safeArea.centerXAnchor)]
NSLayoutConstraint.activate(constraints)
} else {
button.autoresizingMask = [.flexibleTopMargin, .flexibleLeftMargin, .flexibleRightMargin]
}
}
}