Skip to content

Commit

Permalink
initial import
Browse files Browse the repository at this point in the history
  • Loading branch information
incanus committed Oct 18, 2014
0 parents commit c31ba1f
Show file tree
Hide file tree
Showing 7 changed files with 382 additions and 0 deletions.
24 changes: 24 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
MapboxStatic copyright (c) 2014, Mapbox. All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

- Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
- Neither the name "Mapbox" nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
232 changes: 232 additions & 0 deletions MapboxStatic.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
import CoreLocation
import Foundation
import UIKit

private extension UIColor {

private func toHexString() -> String {

var r: CGFloat = 0
var g: CGFloat = 0
var b: CGFloat = 0
var a: CGFloat = 0

self.getRed(&r, green: &g, blue: &b, alpha: &a)

r *= 255
g *= 255
b *= 255

return NSString(format: "%02x%02x%02x", Int(r), Int(g), Int(b))
}

private class func colorWithHexString(var hexString: String) -> UIColor {

hexString = hexString.stringByReplacingOccurrencesOfString("#", withString: "")

if (countElements(hexString) == 3) {
hexString = "\(Array(hexString)[0])\(Array(hexString)[0])\(Array(hexString)[1])\(Array(hexString)[1])\(Array(hexString)[2])\(Array(hexString)[2])"
}

if (countElements(hexString) == 6) {
var r: CGFloat = 0
var g: CGFloat = 0
var b: CGFloat = 0

var hexInt: UInt32 = 0

if (NSScanner(string: hexString).scanHexInt(&hexInt)) {
r = CGFloat((hexInt >> 16) & 0xff) / 255
g = CGFloat((hexInt >> 8) & 0xff) / 255
b = CGFloat(hexInt & 0xff) / 255

return UIColor(red: r, green: g, blue: b, alpha: 1)
}
}

return UIColor.blackColor()
}
}

public class MapboxStaticMap {

let requestURLStringBase = "https://api.tiles.mapbox.com/v4/"

public enum ImageFormat: String {
case PNG = "png"
case PNG256 = "png256"
case PNG128 = "png128"
case PNG64 = "png64"
case PNG32 = "png32"
case JPEG = "jpg"
case JPEG90 = "jpg90"
case JPEG80 = "jpg80"
case JPEG70 = "jpg70"
}

public var requestURL: NSURL

public lazy var image: UIImage? = {
if let data = NSData(contentsOfURL: self.requestURL) {
return UIImage(data: data)
} else {
return nil
}
}()

public func imageWithCompletionHandler(handler: (UIImage? -> Void)) {
let task = NSURLSession.sharedSession().dataTaskWithURL(requestURL, completionHandler: { (data: NSData!, response: NSURLResponse!, error: NSError!) in
let image = UIImage(data: data)
dispatch_async(dispatch_get_main_queue(), { Void -> Void in
handler(image)
})
})
task.resume()
}

init(mapID: String,
center: CLLocationCoordinate2D,
zoom: Int,
size: CGSize,
accessToken: String,
format: ImageFormat = .PNG,
retina: Bool = false,
overlays: [Overlay] = []) {

var requestURLString = requestURLStringBase
requestURLString += mapID
requestURLString += "/"

if (overlays.count > 0) {
requestURLString += join(",", overlays.map({ return $0.requestString }))
requestURLString += "/"
}

requestURLString += "\(center.longitude),\(center.latitude),\(zoom)/"
requestURLString += "\(Int(size.width))x\(Int(size.height))"
requestURLString += (retina ? "@2x" : "")
requestURLString += "."
requestURLString += format.rawValue
requestURLString += "?access_token="
requestURLString += accessToken

requestURL = NSURL(string: requestURLString)!
}

public enum MarkerSize: String {
case Small = "s"
case Medium = "m"
case Large = "l"
}

public class Overlay {

var requestString: String

init() {
requestString = ""
}

}

public class Marker: Overlay {

init(coordinate: CLLocationCoordinate2D,
size: MarkerSize = .Small,
label: String = "",
color: UIColor = UIColor.redColor()) {

super.init()

requestString = "pin-"
requestString += size.rawValue

if (countElements(label) > 0) {
requestString += "-" + label
}

requestString += "+" + color.toHexString()
requestString += "(\(coordinate.longitude),\(coordinate.latitude))"

}

}

public class GeoJSON: Overlay {

init(GeoJSON: String) {

super.init()

requestString = "geojson("
requestString += GeoJSON.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!
requestString += ")"

}

}

public class Path: Overlay {

// based on https://github.com/mapbox/polyline

private func polylineEncode(coordinates: [CLLocationCoordinate2D]) -> String {

func encodeCoordinate(var coordinate: CLLocationDegrees) -> String {

var c = Int(round(coordinate * 1e5))

c = c << 1

if (c < 0) {
c = ~c
}

var output = ""

while (c >= 0x20) {
output += String(UnicodeScalar((0x20 | (c & 0x1f)) + 63))
c = c >> 5
}

output += String(UnicodeScalar(c + 63))

return output
}

var output = encodeCoordinate(coordinates[0].latitude) + encodeCoordinate(coordinates[0].longitude)

for var i = 1; i < countElements(coordinates); ++i {
let a = coordinates[i]
let b = coordinates[i - 1]
output += encodeCoordinate(a.latitude - b.latitude)
output += encodeCoordinate(a.longitude - b.longitude)
}

return output.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!
}

init(pathCoordinates: [CLLocationCoordinate2D],
strokeWidth: Int = 1,
strokeColor: UIColor = UIColor.colorWithHexString("555"),
strokeOpacity: CGFloat = 1.0,
fillColor: UIColor = UIColor.colorWithHexString("555"),
fillOpacity: CGFloat = 0) {

super.init()

requestString = "path"
requestString += "-\(strokeWidth)"
requestString += "+" + strokeColor.toHexString()
requestString += "-\(strokeOpacity)"
requestString += "+" + fillColor.toHexString()
requestString += "-\(fillOpacity)"
requestString += "("
requestString += polylineEncode(pathCoordinates)
requestString += ")"

}

}

}
126 changes: 126 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# MapboxStatic

