Skip to content

Commit

Permalink
Merge pull request #30 from mapbox/1ec5-standalone-marker
Browse files Browse the repository at this point in the history
Add standalone marker API method
  • Loading branch information
1ec5 committed May 16, 2016
2 parents 17f89a0 + 13346f7 commit 543f199
Show file tree
Hide file tree
Showing 9 changed files with 246 additions and 35 deletions.
66 changes: 42 additions & 24 deletions MapboxStatic/Overlay.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.

Expand Down Expand Up @@ -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.

Expand All @@ -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)
}

/**
Expand Down Expand Up @@ -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.
Expand All @@ -156,24 +184,14 @@ 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)"
} else {
labelComponent = ""
}

return "pin-\(sizeComponent)\(labelComponent)+\(color.toHexString())(\(coordinate.longitude),\(coordinate.latitude))"
return "pin-\(size)\(labelComponent)+\(color.toHexString())(\(coordinate.longitude),\(coordinate.latitude))"
}
}

Expand Down
120 changes: 113 additions & 7 deletions MapboxStatic/Snapshot.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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(",")

Expand Down Expand Up @@ -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 []
}
}
Expand Down Expand Up @@ -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
Expand All @@ -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 <https://www.mapbox.com/studio/account/tokens/>. In Info.plist, set the MGLMapboxAccessToken key to your access token, or use the Snapshot(options:accessToken:host:) initializer.")

Expand All @@ -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)
}

Expand All @@ -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)
}

Expand Down
28 changes: 27 additions & 1 deletion MapboxStaticTests/MapboxStaticTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
14 changes: 14 additions & 0 deletions OS X.playground/Contents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion OS X.playground/contents.xcplayground
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='5.0' target-platform='osx' display-mode='rendered'>
<playground version='5.0' target-platform='osx' display-mode='raw'>
<timeline fileName='timeline.xctimeline'/>
</playground>
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
Loading

0 comments on commit 543f199

Please sign in to comment.