Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow fetching terrain texture without terrain heights #73

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 55 additions & 51 deletions MapboxSceneKit/MapboxImageAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,57 +4,57 @@ import CoreLocation
import MapboxMobileEvents

/**
`MapboxImageAPI` provides a convenience wrapper for fetching tiles through the Mapbox web APIs.
`MapboxImageAPI` provides a convenience wrapper for fetching tiles through the Mapbox web APIs.
**/
@objc(MBImageAPI)
public final class MapboxImageAPI: NSObject {
/**
Image format for tileset fetcher, PNG uncompressed.
**/
@objc static let TileImageFormatPNG = "pngraw"

/**
Image format for tileset fetcher, JPG uncompressed.
**/
@objc static let TileImageFormatJPG100 = "jpg"

/**
Image format for tileset fetcher, JPG at compression 0.9.
**/
@objc static let TileImageFormatJPG90 = "jpg90"

/**
Image format for tileset fetcher, JPG at compression 0.8.
**/
@objc static let TileImageFormatJPG80 = "jpg80"

/**
Image format for tileset fetcher, JPG at compression 0.7.
**/
@objc static let TileImageFormatJPG70 = "jpg70"

/**
In-progress callback typealias as tiles are loaded with the expected total needed and the current process as a percent.
**/
public typealias TileLoadProgressCallback = (_ progress: Float, _ total: Int) -> Void

/**
Completion typealias for when tile loading is complete and the image is ready.
**/
public typealias TileLoadCompletion = (_ image: UIImage?, _ error: NSError?) -> Void

fileprivate static let tileSize = CGSize(width: 256, height: 256)
fileprivate static let styleSize = CGSize(width: 256, height: 256)
fileprivate var pendingFetches = [UUID: [UUID]]()
public static var tileSizeWidth: Double {
get { return Double(MapboxImageAPI.tileSize.width) }
}

private static let queue = DispatchQueue(label: "com.mapbox.scenekit.processing", attributes: [.concurrent])

private let httpAPI: MapboxHTTPAPI
private var eventsManager: MMEEventsManager = MMEEventsManager.shared()

@objc
public override init() {
var mapboxAccessToken: String? = nil
Expand All @@ -63,40 +63,40 @@ public final class MapboxImageAPI: NSObject {
let token = dict["MGLMapboxAccessToken"] as? String {
mapboxAccessToken = token
}

if let mapboxAccessToken = mapboxAccessToken {
eventsManager.isMetricsEnabledInSimulator = true
eventsManager.isMetricsEnabledForInUsePermissions = false
eventsManager.initialize(withAccessToken: mapboxAccessToken, userAgentBase: "mapbox-scenekit-ios", hostSDKVersion: String(describing: Bundle(for: MapboxImageAPI.self).object(forInfoDictionaryKey: "CFBundleShortVersionString")!))
eventsManager.disableLocationMetrics()
eventsManager.sendTurnstileEvent()

httpAPI = MapboxHTTPAPI(accessToken: mapboxAccessToken)
} else {
assert(false, "`accessToken` must be set in the Info.plist as `MGLMapboxAccessToken` or the `Route` passed into the `RouteController` must have the `accessToken` property set.")
httpAPI = MapboxHTTPAPI(accessToken: "")
}

super.init()
}

deinit {
let tasks = pendingFetches.keys
for task in tasks {
cancelRequestWithID(task)
}
}

//MARK: - Public API

/**
Used to fetch a stitched together set of tiles from the tileset API. The tileset API can be used to fetch images representing data Mapbox datasets, such
as streets-v10, terrain-rgb, and tilesets a user has uploaded through Mapbox studio.

See: https://www.mapbox.com/api-documentation/#retrieve-tiles

Expected formats are one of `TileImageFormatPNG`, `TileImageFormatJPG100`, `TileImageFormatJPG90`, `TileImageFormatJPG80`, `TileImageFormatJPG70`.

Returns a UUID representing the task managing the fetching and stitching together of the tile images. Used for cancellation if needed.
**/
@objc
Expand All @@ -106,30 +106,30 @@ public final class MapboxImageAPI: NSObject {
northEastCorner: CLLocation,
format: String, progress: TileLoadProgressCallback? = nil,
completion: @escaping TileLoadCompletion) -> UUID {

let bounding = MapboxImageAPI.tiles(zoom: zoom, southWestCorner: southWestCorner, northEastCorner: northEastCorner, tileSize: MapboxImageAPI.tileSize)
let imageBuilder = ImageBuilder(xs: bounding.xs.count, ys: bounding.ys.count, tileSize: MapboxImageAPI.tileSize, insets: bounding.insets)

let group = DispatchGroup()
let groupID = UUID()
pendingFetches[groupID] = [UUID]()


var completed: Int = 0
let total = bounding.xs.count * bounding.ys.count

DispatchQueue.main.async {
self.pendingFetches[groupID] = [UUID]()
progress?(Float(0) / Float(total), total)
}

var error: FetchError?
for (xindex, x) in bounding.xs.enumerated() {
for (yindex, y) in bounding.ys.enumerated() {
group.enter()
if let task = httpAPI.tileset(tileset, zoomLevel: zoom, xTile: x, yTile: y, format: format, completion: { image, fetchError in
MapboxImageAPI.queue.async {
defer {
completed += 1
DispatchQueue.main.async {
completed += 1
progress?(Float(completed) / Float(total), total)
}
group.leave()
Expand All @@ -139,23 +139,25 @@ public final class MapboxImageAPI: NSObject {
NSLog("Couldn't get image for tile {\(zoom),\(x),\(y)}")
return
}

imageBuilder.addTile(x: xindex, y: yindex, image: image)
}
}) {
self.pendingFetches[groupID]?.append(task)
group.notify(queue: DispatchQueue.main) {
self.pendingFetches[groupID]?.append(task)
}
}
}
}

group.notify(queue: MapboxImageAPI.queue) {
guard error == nil else {
DispatchQueue.main.async {
completion(nil, error?.toNSError())
}
return
}

//Color data gets messed up if the user expectes a PNG back but doesn't get one
if format == MapboxImageAPI.TileImageFormatPNG, let image = imageBuilder.makeImage(), let png = UIImagePNGRepresentation(image), let formattedImage = UIImage(data: png) {
DispatchQueue.main.async {
Expand All @@ -171,16 +173,16 @@ public final class MapboxImageAPI: NSObject {
}
}
}

return groupID
}

/**
Used to fetch a stitched together set of tiles from the style API. The style API can be used to fetch images representing user-created styles
via Mapbox Studio. Styles are referenced by `username.id`.

See: https://www.mapbox.com/api-documentation/#static

Returns a UUID representing the task managing the fetching and stitching together of the tile images. Used for cancellation if needed.
**/
@objc
Expand All @@ -189,27 +191,27 @@ public final class MapboxImageAPI: NSObject {
let returnedSize = MapboxImageAPI.styleSize * CGFloat(2.0)
let bounding = MapboxImageAPI.tiles(zoom: zoom, southWestCorner: southWestCorner, northEastCorner: northEastCorner, tileSize: MapboxImageAPI.styleSize)
let imageBuilder = ImageBuilder(xs: bounding.xs.count, ys: bounding.ys.count, tileSize: returnedSize, insets: bounding.insets * 2)

let group = DispatchGroup()
let groupID = UUID()
pendingFetches[groupID] = [UUID]()


var completed: Int = 0
let total = bounding.xs.count * bounding.ys.count

DispatchQueue.main.async {
self.pendingFetches[groupID] = [UUID]()
progress?(Float(0) / Float(total), total)
}

var error: FetchError?
for (xindex, x) in bounding.xs.enumerated() {
for (yindex, y) in bounding.ys.enumerated() {
group.enter()
if let task = httpAPI.style(style, zoomLevel: zoom, xTile: x, yTile: y, tileSize: returnedSize, completion: { image, fetchError in
MapboxImageAPI.queue.async {
defer {
completed += 1
DispatchQueue.main.async {
completed += 1
progress?(Float(completed) / Float(total), total)
}
group.leave()
Expand All @@ -219,23 +221,25 @@ public final class MapboxImageAPI: NSObject {
NSLog("Couldn't get image for tile {\(zoom),\(x),\(y)}")
return
}

imageBuilder.addTile(x: xindex, y: yindex, image: image)
}
}) {
self.pendingFetches[groupID]?.append(task)
group.notify(queue: DispatchQueue.main) {
self.pendingFetches[groupID]?.append(task)
}
}
}
}

group.notify(queue: DispatchQueue.main) {
self.pendingFetches.removeValue(forKey: groupID)
completion(error == nil ? imageBuilder.makeImage() : nil, error?.toNSError())
}

return groupID
}

/**
Used to cancel an in-progress tile fetch request given the UUID returned from the intiial call.
**/
Expand All @@ -249,9 +253,9 @@ public final class MapboxImageAPI: NSObject {
}
pendingFetches.removeValue(forKey: groupID)
}

//MARK: - Helpers

internal static func tiles(zoom: Int, southWestCorner: CLLocation, northEastCorner: CLLocation, tileSize: CGSize) -> (xs: [Int], ys: [Int], insets: UIEdgeInsets) {

let minLat = southWestCorner.coordinate.latitude
Expand All @@ -262,13 +266,13 @@ public final class MapboxImageAPI: NSObject {
var xs = [Int]()
var ys = [Int]()
var insets = UIEdgeInsets.zero

for lat in [minLat, maxLat] {
for lon in [minLon, maxLon] {
let tile = Math.latLng2tile(lat: lat, lon: lon, zoom: zoom, tileSize: tileSize)
xs.append(tile.xTile)
ys.append(tile.yTile)

if lat == minLat {
insets = UIEdgeInsets(top: insets.top, left: insets.left, bottom: tileSize.height - CGFloat(tile.yPos), right: insets.right)
}
Expand All @@ -283,7 +287,7 @@ public final class MapboxImageAPI: NSObject {
}
}
}

return ((xs.min()!...xs.max()!).map({ $0 }), (ys.min()!...ys.max()!).map({ $0 }), insets)
}
}
3 changes: 1 addition & 2 deletions MapboxSceneKit/TerrainNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -251,9 +251,8 @@ open class TerrainNode: SCNNode {
/// - style: Mapbox style ID for given texture.
/// - progress: Handler for fetch progress change.
/// - completion: Handler for complete texture update.
@available(*, deprecated, message: "DEPRECATED - Please use instead fetchTerrainAndTexture.")
@objc public func fetchTerrainTexture(_ style: String, progress: MapboxImageAPI.TileLoadProgressCallback? = nil, completion: @escaping MapboxImageAPI.TileLoadCompletion) {
fetchTerrainTexture(style, zoom: terrainZoomLevel, progress: progress, completion: completion)
fetchTerrainTexture(style, zoom: styleZoomLevel, progress: progress, completion: completion)
}

private func fetchTerrainTexture(_ style: String, zoom: Int, progress: MapboxImageAPI.TileLoadProgressCallback? = nil, completion: @escaping MapboxImageAPI.TileLoadCompletion) {
Expand Down