MapboxStatic is a Swift library for Mapbox's [static maps API](https://www.mapbox.com/developers/api/static/), with support for overlays, asynchronous imagery fetching, and first-class Swift data types.

Static maps are flattened PNG or JPG images, ideal for use in table views, image views, and anyplace else you'd like a quick, custom map without the overhead of an interactive view.

## Installation

Drag the `MapboxStatic.swift` file into your project.

## Usage

You will need a [map ID](https://www.mapbox.com/foundations/glossary/#mapid) from a [custom map style](https://www.mapbox.com/foundations/customizing-the-map) on your Mapbox account. You will also need an [access token](https://www.mapbox.com/developers/api/#access-tokens) in order to use the API.

### Basics

The main map class is `MapboxStaticMap`. To create a basic map, specify the center, zoom level, pixel size, and retina scale:

```swift
let map = MapboxStaticMap(
mapID: <your map ID>,
center: CLLocationCoordinate2D(latitude: 45.52, longitude: -122.681944),
zoom: 13,
size: CGSize(width: 500, height: 300),
accessToken: <your API token>,
retina: (UIScreen.mainScreen().scale > 1.0)
)
```

Then, to retrieve an image, you can do it either synchronously (blocking the calling thread):

```swift
self.imageView.image = map.image
```

![](./screenshot_map.png)

Or you can pass a completion handler to update the UI thread when after the image is retrieved:

```swift
map.imageWithCompletionHandler({ (image: UIImage?) in
self.imageView.image = image
})
```

If you're using your own HTTP library or routines, you can also retrieve a map object's `requestURL` property.

```swift
let requestURLToFetch = map.requestURL
```

### Overlays

Overlays are where things get interesting! You can add [Maki markers](https://www.mapbox.com/maki/), GeoJSON geometries, and even paths made of bare coordinates.

You pass overlays as the `overlays: [Overlays]` parameter during map creation. Here are some versions of our map with various overlays added.

#### Marker

```swift
let markerOverlay = MapboxStaticMap.Marker(
coordinate: CLLocationCoordinate2D(latitude: 45.52, longitude: -122.681944),
size: .Medium,
label: "cafe",
color: UIColor.brownColor()
)
```

![](./screenshot_marker.png)

#### GeoJSON

```swift
let GeoJSONOverlay = MapboxStaticMap.GeoJSON(
GeoJSON: NSString(
contentsOfFile: NSBundle.mainBundle().pathForResource("sample", ofType: "geojson")!,
encoding: NSUTF8StringEncoding,
error: nil
)!
)
```

![](./screenshot_geojson.png)

#### Path

```swift
let path = MapboxStaticMap.Path(
pathCoordinates: [
CLLocationCoordinate2D(
latitude: 45.52475063103141, longitude: -122.68209457397461
),
CLLocationCoordinate2D(
latitude: 45.52451009822193, longitude: -122.67488479614258
),
CLLocationCoordinate2D(
latitude: 45.51681250530043, longitude: -122.67608642578126
),
CLLocationCoordinate2D(
latitude: 45.51693278828882, longitude: -122.68999099731445
),
CLLocationCoordinate2D(
latitude: 45.520300607576864, longitude: -122.68964767456055
),
CLLocationCoordinate2D(
latitude: 45.52475063103141, longitude: -122.68209457397461
)
],
strokeWidth: 2,
strokeColor: UIColor.blackColor(),
fillColor: UIColor.redColor(),
fillOpacity: 0.25
)
```

![](./screenshot_path.png)

### Other options

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/developers/api/static/#format).

Be sure to [attribute your map](https://www.mapbox.com/developers/api/static/#attribution) properly!

### More info

For more info about the Mapbox static maps API, check out the [web service documentation](https://www.mapbox.com/developers/api/static/).
Binary file added screenshot_geojson.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshot_map.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshot_marker.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshot_path.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit c31ba1f

Please sign in to comment.