Map control

Version 2.0 - April 2021

πŸ“˜

Sourcecode

Please see the UgMapControl project for the source code created in this chapter. See Installing examples for information on how to download the examples.

Introduction

Many mobile apps need a map control. We wrapped the native map control from iOS and Android respectively and access its functionality using one common interface and API. Functionality includes

  • positioning map control freely
  • zooming in and out by setting the size of the area that the control displays
  • moving to certain location
  • using annotations to position image, i.e. drop your location pin

General settings

Overview of map classes and structures

SCDPlatformLocationCoordinate

Property/methodDescriptionComment/Example
latitude:NSNumberSets the lattitude
longitude:NSNumberSets the longitude
let location = SCDPlatformLocationCoordinate(latitude:44.061334,longitude:-79.454920)

SCDWidgetsMapWidget

Property/methodDescriptionComment/Example
mapTypeSet the map typeSCDWidgetsMapType.standard,satellite,hybrid
setRegion( SCDPlatformLocationCoordinate, latitudinalMeters:Int, longitudinalMeters:Int)Sets the region you want to displayUses SCDWidgetsMapRegion
userLocationCurrent user locationType is SCDPlatformLocationCoordinate
isShowUserLocation : BoolChecks if user location can be shown

SCDWidgetsMapRegion

Property/ methodDescriptionComment / Example
centerCenters the region around this coordinatespecify location using SCDPlatformLocationCoordinate
latitudinalMetersWidth of the region in meters1000 (map control will cover 1000 meters horizontally)
longitudinalMetersHeight of the region in meters1000 (map control will cover 1000 meters vertically)
self.mapWidget.setRegion(location, latitudinalMeters:1000,longitudinalMeters:1000)

Minimal map application

  • Drag and drop the map control on the main page
  • Drag three buttons below the map control and name them Torronto, Current Location and Sushi
  • Drag three buttons below the map control and name them Standard, Satellite and Hybrid

Add this minimal code to initialize the map control to the main.page.swift (easiest is to replace existing code)

import ScadeKit

class MainPageAdapter: SCDLatticePageAdapter {
    
    // set coordinates
    let perfectlySoftLocation = SCDPlatformLocationCoordinate(latitude:44.061334,longitude:-79.454920)
    let bestSushiLocation = SCDPlatformLocationCoordinate(latitude:43.655156,longitude:-79.385293)
    
    // page adapter initialization
    override func load(_ path: String) {    
        
        super.load(path)
        
        // Goto Toronto
        self.btnToronto.onClick{ _ in self.goto(coordinates:self.perfectlySoftLocation) }
    
        // Goto current location
        self.btnCurrLoc.onClick{_ in self.gotoCurrentLocation()}
        
        // Goto Sushi place
        self.btnSushi.onClick{_ in self.goto(coordinates:self.bestSushiLocation)}
        
        
        // Set Map type
        self.btnStandard.onClick{_ in self.setMapType(self.btnStandard.name)}
        
        // Set Map to hybrid
        self.btnHybrid.onClick{_ in self.setMapType(self.btnHybrid.name)}
    
        
        // set Map to Satellite
        self.btnSatellite.onClick{_ in self.setMapType(self.btnSatellite.name)}
        
        // set Pin
        self.setPerfectlySoftPin()
        
        // demo overlays
        self.setOverlayAroundSushiPlace()
    }
    
    func setMapType(_ name:String) {
        
        switch(name) {
            case "btnHybrid":
                self.mapWidget.mapType = SCDWidgetsMapType.hybrid
            case "btnSatellite":
                self.mapWidget.mapType = SCDWidgetsMapType.satellite
            case "btnStandard":
                self.mapWidget.mapType = SCDWidgetsMapType.standard 
            default:
                print("not covered")
        }
    }
    
    func goto(coordinates:SCDPlatformLocationCoordinate) {
          self.mapWidget.setRegion(coordinates,latitudinalMeters:1000,longitudinalMeters:1000)
    }
    
    func setPerfectlySoftPin() {
        self.setPinAsAnnotaton(coordinate:perfectlySoftLocation,imagePath:"Assets/pinOrange.svg")
    }
    
    func setPinAsAnnotaton(coordinate:SCDPlatformLocationCoordinate, imagePath:String) {
        
        // We will further simplify the API
        let svgImage = SCDSvgImage(width:SCDSvgUnit(value:25),height:SCDSvgUnit(value:25))
        svgImage.xhref = imagePath
                        
        let ann = SCDWidgetsMapAnnotation(location:coordinate)
        ann.drawing = svgImage
        
        self.mapWidget.annotations.append(ann)
    }
    
