-
-
Save incanus/8c277a0700c917ac58600d93cd849bb8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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