From 13346f7a3d0d70ec358c0772a4d24eac19bd9745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Mon, 16 May 2016 01:16:12 -0700 Subject: [PATCH] Added standalone marker API method MarkerOptions conforms to a common SnapshotOptionsProtocol and inherits from MarkerImage like Marker does. --- MapboxStatic/Overlay.swift | 66 +++++++----- MapboxStatic/Snapshot.swift | 120 ++++++++++++++++++++-- MapboxStaticTests/MapboxStaticTests.swift | 28 ++++- OS X.playground/Contents.swift | 14 +++ OS X.playground/contents.xcplayground | 2 +- README.md | 28 +++++ iOS.playground/Contents.swift | 14 +++ iOS.playground/contents.xcplayground | 2 +- iOS.playground/timeline.xctimeline | 7 +- 9 files changed, 246 insertions(+), 35 deletions(-) diff --git a/MapboxStatic/Overlay.swift b/MapboxStatic/Overlay.swift index 119d984..6a6f58a 100644 --- a/MapboxStatic/Overlay.swift +++ b/MapboxStatic/Overlay.swift @@ -26,22 +26,32 @@ public protocol Point: Overlay { } /** - A pin-shaped marker placed at a specific point on the map. - - The Maki icon set is [open source](https://github.com/mapbox/maki/) and [dedicated to the public domain](https://creativecommons.org/publicdomain/zero/1.0/). + A pin-shaped marker image. */ -@objc(MBMarker) -public class Marker: NSObject, Point { +@objc(MBMarkerImage) +public class MarkerImage: NSObject { /** The size of a marker. */ - @objc(MBMarkerSize) public enum Size: Int { + @objc(MBMarkerSize) + public enum Size: Int, CustomStringConvertible { /// Small. case Small /// Medium. case Medium /// Large. case Large + + public var description: String { + switch self { + case .Small: + return "s" + case .Medium: + return "m" + case .Large: + return "l" + } + } } /// Something simple that can be placed atop a marker. @@ -68,9 +78,6 @@ public class Marker: NSObject, Point { } } - /// The geographic coordinate to place the marker at. - public var coordinate: CLLocationCoordinate2D - /** The size of the marker. @@ -101,6 +108,26 @@ public class Marker: NSObject, Point { public var color: UIColor = .redColor() #endif + /** + Initializes a red marker image with the given options. + + - parameter size: The size of the marker. + - parameter label: A label or Maki icon to place atop the pin. + */ + internal init(size: Size, label: Label?) { + self.size = size + self.label = label + } +} + +/** + A pin-shaped marker placed at a specific point on the map. + */ +@objc(MBMarker) +public class Marker: MarkerImage, Point { + /// The geographic coordinate to place the marker at. + public var coordinate: CLLocationCoordinate2D + /** Initializes a red marker with the given options. @@ -110,10 +137,9 @@ public class Marker: NSObject, Point { */ private init(coordinate: CLLocationCoordinate2D, size: Size = .Small, - label: Label? = nil) { + label: Label?) { self.coordinate = coordinate - self.size = size - self.label = label + super.init(size: size, label: label) } /** @@ -143,7 +169,9 @@ public class Marker: NSObject, Point { } /** - Initializes a red marker with a Maki icon. + Initializes a red marker with a [Maki](https://www.mapbox.com/maki-icons/) icon. + + The Maki icon set is [open source](https://github.com/mapbox/maki/) and [dedicated to the public domain](https://creativecommons.org/publicdomain/zero/1.0/). - parameter coordinate: The geographic coordinate to place the marker at. - parameter size: The size of the marker. @@ -156,16 +184,6 @@ public class Marker: NSObject, Point { } public override var description: String { - let sizeComponent: String - switch size { - case .Small: - sizeComponent = "s" - case .Medium: - sizeComponent = "m" - case .Large: - sizeComponent = "l" - } - let labelComponent: String if let label = label { labelComponent = "-\(label)" @@ -173,7 +191,7 @@ public class Marker: NSObject, Point { labelComponent = "" } - return "pin-\(sizeComponent)\(labelComponent)+\(color.toHexString())(\(coordinate.longitude),\(coordinate.latitude))" + return "pin-\(size)\(labelComponent)+\(color.toHexString())(\(coordinate.longitude),\(coordinate.latitude))" } } diff --git a/MapboxStatic/Snapshot.swift b/MapboxStatic/Snapshot.swift index 6a7bb69..c901a44 100644 --- a/MapboxStatic/Snapshot.swift +++ b/MapboxStatic/Snapshot.swift @@ -9,11 +9,17 @@ /// The Mapbox access token specified in the main application bundle’s Info.plist. let defaultAccessToken = NSBundle.mainBundle().objectForInfoDictionaryKey("MGLMapboxAccessToken") as? String +@objc(MBSnapshotOptionsProtocol) +public protocol SnapshotOptionsProtocol: NSObjectProtocol { + var path: String { get } + var params: [NSURLQueryItem] { get } +} + /** A structure that determines what a snapshot depicts and how it is formatted. */ @objc(MBSnapshotOptions) -public class SnapshotOptions: NSObject { +public class SnapshotOptions: NSObject, SnapshotOptionsProtocol { /** An image format supported by the classic Static API. */ @@ -149,7 +155,7 @@ public class SnapshotOptions: NSObject { - returns: An HTTP URL path. */ - private var path: String { + public var path: String { assert(!mapIdentifiers.isEmpty, "At least one map identifier must be specified.") let tileSetComponent = mapIdentifiers.joinWithSeparator(",") @@ -207,7 +213,107 @@ public class SnapshotOptions: NSObject { - returns: The query URL component as an array of name/value pairs. */ - private var params: [NSURLQueryItem] { + public var params: [NSURLQueryItem] { + return [] + } +} + +/** + A structure that configures a standalone marker image and how it is formatted. + */ +@objc(MBMarkerOptions) +public class MarkerOptions: MarkerImage, SnapshotOptionsProtocol { + #if os(OSX) + /** + The scale factor of the image. + + If you multiply the logical size of the image (stored in the `size` property) by the value in this property, you get the dimensions of the image in pixels. + + The default value of this property matches the natural scale factor associated with the main screen. However, only images with a scale factor of 1.0 or 2.0 are ever returned by the classic Static API, so a scale factor of 1.0 of less results in a 1× (standard-resolution) image, while a scale factor greater than 1.0 results in a 2× (high-resolution or Retina) image. + */ + public var scale: CGFloat = NSScreen.mainScreen()?.backingScaleFactor ?? 1 + #elseif os(watchOS) + /** + The scale factor of the image. + + If you multiply the logical size of the image (stored in the `size` property) by the value in this property, you get the dimensions of the image in pixels. + + The default value of this property matches the natural scale factor associated with the screen. Images with a scale factor of 1.0 or 2.0 are ever returned by the classic Static API, so a scale factor of 1.0 of less results in a 1× (standard-resolution) image, while a scale factor greater than 1.0 results in a 2× (high-resolution or Retina) image. + */ + public var scale: CGFloat = WKInterfaceDevice.currentDevice().screenScale + #else + /** + The scale factor of the image. + + If you multiply the logical size of the image (stored in the `size` property) by the value in this property, you get the dimensions of the image in pixels. + + The default value of this property matches the natural scale factor associated with the main screen. However, only images with a scale factor of 1.0 or 2.0 are ever returned by the classic Static API, so a scale factor of 1.0 of less results in a 1× (standard-resolution) image, while a scale factor greater than 1.0 results in a 2× (high-resolution or Retina) image. + */ + public var scale: CGFloat = UIScreen.mainScreen().scale + #endif + + /** + Initializes a marker options instance. + + - parameter size: The size of the marker. + - parameter label: A label or Maki icon to place atop the pin. + */ + private override init(size: Size, label: Label?) { + super.init(size: size, label: label) + } + + /** + Initializes a marker options instance that results in a red marker labeled with an English letter. + + - parameter size: The size of the marker. + - parameter letter: An English letter from A through Z to place atop the pin. + */ + public convenience init(size: Size = .Small, letter: UniChar) { + self.init(size: size, label: .Letter(Character(UnicodeScalar(letter)))) + } + + /** + Initializes a marker options instance that results in a red marker labeled with a one- or two-digit number. + + - parameter size: The size of the marker. + - parameter number: A number from 0 through 99 to place atop the pin. + */ + public convenience init(size: Size = .Small, number: Int) { + self.init(size: size, label: .Number(number)) + } + + /** + Initializes a marker options instance that results in a red marker with a Maki icon. + + - parameter size: The size of the marker. + - parameter iconName: The name of a [Maki](https://www.mapbox.com/maki-icons/) icon to place atop the pin. + */ + public convenience init(size: Size = .Small, iconName: String) { + self.init(size: size, label: .IconName(iconName)) + } + + /** + The path of the HTTP request URL corresponding to the options in this instance. + + - returns: An HTTP URL path. + */ + public var path: String { + let labelComponent: String + if let label = label { + labelComponent = "-\(label)" + } else { + labelComponent = "" + } + + return "/v4/marker/pin-\(size)\(labelComponent)+\(color.toHexString())\(scale > 1 ? "@2x" : "").png" + } + + /** + The query component of the HTTP request URL corresponding to the options in this instance. + + - returns: The query URL component as an array of name/value pairs. + */ + public var params: [NSURLQueryItem] { return [] } } @@ -236,7 +342,7 @@ public class Snapshot: NSObject { public typealias CompletionHandler = (image: Image?, error: NSError?) -> Void /// Options that determine the contents and format of the output image. - public let options: SnapshotOptions + public let options: SnapshotOptionsProtocol /// The API endpoint to request the image from. private var apiEndpoint: String @@ -251,7 +357,7 @@ public class Snapshot: NSObject { - parameter accessToken: A Mapbox [access token](https://www.mapbox.com/help/define-access-token/). If an access token is not specified when initializing the snapshot object, it should be specified in the `MGLMapboxAccessToken` key in the main application bundle’s Info.plist. - parameter host: An optional hostname to the server API. The classic Mapbox Static API endpoint is used by default. */ - public init(options: SnapshotOptions, accessToken: String?, host: String?) { + public init(options: SnapshotOptionsProtocol, accessToken: String?, host: String?) { let accessToken = accessToken ?? defaultAccessToken assert(accessToken != nil && !accessToken!.isEmpty, "A Mapbox access token is required. Go to . In Info.plist, set the MGLMapboxAccessToken key to your access token, or use the Snapshot(options:accessToken:host:) initializer.") @@ -272,7 +378,7 @@ public class Snapshot: NSObject { - parameter options: Options that determine the contents and format of the output image. - parameter accessToken: A Mapbox [access token](https://www.mapbox.com/help/define-access-token/). If an access token is not specified when initializing the snapshot object, it should be specified in the `MGLMapboxAccessToken` key in the main application bundle’s Info.plist. */ - public convenience init(options: SnapshotOptions, accessToken: String?) { + public convenience init(options: SnapshotOptionsProtocol, accessToken: String?) { self.init(options: options, accessToken: accessToken, host: nil) } @@ -283,7 +389,7 @@ public class Snapshot: NSObject { - parameter options: Options that determine the contents and format of the output image. */ - public convenience init(options: SnapshotOptions) { + public convenience init(options: SnapshotOptionsProtocol) { self.init(options: options, accessToken: nil) } diff --git a/MapboxStaticTests/MapboxStaticTests.swift b/MapboxStaticTests/MapboxStaticTests.swift index cf5f99f..ec1cf44 100644 --- a/MapboxStaticTests/MapboxStaticTests.swift +++ b/MapboxStaticTests/MapboxStaticTests.swift @@ -452,5 +452,31 @@ class MapboxStaticTests: XCTestCase { waitForExpectationsWithTimeout(1, handler: nil) } - + + func testStandaloneMarker() { + let size = "m" + let label = "cafe" + let color = Color.brownColor() + let colorRaw = "996633" + + let markerExp = expectationWithDescription("builtin marker argument should format Maki request properly") + + let options = MarkerOptions( + size: .Medium, + iconName: "cafe") + options.color = color + + stub(isHost(serviceHost)) { request in + let scaleSuffix = options.scale == 1 ? "" : "@2x" + if let p = request.URL?.pathComponents where p[3] == "pin-\(size)-\(label)+\(colorRaw)\(scaleSuffix).png" { + markerExp.fulfill() + } + + return OHHTTPStubsResponse() + } + + Snapshot(options: options, accessToken: accessToken).image + + waitForExpectationsWithTimeout(1, handler: nil) + } } diff --git a/OS X.playground/Contents.swift b/OS X.playground/Contents.swift index 34bea9f..fda97c8 100644 --- a/OS X.playground/Contents.swift +++ b/OS X.playground/Contents.swift @@ -146,6 +146,20 @@ snapshot = Snapshot( accessToken: accessToken) snapshot.image +/*: + ### Standalone markers + + Use the `MarkerOptions` class to get a standalone marker image, which can be useful if you’re trying to composite it atop a map yourself. + */ +let markerOptions = MarkerOptions( + size: .Medium, + iconName: "cafe") +markerOptions.color = .brownColor() +snapshot = Snapshot( + options: markerOptions, + accessToken: accessToken) +snapshot.image + /*: ### File format and quality diff --git a/OS X.playground/contents.xcplayground b/OS X.playground/contents.xcplayground index 3de2b51..fd676d5 100644 --- a/OS X.playground/contents.xcplayground +++ b/OS X.playground/contents.xcplayground @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/README.md b/README.md index 50ef2fd..ebc747e 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,34 @@ options.overlays = @[path, geojsonOverlay, markerOverlay, customMarker]; ![](screenshots/autofit.png) +#### Standalone markers + +Use the `MarkerOptions` class to get a standalone marker image, which can be useful if you’re trying to composite it atop a map yourself. + +```swift +// main.swift +let options = MarkerOptions( + size: .Medium, + iconName: "cafe") +options.color = .brownColor() +let snapshot = Snapshot( + options: options, + accessToken: "<#your access token#>") +``` + +```objc +// main.m +MBMarkerOptions *options = [[MBMarkerOptions alloc] initWithSize:MBMarkerSizeMedium + iconName:@"cafe"]; +#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH + options.color = [UIColor brownColor]; +#elif TARGET_OS_MAC + options.color = [NSColor brownColor]; +#endif +MBSnapshot *snapshot = [[MBSnapshot alloc] initWithOptions:options + accessToken:@"<#your access token#>"]; +``` + #### File format and quality When creating a map, you can also specify PNG or JPEG image format as well as various [bandwidth-saving image qualities](https://www.mapbox.com/api-documentation/#retrieve-a-static-map-image). diff --git a/iOS.playground/Contents.swift b/iOS.playground/Contents.swift index b6fd664..19cddef 100644 --- a/iOS.playground/Contents.swift +++ b/iOS.playground/Contents.swift @@ -146,6 +146,20 @@ snapshot = Snapshot( accessToken: accessToken) snapshot.image +/*: + ### Standalone markers + + Use the `MarkerOptions` class to get a standalone marker image, which can be useful if you’re trying to composite it atop a map yourself. + */ +let markerOptions = MarkerOptions( + size: .Medium, + iconName: "cafe") +markerOptions.color = .brownColor() +snapshot = Snapshot( + options: markerOptions, + accessToken: accessToken) +snapshot.image + /*: ### File format and quality diff --git a/iOS.playground/contents.xcplayground b/iOS.playground/contents.xcplayground index 89da2d4..3596865 100644 --- a/iOS.playground/contents.xcplayground +++ b/iOS.playground/contents.xcplayground @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/iOS.playground/timeline.xctimeline b/iOS.playground/timeline.xctimeline index 486ca01..80284a7 100644 --- a/iOS.playground/timeline.xctimeline +++ b/iOS.playground/timeline.xctimeline @@ -28,7 +28,7 @@ shouldTrackSuperviewWidth = "NO"> @@ -57,5 +57,10 @@ selectedRepresentationIndex = "0" shouldTrackSuperviewWidth = "NO"> + +