Reputation: 6658
I saw simple code for calculating a grid of tile bounds needed for a given MKMapRect
but I can't find it now. IIRC it was a function built-in to MapKit
.
Given a MKMapRect
like mapview.visibleMapRect
and zoom level, how can I calculate an array of tile paths that will be used for the given rect?
import MapKit
class ViewController: UIViewController {
@IBOutlet weak var mapview: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
mapview.delegate = self
let location = CLLocationCoordinate2D(latitude: 44.0, longitude: -120.0)
let span = MKCoordinateSpanMake(1.0, 1.0)
let region = MKCoordinateRegion(center: location, span: span)
mapview.setRegion(region, animated: true)
let rect = mapview.visibleMapRect
let requiredTiles = ?
}
}
Upvotes: 0
Views: 119
Reputation: 1011
I've been working with MapKit and MKTileOverlays for years, and I don't recall ever seeing a built-in way to calculate tiles in an area. But there are some libraries out there that can help with it. For example, gis-tools, and the MapTile
struct.
You can initialize a MapTile
with a coordinate and zoom level, so you can calculate the northwest and southeast corners of your MKMapRect
, convert them to coordinates and then to MapTiles
, and then use a simple nested for
loop to go through the tile keys.
That would look something like this:
let nwCoord = Coordinate3D(mapRect.origin.coordinate)
let seCoord = Coordinate3D(MKMapPoint(x: mapRect.maxX, y: mapRect.maxY).coordinate)
let nwTile = MapTile(coordinate: nwCoord, atZoom: zoom)
let seTile = MapTile(coordinate: seCoord, atZoom: zoom)
var result = Set<MapTile>()
for x in nwTile.x...seTile.x {
for y in nwTile.y...seTile.y {
result.insert(MapTile(x: x, y: y, z: zoom))
}
}
Maybe not the exact elegant solution you were looking for, but hopefully it's helpful.
Upvotes: 0
Reputation: 6849
The simplest answer for most use cases is that you don't do the calculation, and let MapKit do the work:
You define your own subclass of MKTileOverlay, set
class DigitransitTileOverlay: MKTileOverlay {
override func url(forTilePath path: MKTileOverlayPath) -> URL {
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = "cdn.digitransit.fi"
// standard tile size for MKTileOverlay is 265
//let startPath = "/map/v1/hsl-map-256" // the hsl-map part can be customized with language parameters
// standard tile size for digitransit is 512
let startPath = "/map/v1/hsl-map" // the hsl-map part can be customized with language parameters
let size: String = "" // @2x for retina, empty for normal
urlComponents.path = startPath + "/\(path.z)/\(path.x)/\(path.y)\(size).png"
let url: URL = urlComponents.url!
return url
}
}
Then you set
digiTransitTileOverlay.canReplaceMapContent = false
digiTransitTileOverlay.tileSize = CGSize(width: 512, height: 512)
mapView.addOverlay(digiTransitTileOverlay, level: .aboveRoads)
tileSize is not necessary, but appears to make some servers faster, as popular web libraries prefer 512 x 512 so this tile size is cached my many servers.
and define a renderer:
public override func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer? {
if let tileOverlay = overlay as? DigitransitTileOverlay {
let tileOverlayRenderer = MKTileOverlayRenderer(tileOverlay: tileOverlay)
return tileOverlayRenderer
}
return nil
}
As you see, url(forTilePath is called by MapKit for each necessary tile in the region you have set. You get parameters for each tile in MKTileOverlayPath
Upvotes: -1