Skip to main content

Projections

Last updated: April 24, 2026 | 7 minutes read

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)
}
}
}