Projections
- UIKit
This example demonstrates how to use GEMKit in a UIKit application to convert the coordinates of a selected landmark to different projections and display the results in a table view.
Check the full implementation on GitHub.

Projections Table
Map Display and Projections Table View
The following code outlines the main view, which displays the map and prepares the table view to show the different projections for a selected landmark:
ViewController.swiftView on GitHub
class ItemProjection: NSObject {
var title: NSAttributedString = NSAttributedString.init(string: "")
var details: NSAttributedString = NSAttributedString.init(string: "")
var object: ProjectionObject?
}
class ViewController: UIViewController, MapViewControllerDelegate, UITableViewDataSource, UITableViewDelegate {
var mapViewController: MapViewController?
var projectionContext: ProjectionContext?
var tableView: UITableView?
var buttonExit: UIButton?
var modelData: [ItemProjection] = []
var selectedLandmark: LandmarkObject?
deinit {
if let controller = mapViewController {
controller.destroy()
}
}
override func viewDidLoad() {
super.viewDidLoad()
if let navigationController = self.navigationController {
let appearance = navigationController.navigationBar.standardAppearance
navigationController.navigationBar.scrollEdgeAppearance = appearance
}
self.title = "Projections"
self.projectionContext = ProjectionContext.init()
self.createMapView()
self.addTableView()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.mapViewController!.startRender()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let location = CoordinatesObject.coordinates(withLatitude: 53.592590, longitude: 9.924337) // Hamburg
self.mapViewController!.center(onCoordinates: location, zoomLevel: 70, animationDuration: 0)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.mapViewController!.stopRender()
}
// MARK: - Map View
func createMapView() {
self.mapViewController = MapViewController.init()
self.mapViewController!.delegate = self
self.mapViewController!.view.backgroundColor = UIColor.systemBackground
self.addChild(self.mapViewController!)
self.view.addSubview(self.mapViewController!.view)
self.mapViewController!.didMove(toParent: self)
self.mapViewController!.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
self.mapViewController!.view.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0),
self.mapViewController!.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0),
self.mapViewController!.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -0),
self.mapViewController!.view.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -0)
])
}
// MARK: - TableView
func addTableView() {
self.tableView = UITableView.init(frame: self.view.frame, style: .insetGrouped)
self.tableView!.isHidden = true
self.tableView!.dataSource = self
self.tableView!.delegate = self
self.view.addSubview(self.tableView!)
self.tableView!.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
self.tableView!.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
self.tableView!.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
self.tableView!.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
self.tableView!.heightAnchor.constraint(equalToConstant: 360)
])
let size: CGFloat = 50
let buttonExit = UIButton.init(type: .system)
buttonExit.isHidden = true
buttonExit.setImage(
UIImage.init(systemName: "xmark.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 30, weight: .medium)),
for: .normal)
buttonExit.addTarget(self, action: #selector(closeTableView), for: .touchUpInside)
buttonExit.layer.shadowColor = UIColor.darkGray.cgColor
buttonExit.layer.shadowOpacity = 0.8
buttonExit.backgroundColor = UIColor.systemBackground
buttonExit.layer.cornerRadius = size / 2
self.buttonExit = buttonExit
self.view.addSubview(buttonExit)
buttonExit.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
buttonExit.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -5),
buttonExit.bottomAnchor.constraint(equalTo: self.tableView!.topAnchor, constant: size + 5),
buttonExit.widthAnchor.constraint(equalToConstant: size),
buttonExit.heightAnchor.constraint(equalToConstant: size)
])
}
// MARK: - MapViewControllerDelegate
func mapViewController(_ mapViewController: MapViewController, didSelectLandmarks landmarks: [LandmarkObject], onTouch point: CGPoint) {
guard let landmark = landmarks.first else { return }
self.handleSelection(landmark: landmark)
}
func mapViewController(
_ mapViewController: MapViewController, didSelectLandmarks landmarks: [LandmarkObject], onLongTouch point: CGPoint
) {
guard let landmark = landmarks.first else { return }
self.handleSelection(landmark: landmark)
}
func mapViewController(_ mapViewController: MapViewController, didSelectStreets streets: [LandmarkObject], onTouch point: CGPoint) {
guard let landmark = streets.first else { return }
self.handleSelection(landmark: landmark)
}
func mapViewController(_ mapViewController: MapViewController, didSelectStreets streets: [LandmarkObject], onLongTouch point: CGPoint) {
guard let landmark = streets.first else { return }
self.handleSelection(landmark: landmark)
}
// MARK: - UITableViewDataSource
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let count = self.modelData.count
return count
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if let lmk = self.selectedLandmark {
return lmk.getLandmarkName()
}
return ""
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier = "defaultCellId"
var cell = tableView.dequeueReusableCell(withIdentifier: identifier)
if cell == nil {
cell = UITableViewCell.init(style: .subtitle, reuseIdentifier: identifier)
cell!.textLabel!.numberOfLines = 0
cell!.detailTextLabel!.numberOfLines = 0
}
let item = self.modelData[indexPath.row]
cell!.textLabel?.attributedText = item.title
cell!.detailTextLabel?.attributedText = item.details
return cell!
}
// MARK: - UITableViewDelegate
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
Converting Coordinates to Different Projections
The following code outlines the main functions to handle the landmark selection, highlight it on the map, and convert its coordinates to different projections:
ViewController.swiftView on GitHub
// MARK: - Utils
func handleSelection(landmark: LandmarkObject) {
self.modelData.removeAll()
let attributesMessage = self.getStringAttributes()
let coordinates = landmark.getCoordinates()
if coordinates.isValid() {
let details = NSAttributedString.init(
string: "Latitude: \(coordinates.latitude)\nLongitude: \(coordinates.longitude)", attributes: attributesMessage)
let item = ItemProjection.init()
item.title = self.getString(title: "World Geodetic System:")
item.details = details
self.modelData.append(item)
}
self.convertFrom(coordinates: coordinates, toType: .mgrs) { error, details in
let item = ItemProjection.init()
item.title = self.getString(title: "Military Grid Reference System:")
item.details = self.getString(title: "", description: "Not supported.")
if error == .kNoError {
item.details = details
}
self.modelData.append(item)
self.tableView!.reloadData()
}
self.convertFrom(coordinates: coordinates, toType: .bng) { error, details in
let item = ItemProjection.init()
item.title = self.getString(title: "British National Grid:")
item.details = self.getString(title: "", description: "Not supported.")
if error == .kNoError {
item.details = details
}
self.modelData.append(item)
self.tableView!.reloadData()
}
self.convertFrom(coordinates: coordinates, toType: .lam) { error, details in
let item = ItemProjection.init()
item.title = self.getString(title: "Lambert 93")
item.details = self.getString(title: "", description: "Not supported.")
if error == .kNoError {
item.details = details
}
self.modelData.append(item)
self.tableView!.reloadData()
}
self.convertFrom(coordinates: coordinates, toType: .utm) { error, details in
let item = ItemProjection.init()
item.title = self.getString(title: "Universal Transverse Mercator:")
item.details = self.getString(title: "", description: "Not supported.")
if error == .kNoError {
item.details = details
}
self.modelData.append(item)
self.tableView!.reloadData()
}
self.convertFrom(coordinates: coordinates, toType: .gk) { error, details in
let item = ItemProjection.init()
item.title = self.getString(title: "Gauss-Krueger")
item.details = self.getString(title: "", description: "Not supported.")
if error == .kNoError {
item.details = details
}
self.modelData.append(item)
self.tableView!.reloadData()
}
self.convertFrom(coordinates: coordinates, toType: .W3W) { error, details in
let item = ItemProjection.init()
item.title = self.getString(title: "What 3 Words")
item.details = self.getString(title: "", description: "Not supported.")
if error == .kNoError {
item.details = details
}
self.modelData.append(item)
self.tableView!.reloadData()
}
// let text = landmark.getLandmarkName()
// let scale = UIScreen.main.scale
// let image = landmark.getLandmarkImage(CGSize.init(width: 40*scale, height: 40*scale))
self.highlight(landmark: landmark)
self.selectedLandmark = landmark
self.tableView!.reloadData()
self.tableView!.isHidden = false
self.buttonExit!.isHidden = false
}
func highlight(landmark: LandmarkObject) {
let settings = HighlightRenderSettings.init()
settings.showPin = true
settings.imageSize = 7
if landmark.isContourGeograficAreaEmpty() == false {
settings.options = Int32(
HighlightOption.showLandmark.rawValue | HighlightOption.overlap.rawValue | HighlightOption.showContour.rawValue)
settings.contourInnerColor = UIColor.white
settings.contourOuterColor = UIColor.systemBlue
}
self.mapViewController!.presentHighlights([landmark], settings: settings, highlightId: 0)
if self.selectedLandmark == nil {
self.mapViewController!.center(onCoordinates: landmark.getCoordinates(), zoomLevel: 70, animationDuration: 400)
}
}
func convertFrom(
coordinates: CoordinatesObject, toType: ProjectionType,
completionHandler: (@escaping (_ error: SDKErrorCode, _ details: NSAttributedString) -> Void)
) {
guard let projectionContext = self.projectionContext else { return }
let fromProj = ProjectionWGS84Object.init(coordinates: coordinates)
var toProj: ProjectionObject?
switch toType {
case .bng:
toProj = ProjectionBNGObject.init()
case .lam:
toProj = ProjectionLAMObject.init()
case .utm:
toProj = ProjectionUTMObject.init()
case .mgrs:
toProj = ProjectionMGRSObject.init()
case .gk:
toProj = ProjectionGKObject.init()
case .W3W:
//
// Token is available at: https://developer.what3words.com
//
/* toProj = ProjectionW3WObject(token: "") */
break
default:
completionHandler(SDKErrorCode.kNotSupported, NSMutableAttributedString.init())
}
if let toProj = toProj {
let attributesMessage = self.getStringAttributes()
projectionContext.convert(fromProj, to: toProj) { error in
let details = NSMutableAttributedString.init()
if error == .kNoError {
switch toProj.getType() {
case .bng:
if let toProj = toProj as? ProjectionBNGObject {
let easting = toProj.getEasting()
let northing = toProj.getNorthing()
let gridRef = toProj.getGridReference()
details.append(NSMutableAttributedString.init(string: "Easting: \(easting)", attributes: attributesMessage))
details.append(NSMutableAttributedString.init(string: "\nNorthing: \(northing)", attributes: attributesMessage))
details.append(NSMutableAttributedString.init(string: "\nGrid: \(gridRef)", attributes: attributesMessage))
}
case .lam:
if let toProj = toProj as? ProjectionLAMObject {
let x = toProj.getX()
let y = toProj.getY()
details.append(NSMutableAttributedString.init(string: "X: \(x)", attributes: attributesMessage))
details.append(NSMutableAttributedString.init(string: "\nY: \(y)", attributes: attributesMessage))
}
case .utm:
if let toProj = toProj as? ProjectionUTMObject {
let x = toProj.getX()
let y = toProj.getY()
let zone = toProj.getZone()
let hemisphere = toProj.getHemisphere() == .north ? "North" : "South"
details.append(NSMutableAttributedString.init(string: "X: \(x)", attributes: attributesMessage))
details.append(NSMutableAttributedString.init(string: "\nY: \(y)", attributes: attributesMessage))
details.append(NSMutableAttributedString.init(string: "\nZone: \(zone)", attributes: attributesMessage))
details.append(
NSMutableAttributedString.init(string: "\nHemisphere: \(hemisphere)", attributes: attributesMessage))
}
case .mgrs:
if let toProj = toProj as? ProjectionMGRSObject {
let easting = toProj.getEasting()
let northing = toProj.getNorthing()
let zone = toProj.getZone()
let letters = toProj.getSq100kIdentifier()
details.append(NSMutableAttributedString.init(string: "Easting: \(easting)", attributes: attributesMessage))
details.append(NSMutableAttributedString.init(string: "\nNorthing: \(northing)", attributes: attributesMessage))
details.append(NSMutableAttributedString.init(string: "\nZone: \(zone)", attributes: attributesMessage))
details.append(
NSMutableAttributedString.init(
string: "\n100,000 meter square id: \(letters)", attributes: attributesMessage))
}
case .gk:
if let toProj = toProj as? ProjectionGKObject {
let easting = toProj.getEasting()
let northing = toProj.getNorthing()
let zone = toProj.getZone()
details.append(NSMutableAttributedString.init(string: "Easting: \(easting)", attributes: attributesMessage))
details.append(NSMutableAttributedString.init(string: "\nNorthing: \(northing)", attributes: attributesMessage))
details.append(NSMutableAttributedString.init(string: "\nZone: \(zone)", attributes: attributesMessage))
}
case .W3W:
if let toProj = toProj as? ProjectionW3WObject {
let words = toProj.getWords()
details.append(NSMutableAttributedString.init(string: "Words: \(words)", attributes: attributesMessage))
}
default:
break
}
}
completionHandler(error, details)
}
}
}