    func setOverlayAroundSushiPlace() {
        
        // get the map specific coordinates
        let coor2d = mapWidget.convertFromGeoLocation(bestSushiLocation)
        
        // Create overlay circle of radius 1000m 
        let overlayCircle = SCDSvgCircle(cx:SCDSvgUnit(value:coor2d!.x),cy:SCDSvgUnit(value:coor2d!.y),r:SCDSvgUnit(value:1000))
        overlayCircle.fill = SCDColor(r: 1, g: 0, b: 0, a: 0.2)
        
        // Create an overlay
        let overlay = SCDWidgetsMapOverlay()
        overlay.drawing = overlayCircle
        
        // Add overlay onto the map
        mapWidget.overlays.append(overlay)
        
    }
    
    func gotoCurrentLocation() {    
        // currently, the desktop returns isShowUserLocation==false
        if(self.mapWidget.showUserLocation) {
            let curr = mapWidget.userLocation
            print(curr?.latitude ?? 0)
            print(curr!.longitude)
            mapWidget.moveToUserLocation()
        }
    }   
}

Setting the map type

The type of the map can easily be set using SCDWidgetsMapType.

func setMapType(_ name:String) {
 switch(name) {
  case "btnHybrid":
    self.mapWidget.mapType = SCDWidgetsMapType.hybrid
  case "btnSatellite":
    self.mapWidget.mapType = SCDWidgetsMapType.satellite
  case "btnStandard":
    self.mapWidget.mapType = SCDWidgetsMapType.standard 
   default:
    print("not covered")
 }
}

Setting the zoom level

Setting the zoom level is easy. Just set new values for latitudinalMeters and longitudinalMeters, the zoom respectively around the center coordinate.

// Use the meters metric to set the zoom level. Make sure to use a new instance
// set new region
    self.mapWidget.setRegion(coordinates,latitudinalMeters:1000,longitudinalMeters:1000)

Getting and going to current location

The current location is stored in the map widget's userLocation variable. However, you should first check the permission using isShowUserLocation() before going to the current location using moveToUserLocation()

func gotoCurrentLocation() {
// currently, the desktop returns isShowUserLocation==false
  if(mapWidget.isShowUserLocation) {
    let curr = mapWidget.userLocation
    print(curr.latitude)
    print(curr.longitude)
    mapWidget.moveToUserLocation()
  }
}

Map annotations

πŸ“˜

Annotations position images and are zoom agnostic

Annotations are a common concept on both iOS and Android. The most common use case is for instance setting a pin to indicate a location Info

Follow these steps:

  1. Use a SVG graphic to represent the annotation, i.e. the pin
  2. Always set the width and height, these parameters are mandatory
  3. Use the xhref field to set the relative location of the SVG file
  4. Create an annotation at a specific location using the location field
  5. and assign the image in the drawing field
  6. Finally, add the annotation to the widget's list of annotations: .annotations
func setPinAsAnnotaton(coordinate:SCDPlatformLocationCoordinate, imagePath:String) {

    // We will further simplify the API
    let svgImage = SCDSvgImage(width:SCDSvgUnit(value:25),height:SCDSvgUnit(value:25))
    svgImage.xhref = imagePath

    let ann = SCDWidgetsMapAnnotation(location:coordinate)
    ann.drawing = svgImage

    self.mapWidget.annotations.append(ann)
}

Map overlays

πŸ“˜

Overlays are images on a map that adjust to the zoom level

Depending on weather you zoom in or out, the image adjusts to the current zoom and increases or decreases in size.

Follow these steps:

  1. Convert your usual coordinates into the coordinate system of your mobile os using the convert method
  2. Create a scalable image. We use a circle in this example. Make sure to use a color.
  3. Create a map overlay and set the image to use using the drawing field
  4. Append the overlay to the list of overlays
func setOverlayAroundSushiPlace() {

  // get the map specific coordinates
  let coor2d = mapWidget.convert(fromGeoLocation: bestSushiLocation)

  // Create overlay circle of radius 1000m 
  let overlayCircle = SCDSvgCircle(cx:SCDSvgUnit(value:coor2d.x),cy:SCDSvgUnit(value:coor2d.y),r:SCDSvgUnit(value:1000))
  overlayCircle.fill = SCDSvgRGBColor(a:0.2,r:1,g:0,b:0)

  // Create an overlay
  let overlay = SCDWidgetsMapOverlay()
  overlay.drawing = overlayCircle

  // Add overlay onto the map
  mapWidget.overlays.append(overlay)

}

Troubleshooting

GoogleMaps in China

Please understand that Google Maps is not or only partially supported in China and the MapWidget won't work on Android