Skip to content

Instantly share code, notes, and snippets.

@incanus
Last active March 2, 2017 23:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save incanus/8c277a0700c917ac58600d93cd849bb8 to your computer and use it in GitHub Desktop.
Save incanus/8c277a0700c917ac58600d93cd849bb8 to your computer and use it in GitHub Desktop.
import UIKit
import Mapbox
class ViewController: UIViewController, MGLMapViewDelegate {
var map: MGLMapView!
override func viewDidLoad() {
super.viewDidLoad()
map = MGLMapView(frame: view.bounds, styleURL: MGLStyle.darkStyleURL(withVersion: MGLStyleDefaultVersion))
map.autoresizingMask = [.flexibleWidth, .flexibleHeight]
map.delegate = self
view.addSubview(map)
}
func mapView(_ mapView: MGLMapView, didFinishLoading style: MGLStyle) {
//
// Here we replicate GroundOverlay functionality through a combination of
// using the image as an icon in a symbol layer at runtime, plus adjusting
// its scale during map zooms using data-driven styling.
//
//
// Define the bounds of the overlay on the map.
//
let north: CLLocationDegrees = 40.773941
let south: CLLocationDegrees = 40.712216
let east: CLLocationDegrees = -74.12544
let west: CLLocationDegrees = -74.22655
//
// Construct a reference feature for the bounds for later coordinate use.
//
let polygon = MGLPolygon(coordinates: [
CLLocationCoordinate2D(latitude: north, longitude: west),
CLLocationCoordinate2D(latitude: north, longitude: east),
CLLocationCoordinate2D(latitude: south, longitude: east),
CLLocationCoordinate2D(latitude: south, longitude: west)
], count: 4)
//
// Zoom the map to the region in question (plus padding) for clarity.
//
mapView.setVisibleCoordinateBounds(polygon.overlayBounds,
edgePadding: UIEdgeInsetsMake(50, 50, 50, 50),
animated: false)
//
// Create a point feature at the polygon center to attach the icon to.
//
let center = CLLocationCoordinate2D(latitude: (north + south) / 2,
longitude: (east + west) / 2)
let point = MGLPointFeature()
point.coordinate = center
//
// Dynamically add a source to the map with that point feature.
//
let source = MGLShapeSource(identifier: "overlay", shape: point, options: nil)
style.addSource(source)
//
// Load up the image, figure out its native size, and, via the current map scale
// and dimensions of the reference polygon, what scale the native image would have to
// be shown at for the current zoom.
//
let image = UIImage(named: "newark_nj_1922.jpg")!
let metersPerPoint = mapView.metersPerPoint(atLatitude: center.latitude)
let polygonMetersWidth = CLLocation(latitude: north, longitude: west).distance(from: CLLocation(latitude: north, longitude: east))
let polygonPointsWidth = CGFloat(polygonMetersWidth / metersPerPoint)
let currentImageScale = image.size.width / polygonPointsWidth
//
// Resize the image so that it's initially drawn at current zoom. We'll call
// this scale factor 1.0.
//
let rect = CGRect(x: 0, y: 0, width: image.size.width / currentImageScale, height: image.size.height / currentImageScale)
UIGraphicsBeginImageContextWithOptions(rect.size, true, UIScreen.main.scale)
image.draw(in: rect)
let scaledImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
//
// Add the newly-scaled image to the style for use as an icon.
//
style.setImage(scaledImage, forName: "overlay")
//
// Work backwards and forwards through all zoom levels below and above the current
// one, defining an image scale factor that decreases by half when going down and
// doubles going when going up. We end up with a set of scale factor stops for all
// zoom levels the map can be seen at.
//
// e.g.
//
// [ ...
// (currentZoom - 2): 0.25
// (currentZoom - 1): 0.5,
// currentZoom: 1.0, <== starting point for our newly-scaled image
// (currentZoom + 1): 2.0,
// (currentZoom + 2): 4.0,
// ... ]
//
var stops = [NSNumber : MGLStyleValue<NSNumber>]()
var zoom = mapView.zoomLevel
var scale = 1.0
while zoom > 0 {
stops[NSNumber(value: zoom)] = MGLStyleValue(rawValue: NSNumber(value: scale))
zoom = zoom - 1
scale = scale / 2
}
zoom = mapView.zoomLevel + 1
scale = 2.0
while zoom < mapView.maximumZoomLevel + 1 {
stops[NSNumber(value: zoom)] = MGLStyleValue(rawValue: NSNumber(value: scale))
zoom = zoom + 1
scale = scale * 2
}
//
// Create a runtime styling symbol layer that uses our source above to position the
// scaled image at the polygon center. Since we scaled the image for the current
// zoom, it should completely and perfectly fill the polygon coordinates.
//
let layer = MGLSymbolStyleLayer(identifier: "overlay", source: source)
layer.iconImageName = MGLStyleValue(rawValue: "overlay")
layer.iconOpacity = MGLStyleValue(rawValue: 0.5)
//
// Ensure that the "icon" rotates with the map.
//
layer.iconRotationAlignment = MGLStyleValue(rawValue: NSNumber(value: MGLIconRotationAlignment.map.rawValue))
//
// Use the scale factor stops defined above and data-driven styling to create
// a camera style function that adjusts the icon's scale smoothly for each zoom.
// An exponential interpolation mode defaults to an interpolation base of 1.0,
// which means linearly scaling between the defined zoom levels.
//
layer.iconScale = MGLCameraStyleFunction(interpolationMode: .exponential,
cameraStops: stops,
options: nil)
//
// Add the symbol layer (below labels) and we're off!
//
style.insertLayer(layer, below: style.layer(withIdentifier: "waterway-label")!)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment