diff --git a/Example/ViewController.m b/Example/ViewController.m index f254afe..98afef8 100644 --- a/Example/ViewController.m +++ b/Example/ViewController.m @@ -4,7 +4,7 @@ @import MapboxStatic; // You can also specify the access token with the `MGLMapboxAccessToken` key in Info.plist. -static NSString * const AccessToken = @"pk.eyJ1IjoianVzdGluIiwiYSI6IlpDbUJLSUEifQ.4mG8vhelFMju6HpIY-Hi5A"; +static NSString * const AccessToken = @"pk.eyJ1IjoibWFwYm94IiwiYSI6ImNqMHFiNXN4ZDAxazMyd253cmt3a2hmN2cifQ.q0ntnAWEdwckfZnT0IEy5A"; @interface ViewController () @@ -25,10 +25,12 @@ - (void)viewDidLoad { - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; - MBSnapshotOptions *options = [[MBSnapshotOptions alloc] initWithMapIdentifiers:@[@"justin.tm2-basemap"] - centerCoordinate:CLLocationCoordinate2DMake(45, -122) - zoomLevel:6 - size:self.imageView.bounds.size]; + NSURL *styleURL = [NSURL URLWithString:@"mapbox://styles/mapbox/streets-v9"]; + MBSnapshotCamera *camera = [MBSnapshotCamera cameraLookingAtCenterCoordinate:CLLocationCoordinate2DMake(45, -122) + zoomLevel:6]; + MBSnapshotOptions *options = [[MBSnapshotOptions alloc] initWithStyleURL:styleURL + camera:camera + size:self.imageView.bounds.size]; CLLocationCoordinate2D coords[] = { CLLocationCoordinate2DMake(45, -122), CLLocationCoordinate2DMake(45, -124), diff --git a/Example/ViewController.swift b/Example/ViewController.swift index eb07f8f..9660cf4 100644 --- a/Example/ViewController.swift +++ b/Example/ViewController.swift @@ -4,7 +4,7 @@ import MapboxStatic class ViewController: UIViewController { // You can also specify the access token with the `MGLMapboxAccessToken` key in Info.plist. - let accessToken = "pk.eyJ1IjoianVzdGluIiwiYSI6IlpDbUJLSUEifQ.4mG8vhelFMju6HpIY-Hi5A" + let accessToken = "pk.eyJ1IjoibWFwYm94IiwiYSI6ImNqMHFiNXN4ZDAxazMyd253cmt3a2hmN2cifQ.q0ntnAWEdwckfZnT0IEy5A" var imageView: UIImageView! override func viewDidLoad() { @@ -18,10 +18,10 @@ class ViewController: UIViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) + let camera = SnapshotCamera(lookingAtCenter: CLLocationCoordinate2D(latitude: 45, longitude: -122), zoomLevel: 6) let options = SnapshotOptions( - mapIdentifiers: ["justin.tm2-basemap"], - centerCoordinate: CLLocationCoordinate2D(latitude: 45, longitude: -122), - zoomLevel: 6, + styleURL: URL(string: "mapbox://styles/mapbox/streets-v9")!, + camera: camera, size: imageView.bounds.size) _ = Snapshot(options: options, accessToken: accessToken).image { [weak self] (image, error) in if let error = error { diff --git a/MapboxStatic.xcodeproj/project.pbxproj b/MapboxStatic.xcodeproj/project.pbxproj index 69912e4..a0a85b2 100644 --- a/MapboxStatic.xcodeproj/project.pbxproj +++ b/MapboxStatic.xcodeproj/project.pbxproj @@ -7,6 +7,90 @@ objects = { /* Begin PBXBuildFile section */ + DA031C621E6F4824007CA06A /* rotate.png in Resources */ = {isa = PBXBuildFile; fileRef = DA031C611E6F4824007CA06A /* rotate.png */; }; + DA031C631E6F4824007CA06A /* rotate.png in Resources */ = {isa = PBXBuildFile; fileRef = DA031C611E6F4824007CA06A /* rotate.png */; }; + DA031C641E6F4824007CA06A /* rotate.png in Resources */ = {isa = PBXBuildFile; fileRef = DA031C611E6F4824007CA06A /* rotate.png */; }; + DA031C661E6F487E007CA06A /* tilt.png in Resources */ = {isa = PBXBuildFile; fileRef = DA031C651E6F487E007CA06A /* tilt.png */; }; + DA031C671E6F487E007CA06A /* tilt.png in Resources */ = {isa = PBXBuildFile; fileRef = DA031C651E6F487E007CA06A /* tilt.png */; }; + DA031C681E6F487E007CA06A /* tilt.png in Resources */ = {isa = PBXBuildFile; fileRef = DA031C651E6F487E007CA06A /* tilt.png */; }; + DA031C6A1E6F4A7C007CA06A /* no-logo.png in Resources */ = {isa = PBXBuildFile; fileRef = DA031C691E6F4A7C007CA06A /* no-logo.png */; }; + DA031C6B1E6F4A7C007CA06A /* no-logo.png in Resources */ = {isa = PBXBuildFile; fileRef = DA031C691E6F4A7C007CA06A /* no-logo.png */; }; + DA031C6C1E6F4A7C007CA06A /* no-logo.png in Resources */ = {isa = PBXBuildFile; fileRef = DA031C691E6F4A7C007CA06A /* no-logo.png */; }; + DA031C6E1E6F76FA007CA06A /* no-attribution.png in Resources */ = {isa = PBXBuildFile; fileRef = DA031C6D1E6F76FA007CA06A /* no-attribution.png */; }; + DA031C6F1E6F76FA007CA06A /* no-attribution.png in Resources */ = {isa = PBXBuildFile; fileRef = DA031C6D1E6F76FA007CA06A /* no-attribution.png */; }; + DA031C701E6F76FA007CA06A /* no-attribution.png in Resources */ = {isa = PBXBuildFile; fileRef = DA031C6D1E6F76FA007CA06A /* no-attribution.png */; }; + DA1BA20D1E6B4A90007F9FAC /* ClassicSnapshotOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA1BA20C1E6B4A90007F9FAC /* ClassicSnapshotOptions.swift */; }; + DA1BA20E1E6B4A90007F9FAC /* ClassicSnapshotOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA1BA20C1E6B4A90007F9FAC /* ClassicSnapshotOptions.swift */; }; + DA1BA20F1E6B4A90007F9FAC /* ClassicSnapshotOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA1BA20C1E6B4A90007F9FAC /* ClassicSnapshotOptions.swift */; }; + DA1BA2101E6B4A90007F9FAC /* ClassicSnapshotOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA1BA20C1E6B4A90007F9FAC /* ClassicSnapshotOptions.swift */; }; + DA1BA2171E6BA8CB007F9FAC /* SnapshotOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA1BA2161E6BA8CB007F9FAC /* SnapshotOptions.swift */; }; + DA1BA2181E6BA8CB007F9FAC /* SnapshotOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA1BA2161E6BA8CB007F9FAC /* SnapshotOptions.swift */; }; + DA1BA2191E6BA8CB007F9FAC /* SnapshotOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA1BA2161E6BA8CB007F9FAC /* SnapshotOptions.swift */; }; + DA1BA21A1E6BA8CB007F9FAC /* SnapshotOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA1BA2161E6BA8CB007F9FAC /* SnapshotOptions.swift */; }; + DA1BA21C1E6BA918007F9FAC /* MarkerOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA1BA21B1E6BA918007F9FAC /* MarkerOptions.swift */; }; + DA1BA21D1E6BA918007F9FAC /* MarkerOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA1BA21B1E6BA918007F9FAC /* MarkerOptions.swift */; }; + DA1BA21E1E6BA918007F9FAC /* MarkerOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA1BA21B1E6BA918007F9FAC /* MarkerOptions.swift */; }; + DA1BA21F1E6BA918007F9FAC /* MarkerOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA1BA21B1E6BA918007F9FAC /* MarkerOptions.swift */; }; + DA1BA2211E6CD572007F9FAC /* ClassicSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA1BA2201E6CD572007F9FAC /* ClassicSnapshotTests.swift */; }; + DA1BA2221E6CD572007F9FAC /* ClassicSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA1BA2201E6CD572007F9FAC /* ClassicSnapshotTests.swift */; }; + DA1BA2231E6CD572007F9FAC /* ClassicSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA1BA2201E6CD572007F9FAC /* ClassicSnapshotTests.swift */; }; + DA1BA22A1E6CF00C007F9FAC /* basic.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2281E6CF00C007F9FAC /* basic.png */; }; + DA1BA22C1E6CF00C007F9FAC /* basic.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2281E6CF00C007F9FAC /* basic.png */; }; + DA1BA22D1E6CF00C007F9FAC /* basic@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2291E6CF00C007F9FAC /* basic@2x.png */; }; + DA1BA22E1E6CF00C007F9FAC /* basic@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2291E6CF00C007F9FAC /* basic@2x.png */; }; + DA1BA22F1E6CF00C007F9FAC /* basic@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2291E6CF00C007F9FAC /* basic@2x.png */; }; + DA1BA2301E6CF44C007F9FAC /* basic.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2281E6CF00C007F9FAC /* basic.png */; }; + DA1BA2331E6D174E007F9FAC /* center.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2311E6D174E007F9FAC /* center.png */; }; + DA1BA2341E6D174E007F9FAC /* center.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2311E6D174E007F9FAC /* center.png */; }; + DA1BA2351E6D174E007F9FAC /* center.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2311E6D174E007F9FAC /* center.png */; }; + DA1BA23A1E6D17B5007F9FAC /* zoom.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2391E6D17B5007F9FAC /* zoom.png */; }; + DA1BA23B1E6D17B5007F9FAC /* zoom.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2391E6D17B5007F9FAC /* zoom.png */; }; + DA1BA23C1E6D17B5007F9FAC /* zoom.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2391E6D17B5007F9FAC /* zoom.png */; }; + DA1BA2521E6D1B5B007F9FAC /* format.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA24D1E6D1B5B007F9FAC /* format.png */; }; + DA1BA2531E6D1B5C007F9FAC /* format.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA24D1E6D1B5B007F9FAC /* format.png */; }; + DA1BA2541E6D1B5C007F9FAC /* format.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA24D1E6D1B5B007F9FAC /* format.png */; }; + DA1BA2551E6D1B5C007F9FAC /* format.png32 in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA24E1E6D1B5B007F9FAC /* format.png32 */; }; + DA1BA2561E6D1B5C007F9FAC /* format.png32 in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA24E1E6D1B5B007F9FAC /* format.png32 */; }; + DA1BA2571E6D1B5C007F9FAC /* format.png32 in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA24E1E6D1B5B007F9FAC /* format.png32 */; }; + DA1BA2581E6D1B5C007F9FAC /* format.png64 in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA24F1E6D1B5B007F9FAC /* format.png64 */; }; + DA1BA2591E6D1B5C007F9FAC /* format.png64 in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA24F1E6D1B5B007F9FAC /* format.png64 */; }; + DA1BA25A1E6D1B5C007F9FAC /* format.png64 in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA24F1E6D1B5B007F9FAC /* format.png64 */; }; + DA1BA25B1E6D1B5C007F9FAC /* format.png128 in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2501E6D1B5B007F9FAC /* format.png128 */; }; + DA1BA25C1E6D1B5C007F9FAC /* format.png128 in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2501E6D1B5B007F9FAC /* format.png128 */; }; + DA1BA25D1E6D1B5C007F9FAC /* format.png128 in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2501E6D1B5B007F9FAC /* format.png128 */; }; + DA1BA25E1E6D1B5C007F9FAC /* format.png256 in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2511E6D1B5B007F9FAC /* format.png256 */; }; + DA1BA25F1E6D1B5C007F9FAC /* format.png256 in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2511E6D1B5B007F9FAC /* format.png256 */; }; + DA1BA2601E6D1B5C007F9FAC /* format.png256 in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2511E6D1B5B007F9FAC /* format.png256 */; }; + DA1BA2651E6D1B92007F9FAC /* format.jpg in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2611E6D1B92007F9FAC /* format.jpg */; }; + DA1BA2661E6D1B92007F9FAC /* format.jpg in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2611E6D1B92007F9FAC /* format.jpg */; }; + DA1BA2671E6D1B92007F9FAC /* format.jpg in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2611E6D1B92007F9FAC /* format.jpg */; }; + DA1BA2681E6D1B92007F9FAC /* format.jpg70 in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2621E6D1B92007F9FAC /* format.jpg70 */; }; + DA1BA2691E6D1B92007F9FAC /* format.jpg70 in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2621E6D1B92007F9FAC /* format.jpg70 */; }; + DA1BA26A1E6D1B92007F9FAC /* format.jpg70 in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2621E6D1B92007F9FAC /* format.jpg70 */; }; + DA1BA26B1E6D1B92007F9FAC /* format.jpg80 in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2631E6D1B92007F9FAC /* format.jpg80 */; }; + DA1BA26C1E6D1B92007F9FAC /* format.jpg80 in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2631E6D1B92007F9FAC /* format.jpg80 */; }; + DA1BA26D1E6D1B92007F9FAC /* format.jpg80 in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2631E6D1B92007F9FAC /* format.jpg80 */; }; + DA1BA26E1E6D1B92007F9FAC /* format.jpg90 in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2641E6D1B92007F9FAC /* format.jpg90 */; }; + DA1BA26F1E6D1B92007F9FAC /* format.jpg90 in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2641E6D1B92007F9FAC /* format.jpg90 */; }; + DA1BA2701E6D1B92007F9FAC /* format.jpg90 in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2641E6D1B92007F9FAC /* format.jpg90 */; }; + DA1BA2721E6D1E8E007F9FAC /* ClassicOverlayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA1BA2711E6D1E8E007F9FAC /* ClassicOverlayTests.swift */; }; + DA1BA2731E6D1E8E007F9FAC /* ClassicOverlayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA1BA2711E6D1E8E007F9FAC /* ClassicOverlayTests.swift */; }; + DA1BA2741E6D1E8E007F9FAC /* ClassicOverlayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA1BA2711E6D1E8E007F9FAC /* ClassicOverlayTests.swift */; }; + DA1BA2761E6D2014007F9FAC /* cafe.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2751E6D2014007F9FAC /* cafe.png */; }; + DA1BA2771E6D2014007F9FAC /* cafe.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2751E6D2014007F9FAC /* cafe.png */; }; + DA1BA2781E6D2014007F9FAC /* cafe.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2751E6D2014007F9FAC /* cafe.png */; }; + DA1BA27A1E6D23DD007F9FAC /* marker.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2791E6D23DD007F9FAC /* marker.png */; }; + DA1BA27B1E6D23DD007F9FAC /* marker.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2791E6D23DD007F9FAC /* marker.png */; }; + DA1BA27C1E6D23DD007F9FAC /* marker.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2791E6D23DD007F9FAC /* marker.png */; }; + DA1BA27E1E6D2DE3007F9FAC /* rocket.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA27D1E6D2DE3007F9FAC /* rocket.png */; }; + DA1BA27F1E6D2DE3007F9FAC /* rocket.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA27D1E6D2DE3007F9FAC /* rocket.png */; }; + DA1BA2801E6D2DE3007F9FAC /* rocket.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA27D1E6D2DE3007F9FAC /* rocket.png */; }; + DA1BA2821E6D3386007F9FAC /* geojson.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2811E6D3386007F9FAC /* geojson.png */; }; + DA1BA2831E6D3386007F9FAC /* geojson.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2811E6D3386007F9FAC /* geojson.png */; }; + DA1BA2841E6D3386007F9FAC /* geojson.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2811E6D3386007F9FAC /* geojson.png */; }; + DA1BA2861E6D39AE007F9FAC /* path.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2851E6D39AE007F9FAC /* path.png */; }; + DA1BA2871E6D39AE007F9FAC /* path.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2851E6D39AE007F9FAC /* path.png */; }; + DA1BA2881E6D39AE007F9FAC /* path.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1BA2851E6D39AE007F9FAC /* path.png */; }; DA20FB9E1CE3DEBB00B07762 /* MapboxStatic.h in Headers */ = {isa = PBXBuildFile; fileRef = DA20FB9D1CE3DEBB00B07762 /* MapboxStatic.h */; settings = {ATTRIBUTES = (Public, ); }; }; DA20FBA21CE3DEBB00B07762 /* MapboxStatic.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA20FB9B1CE3DEBB00B07762 /* MapboxStatic.framework */; }; DA20FBA81CE3DF6900B07762 /* Snapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDAD19871BD9B3970057AC9F /* Snapshot.swift */; }; @@ -27,6 +111,21 @@ DA9CC68B1E5C550000A14964 /* OHHTTPStubs.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA9CC68A1E5C550000A14964 /* OHHTTPStubs.framework */; }; DA9CC68D1E5C551000A14964 /* OHHTTPStubs.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA9CC68C1E5C551000A14964 /* OHHTTPStubs.framework */; }; DA9CC68F1E5C551500A14964 /* OHHTTPStubs.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA9CC68E1E5C551500A14964 /* OHHTTPStubs.framework */; }; + DAE8CCB01E6E8E6B009B5CB0 /* SnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE8CCAF1E6E8E6B009B5CB0 /* SnapshotTests.swift */; }; + DAE8CCB11E6E8E6B009B5CB0 /* SnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE8CCAF1E6E8E6B009B5CB0 /* SnapshotTests.swift */; }; + DAE8CCB21E6E8E6B009B5CB0 /* SnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE8CCAF1E6E8E6B009B5CB0 /* SnapshotTests.swift */; }; + DAE8CCB41E6E9770009B5CB0 /* basic-gl.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE8CCB31E6E9770009B5CB0 /* basic-gl.png */; }; + DAE8CCB51E6E9770009B5CB0 /* basic-gl.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE8CCB31E6E9770009B5CB0 /* basic-gl.png */; }; + DAE8CCB61E6E9770009B5CB0 /* basic-gl.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE8CCB31E6E9770009B5CB0 /* basic-gl.png */; }; + DAE8CCB81E6E97A5009B5CB0 /* basic-gl@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE8CCB71E6E97A5009B5CB0 /* basic-gl@2x.png */; }; + DAE8CCB91E6E97A5009B5CB0 /* basic-gl@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE8CCB71E6E97A5009B5CB0 /* basic-gl@2x.png */; }; + DAE8CCBA1E6E97A5009B5CB0 /* basic-gl@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE8CCB71E6E97A5009B5CB0 /* basic-gl@2x.png */; }; + DAE8CCBD1E6E9842009B5CB0 /* center-gl.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE8CCBB1E6E9842009B5CB0 /* center-gl.png */; }; + DAE8CCBE1E6E9842009B5CB0 /* center-gl.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE8CCBB1E6E9842009B5CB0 /* center-gl.png */; }; + DAE8CCBF1E6E9842009B5CB0 /* center-gl.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE8CCBB1E6E9842009B5CB0 /* center-gl.png */; }; + DAE8CCC41E6E992E009B5CB0 /* zoom-gl.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE8CCC31E6E992E009B5CB0 /* zoom-gl.png */; }; + DAE8CCC51E6E992E009B5CB0 /* zoom-gl.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE8CCC31E6E992E009B5CB0 /* zoom-gl.png */; }; + DAE8CCC61E6E992E009B5CB0 /* zoom-gl.png in Resources */ = {isa = PBXBuildFile; fileRef = DAE8CCC31E6E992E009B5CB0 /* zoom-gl.png */; }; DAF158881D03E7AC00829B35 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DAF158871D03E7AC00829B35 /* Launch Screen.storyboard */; }; DAF158891D03E7AC00829B35 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DAF158871D03E7AC00829B35 /* Launch Screen.storyboard */; }; DAF15A431CE8FBBC0040E86C /* MapboxStatic.h in Headers */ = {isa = PBXBuildFile; fileRef = DA20FB9D1CE3DEBB00B07762 /* MapboxStatic.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -82,6 +181,33 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + DA031C611E6F4824007CA06A /* rotate.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = rotate.png; sourceTree = ""; }; + DA031C651E6F487E007CA06A /* tilt.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = tilt.png; sourceTree = ""; }; + DA031C691E6F4A7C007CA06A /* no-logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "no-logo.png"; sourceTree = ""; }; + DA031C6D1E6F76FA007CA06A /* no-attribution.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "no-attribution.png"; sourceTree = ""; }; + DA1BA20C1E6B4A90007F9FAC /* ClassicSnapshotOptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClassicSnapshotOptions.swift; sourceTree = ""; }; + DA1BA2161E6BA8CB007F9FAC /* SnapshotOptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnapshotOptions.swift; sourceTree = ""; }; + DA1BA21B1E6BA918007F9FAC /* MarkerOptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkerOptions.swift; sourceTree = ""; }; + DA1BA2201E6CD572007F9FAC /* ClassicSnapshotTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClassicSnapshotTests.swift; sourceTree = ""; }; + DA1BA2281E6CF00C007F9FAC /* basic.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = basic.png; sourceTree = ""; }; + DA1BA2291E6CF00C007F9FAC /* basic@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "basic@2x.png"; sourceTree = ""; }; + DA1BA2311E6D174E007F9FAC /* center.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = center.png; sourceTree = ""; }; + DA1BA2391E6D17B5007F9FAC /* zoom.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = zoom.png; sourceTree = ""; }; + DA1BA24D1E6D1B5B007F9FAC /* format.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = format.png; sourceTree = ""; }; + DA1BA24E1E6D1B5B007F9FAC /* format.png32 */ = {isa = PBXFileReference; lastKnownFileType = file; path = format.png32; sourceTree = ""; }; + DA1BA24F1E6D1B5B007F9FAC /* format.png64 */ = {isa = PBXFileReference; lastKnownFileType = file; path = format.png64; sourceTree = ""; }; + DA1BA2501E6D1B5B007F9FAC /* format.png128 */ = {isa = PBXFileReference; lastKnownFileType = file; path = format.png128; sourceTree = ""; }; + DA1BA2511E6D1B5B007F9FAC /* format.png256 */ = {isa = PBXFileReference; lastKnownFileType = file; path = format.png256; sourceTree = ""; }; + DA1BA2611E6D1B92007F9FAC /* format.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = format.jpg; sourceTree = ""; }; + DA1BA2621E6D1B92007F9FAC /* format.jpg70 */ = {isa = PBXFileReference; lastKnownFileType = file; path = format.jpg70; sourceTree = ""; }; + DA1BA2631E6D1B92007F9FAC /* format.jpg80 */ = {isa = PBXFileReference; lastKnownFileType = file; path = format.jpg80; sourceTree = ""; }; + DA1BA2641E6D1B92007F9FAC /* format.jpg90 */ = {isa = PBXFileReference; lastKnownFileType = file; path = format.jpg90; sourceTree = ""; }; + DA1BA2711E6D1E8E007F9FAC /* ClassicOverlayTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClassicOverlayTests.swift; sourceTree = ""; }; + DA1BA2751E6D2014007F9FAC /* cafe.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = cafe.png; sourceTree = ""; }; + DA1BA2791E6D23DD007F9FAC /* marker.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = marker.png; sourceTree = ""; }; + DA1BA27D1E6D2DE3007F9FAC /* rocket.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = rocket.png; sourceTree = ""; }; + DA1BA2811E6D3386007F9FAC /* geojson.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = geojson.png; sourceTree = ""; }; + DA1BA2851E6D39AE007F9FAC /* path.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = path.png; sourceTree = ""; }; DA20FB9B1CE3DEBB00B07762 /* MapboxStatic.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MapboxStatic.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DA20FB9D1CE3DEBB00B07762 /* MapboxStatic.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MapboxStatic.h; sourceTree = ""; }; DA20FB9F1CE3DEBB00B07762 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -96,6 +222,11 @@ DA9CC68A1E5C550000A14964 /* OHHTTPStubs.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OHHTTPStubs.framework; path = Carthage/Build/iOS/OHHTTPStubs.framework; sourceTree = ""; }; DA9CC68C1E5C551000A14964 /* OHHTTPStubs.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OHHTTPStubs.framework; path = Carthage/Build/Mac/OHHTTPStubs.framework; sourceTree = ""; }; DA9CC68E1E5C551500A14964 /* OHHTTPStubs.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OHHTTPStubs.framework; path = Carthage/Build/tvOS/OHHTTPStubs.framework; sourceTree = ""; }; + DAE8CCAF1E6E8E6B009B5CB0 /* SnapshotTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnapshotTests.swift; sourceTree = ""; }; + DAE8CCB31E6E9770009B5CB0 /* basic-gl.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "basic-gl.png"; sourceTree = ""; }; + DAE8CCB71E6E97A5009B5CB0 /* basic-gl@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "basic-gl@2x.png"; sourceTree = ""; }; + DAE8CCBB1E6E9842009B5CB0 /* center-gl.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "center-gl.png"; sourceTree = ""; }; + DAE8CCC31E6E992E009B5CB0 /* zoom-gl.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "zoom-gl.png"; sourceTree = ""; }; DAF158871D03E7AC00829B35 /* Launch Screen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = ""; }; DAF15A3B1CE8FB8D0040E86C /* MapboxStatic.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MapboxStatic.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DAF15A4B1CE90A6C0040E86C /* MapboxStatic (Objective-C).app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "MapboxStatic (Objective-C).app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -192,6 +323,9 @@ children = ( DA20FB9D1CE3DEBB00B07762 /* MapboxStatic.h */, DDAD19871BD9B3970057AC9F /* Snapshot.swift */, + DA1BA2161E6BA8CB007F9FAC /* SnapshotOptions.swift */, + DA1BA20C1E6B4A90007F9FAC /* ClassicSnapshotOptions.swift */, + DA1BA21B1E6BA918007F9FAC /* MarkerOptions.swift */, DA20FBAB1CE4026B00B07762 /* Overlay.swift */, DA20FBA91CE401E800B07762 /* Color.swift */, DA20FB9F1CE3DEBB00B07762 /* Info.plist */, @@ -233,7 +367,33 @@ DD0C88151BE1A9CC00606E9F /* Fixtures */ = { isa = PBXGroup; children = ( + DA1BA2281E6CF00C007F9FAC /* basic.png */, + DA1BA2291E6CF00C007F9FAC /* basic@2x.png */, + DAE8CCB31E6E9770009B5CB0 /* basic-gl.png */, + DAE8CCB71E6E97A5009B5CB0 /* basic-gl@2x.png */, + DA1BA2751E6D2014007F9FAC /* cafe.png */, + DA1BA2311E6D174E007F9FAC /* center.png */, + DAE8CCBB1E6E9842009B5CB0 /* center-gl.png */, + DA1BA2611E6D1B92007F9FAC /* format.jpg */, + DA1BA2621E6D1B92007F9FAC /* format.jpg70 */, + DA1BA2631E6D1B92007F9FAC /* format.jpg80 */, + DA1BA2641E6D1B92007F9FAC /* format.jpg90 */, + DA1BA24D1E6D1B5B007F9FAC /* format.png */, + DA1BA24E1E6D1B5B007F9FAC /* format.png32 */, + DA1BA24F1E6D1B5B007F9FAC /* format.png64 */, + DA1BA2501E6D1B5B007F9FAC /* format.png128 */, + DA1BA2511E6D1B5B007F9FAC /* format.png256 */, + DA1BA2811E6D3386007F9FAC /* geojson.png */, + DA1BA2791E6D23DD007F9FAC /* marker.png */, + DA031C6D1E6F76FA007CA06A /* no-attribution.png */, + DA031C691E6F4A7C007CA06A /* no-logo.png */, + DA1BA2851E6D39AE007F9FAC /* path.png */, DD0C88161BE1A9D400606E9F /* polyline.geojson */, + DA1BA27D1E6D2DE3007F9FAC /* rocket.png */, + DA031C611E6F4824007CA06A /* rotate.png */, + DA031C651E6F487E007CA06A /* tilt.png */, + DA1BA2391E6D17B5007F9FAC /* zoom.png */, + DAE8CCC31E6E992E009B5CB0 /* zoom-gl.png */, ); name = Fixtures; path = fixtures; @@ -283,6 +443,9 @@ isa = PBXGroup; children = ( DDAD19831BD9B2F80057AC9F /* MapboxStaticTests.swift */, + DAE8CCAF1E6E8E6B009B5CB0 /* SnapshotTests.swift */, + DA1BA2201E6CD572007F9FAC /* ClassicSnapshotTests.swift */, + DA1BA2711E6D1E8E007F9FAC /* ClassicOverlayTests.swift */, DDAD19841BD9B2F80057AC9F /* Info.plist */, DD0C88151BE1A9CC00606E9F /* Fixtures */, ); @@ -584,7 +747,33 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + DAE8CCC51E6E992E009B5CB0 /* zoom-gl.png in Resources */, + DA1BA2301E6CF44C007F9FAC /* basic.png in Resources */, + DA1BA23B1E6D17B5007F9FAC /* zoom.png in Resources */, + DA1BA2831E6D3386007F9FAC /* geojson.png in Resources */, + DA1BA25F1E6D1B5C007F9FAC /* format.png256 in Resources */, + DA1BA25C1E6D1B5C007F9FAC /* format.png128 in Resources */, + DA1BA2591E6D1B5C007F9FAC /* format.png64 in Resources */, DA20FBD51CE4760100B07762 /* polyline.geojson in Resources */, + DA1BA2661E6D1B92007F9FAC /* format.jpg in Resources */, + DA1BA26C1E6D1B92007F9FAC /* format.jpg80 in Resources */, + DA1BA2691E6D1B92007F9FAC /* format.jpg70 in Resources */, + DA1BA2341E6D174E007F9FAC /* center.png in Resources */, + DAE8CCBE1E6E9842009B5CB0 /* center-gl.png in Resources */, + DA031C6F1E6F76FA007CA06A /* no-attribution.png in Resources */, + DA1BA2531E6D1B5C007F9FAC /* format.png in Resources */, + DA1BA2871E6D39AE007F9FAC /* path.png in Resources */, + DA1BA2771E6D2014007F9FAC /* cafe.png in Resources */, + DA031C671E6F487E007CA06A /* tilt.png in Resources */, + DA1BA26F1E6D1B92007F9FAC /* format.jpg90 in Resources */, + DA1BA2561E6D1B5C007F9FAC /* format.png32 in Resources */, + DA031C6B1E6F4A7C007CA06A /* no-logo.png in Resources */, + DA1BA22E1E6CF00C007F9FAC /* basic@2x.png in Resources */, + DAE8CCB51E6E9770009B5CB0 /* basic-gl.png in Resources */, + DA031C631E6F4824007CA06A /* rotate.png in Resources */, + DA1BA27B1E6D23DD007F9FAC /* marker.png in Resources */, + DAE8CCB91E6E97A5009B5CB0 /* basic-gl@2x.png in Resources */, + DA1BA27F1E6D2DE3007F9FAC /* rocket.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -599,7 +788,33 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + DAE8CCC61E6E992E009B5CB0 /* zoom-gl.png in Resources */, + DA1BA22C1E6CF00C007F9FAC /* basic.png in Resources */, + DA1BA23C1E6D17B5007F9FAC /* zoom.png in Resources */, + DA1BA2841E6D3386007F9FAC /* geojson.png in Resources */, + DA1BA2601E6D1B5C007F9FAC /* format.png256 in Resources */, + DA1BA25D1E6D1B5C007F9FAC /* format.png128 in Resources */, + DA1BA25A1E6D1B5C007F9FAC /* format.png64 in Resources */, DA4B4BAE1CE8F87C00296A52 /* polyline.geojson in Resources */, + DA1BA2671E6D1B92007F9FAC /* format.jpg in Resources */, + DA1BA26D1E6D1B92007F9FAC /* format.jpg80 in Resources */, + DA1BA26A1E6D1B92007F9FAC /* format.jpg70 in Resources */, + DA1BA2351E6D174E007F9FAC /* center.png in Resources */, + DAE8CCBF1E6E9842009B5CB0 /* center-gl.png in Resources */, + DA031C701E6F76FA007CA06A /* no-attribution.png in Resources */, + DA1BA2541E6D1B5C007F9FAC /* format.png in Resources */, + DA1BA2881E6D39AE007F9FAC /* path.png in Resources */, + DA1BA2781E6D2014007F9FAC /* cafe.png in Resources */, + DA031C681E6F487E007CA06A /* tilt.png in Resources */, + DA1BA2701E6D1B92007F9FAC /* format.jpg90 in Resources */, + DA1BA2571E6D1B5C007F9FAC /* format.png32 in Resources */, + DA031C6C1E6F4A7C007CA06A /* no-logo.png in Resources */, + DA1BA22F1E6CF00C007F9FAC /* basic@2x.png in Resources */, + DAE8CCB61E6E9770009B5CB0 /* basic-gl.png in Resources */, + DA031C641E6F4824007CA06A /* rotate.png in Resources */, + DA1BA27C1E6D23DD007F9FAC /* marker.png in Resources */, + DAE8CCBA1E6E97A5009B5CB0 /* basic-gl@2x.png in Resources */, + DA1BA2801E6D2DE3007F9FAC /* rocket.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -630,7 +845,33 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + DAE8CCC41E6E992E009B5CB0 /* zoom-gl.png in Resources */, + DA1BA22A1E6CF00C007F9FAC /* basic.png in Resources */, + DA1BA23A1E6D17B5007F9FAC /* zoom.png in Resources */, + DA1BA2821E6D3386007F9FAC /* geojson.png in Resources */, + DA1BA25E1E6D1B5C007F9FAC /* format.png256 in Resources */, + DA1BA25B1E6D1B5C007F9FAC /* format.png128 in Resources */, + DA1BA2581E6D1B5C007F9FAC /* format.png64 in Resources */, DD0C88181BE1A9E100606E9F /* polyline.geojson in Resources */, + DA1BA2651E6D1B92007F9FAC /* format.jpg in Resources */, + DA1BA26B1E6D1B92007F9FAC /* format.jpg80 in Resources */, + DA1BA2681E6D1B92007F9FAC /* format.jpg70 in Resources */, + DA1BA2331E6D174E007F9FAC /* center.png in Resources */, + DAE8CCBD1E6E9842009B5CB0 /* center-gl.png in Resources */, + DA031C6E1E6F76FA007CA06A /* no-attribution.png in Resources */, + DA1BA2521E6D1B5B007F9FAC /* format.png in Resources */, + DA1BA2861E6D39AE007F9FAC /* path.png in Resources */, + DA1BA2761E6D2014007F9FAC /* cafe.png in Resources */, + DA031C661E6F487E007CA06A /* tilt.png in Resources */, + DA1BA26E1E6D1B92007F9FAC /* format.jpg90 in Resources */, + DA1BA2551E6D1B5C007F9FAC /* format.png32 in Resources */, + DA031C6A1E6F4A7C007CA06A /* no-logo.png in Resources */, + DA1BA22D1E6CF00C007F9FAC /* basic@2x.png in Resources */, + DAE8CCB41E6E9770009B5CB0 /* basic-gl.png in Resources */, + DA031C621E6F4824007CA06A /* rotate.png in Resources */, + DA1BA27A1E6D23DD007F9FAC /* marker.png in Resources */, + DAE8CCB81E6E97A5009B5CB0 /* basic-gl@2x.png in Resources */, + DA1BA27E1E6D2DE3007F9FAC /* rocket.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -689,8 +930,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DA1BA21C1E6BA918007F9FAC /* MarkerOptions.swift in Sources */, + DA1BA2171E6BA8CB007F9FAC /* SnapshotOptions.swift in Sources */, DA20FBA81CE3DF6900B07762 /* Snapshot.swift in Sources */, DA20FBAC1CE4026B00B07762 /* Overlay.swift in Sources */, + DA1BA20D1E6B4A90007F9FAC /* ClassicSnapshotOptions.swift in Sources */, DA20FBAA1CE401E800B07762 /* Color.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -699,8 +943,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DA1BA21D1E6BA918007F9FAC /* MarkerOptions.swift in Sources */, + DA1BA2181E6BA8CB007F9FAC /* SnapshotOptions.swift in Sources */, DA20FBD21CE475F300B07762 /* Overlay.swift in Sources */, DA20FBD31CE475F300B07762 /* Color.swift in Sources */, + DA1BA20E1E6B4A90007F9FAC /* ClassicSnapshotOptions.swift in Sources */, DA20FBD11CE475F300B07762 /* Snapshot.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -709,6 +956,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DA1BA2221E6CD572007F9FAC /* ClassicSnapshotTests.swift in Sources */, + DAE8CCB11E6E8E6B009B5CB0 /* SnapshotTests.swift in Sources */, + DA1BA2731E6D1E8E007F9FAC /* ClassicOverlayTests.swift in Sources */, DA20FBD41CE475FD00B07762 /* MapboxStaticTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -717,8 +967,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DA1BA21E1E6BA918007F9FAC /* MarkerOptions.swift in Sources */, + DA1BA2191E6BA8CB007F9FAC /* SnapshotOptions.swift in Sources */, DA4B4BAB1CE8F87000296A52 /* Overlay.swift in Sources */, DA4B4BAA1CE8F87000296A52 /* Snapshot.swift in Sources */, + DA1BA20F1E6B4A90007F9FAC /* ClassicSnapshotOptions.swift in Sources */, DA4B4BAC1CE8F87000296A52 /* Color.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -727,6 +980,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DA1BA2231E6CD572007F9FAC /* ClassicSnapshotTests.swift in Sources */, + DAE8CCB21E6E8E6B009B5CB0 /* SnapshotTests.swift in Sources */, + DA1BA2741E6D1E8E007F9FAC /* ClassicOverlayTests.swift in Sources */, DA4B4BAD1CE8F87700296A52 /* MapboxStaticTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -735,8 +991,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DA1BA21F1E6BA918007F9FAC /* MarkerOptions.swift in Sources */, + DA1BA21A1E6BA8CB007F9FAC /* SnapshotOptions.swift in Sources */, DAF15A451CE8FBBC0040E86C /* Overlay.swift in Sources */, DAF15A441CE8FBBC0040E86C /* Snapshot.swift in Sources */, + DA1BA2101E6B4A90007F9FAC /* ClassicSnapshotOptions.swift in Sources */, DAF15A461CE8FBBC0040E86C /* Color.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -764,6 +1023,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DA1BA2211E6CD572007F9FAC /* ClassicSnapshotTests.swift in Sources */, + DAE8CCB01E6E8E6B009B5CB0 /* SnapshotTests.swift in Sources */, + DA1BA2721E6D1E8E007F9FAC /* ClassicOverlayTests.swift in Sources */, DD685BA91BDB14C1002E2BB2 /* MapboxStaticTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -898,7 +1160,6 @@ buildSettings = { CLANG_ANALYZER_NONNULL = YES; CODE_SIGN_IDENTITY = "-"; - COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 5; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -918,7 +1179,6 @@ buildSettings = { CLANG_ANALYZER_NONNULL = YES; CODE_SIGN_IDENTITY = "-"; - COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 5; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/MapboxStatic/ClassicSnapshotOptions.swift b/MapboxStatic/ClassicSnapshotOptions.swift new file mode 100644 index 0000000..b2c6cfc --- /dev/null +++ b/MapboxStatic/ClassicSnapshotOptions.swift @@ -0,0 +1,213 @@ +#if os(OSX) + import Cocoa +#elseif os(watchOS) + import WatchKit +#else + import UIKit +#endif + +/** + A structure that determines what a snapshot depicts and how it is formatted. A classic snapshot is made by compositing one or more [tile sets](https://www.mapbox.com/help/define-tileset/) with optional overlays using the [classic Mapbox Static API](https://www.mapbox.com/api-documentation/?language=Swift#static-classic). + + Typically, you use a `ClassicSnapshotOptions` object to generate a snapshot of a [raster tile set](https://www.mapbox.com/help/define-tileset/#raster-tilesets). If you use `ClassicSnapshotOptions` to display a [vector tile set](https://www.mapbox.com/help/define-tileset/#vector-tilesets), the snapshot image will depict a wireframe representation of the tile set. To generate a static, styled image of a vector tile set, use a `SnapshotOptions` object. + */ +@objc(MBClassicSnapshotOptions) +open class ClassicSnapshotOptions: NSObject, SnapshotOptionsProtocol { + /** + An image format supported by the classic Static API. + */ + @objc(MBSnapshotFormat) + public enum Format: Int, CustomStringConvertible { + /// True-color Portable Network Graphics format. + case png + /// 32-color color-indexed Portable Network Graphics format. + case png32 + /// 64-color color-indexed Portable Network Graphics format. + case png64 + /// 128-color color-indexed Portable Network Graphics format. + case png128 + /// 256-color color-indexed Portable Network Graphics format. + case png256 + /// JPEG format at default quality. + case jpeg + /// JPEG format at 70% quality. + case jpeg70 + /// JPEG format at 80% quality. + case jpeg80 + /// JPEG format at 90% quality. + case jpeg90 + + public var description: String { + switch self { + case .png: + return "png" + case .png32: + return "png32" + case .png64: + return "png64" + case .png128: + return "png128" + case .png256: + return "png256" + case .jpeg: + return "jpg" + case .jpeg70: + return "jpg70" + case .jpeg80: + return "jpg80" + case .jpeg90: + return "jpg90" + } + } + } + + // MARK: Configuring the Map Data + + /** + An array of [map identifiers](https://www.mapbox.com/help/define-map-id/) of the form `username.id`, identifying the [tile sets](https://www.mapbox.com/help/define-tileset/) to display in the snapshot. This array may not be empty. + + The order of the map identifiers in the array reflects their visible order in the snapshot, with the tile set identified at index 0 being the backmost tile set. + */ + open var mapIdentifiers: [String] + + /** + An array of overlays to draw atop the map. + + The order in which the overlays are drawn on the map is undefined. + */ + open var overlays: [Overlay] = [] + + /** + The geographic coordinate at the center of the snapshot. + + If the value of this property is `nil`, the `zoomLevel` property is ignored and a center coordinate and zoom level are automatically chosen to fit any overlays specified in the `overlays` property. If the `overlays` property is also empty, the behavior is undefined. + + The default value of this property is `nil`. + */ + open var centerCoordinate: CLLocationCoordinate2D? + + /** + The zoom level of the snapshot. + + In addition to affecting the visual size and detail of features on the map, the zoom level may affect style properties that depend on the zoom level. + + `ClassicSnapshotOptions` zoom levels differ from `SnapshotCamera` zoom levels. At zoom level 0, the entire world map is 256 points wide and 256 points tall; at zoom level 1, it is 512×512 points; at zoom level 2, it is 1,024×1,024 points; and so on. + */ + open var zoomLevel: Int? + + // MARK: Configuring the Image Output + + /** + The format of the image to output. + + The default value of this property is `SnapshotOptions.Format.png`, causing the image to be output in true-color Portable Network Graphics format. + */ + open var format: Format = .png + + /** + The logical size of the image to output, measured in points. + */ + open var size: CGSize + + #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. + */ + open var scale: CGFloat = NSScreen.main()?.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. + */ + open var scale: CGFloat = WKInterfaceDevice.current().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. + */ + open var scale: CGFloat = UIScreen.main.scale + #endif + + /** + Initializes a snapshot options instance that causes a snapshotter object to automatically choose a center coordinate and zoom level that fits any overlays. + + After initializing a snapshot options instance with this initializer, set the `overlays` property to specify the overlays to fit the snapshot to. + + - parameter mapIdentifiers: An array of [map identifiers](https://www.mapbox.com/help/define-map-id/) of the form `username.id`, identifying the [tile sets](https://www.mapbox.com/help/define-tileset/) to display in the snapshot. This array may not be empty. + - parameter size: The logical size of the image to output, measured in points. + */ + public init(mapIdentifiers: [String], size: CGSize) { + self.mapIdentifiers = mapIdentifiers + self.size = size + } + + /** + Initializes a snapshot options instance that results in a snapshot centered at the given geographical coordinate and showing the given zoom level. + + - parameter mapIdentifiers: An array of [map identifiers](https://www.mapbox.com/help/define-map-id/) of the form `username.id`, identifying the [tile sets](https://www.mapbox.com/help/define-tileset/) to display in the snapshot. This array may not be empty. + - parameter centerCoordinate: The geographic coordinate at the center of the snapshot. + - parameter zoomLevel: The zoom level of the snapshot. + - parameter size: The logical size of the image to output, measured in points. + */ + public init(mapIdentifiers: [String], centerCoordinate: CLLocationCoordinate2D, zoomLevel: Int, size: CGSize) { + self.mapIdentifiers = mapIdentifiers + self.centerCoordinate = centerCoordinate + self.zoomLevel = zoomLevel + self.size = size + } + + /** + The path of the HTTP request URL corresponding to the options in this instance. + + - returns: An HTTP URL path. + */ + open var path: String { + assert(!mapIdentifiers.isEmpty, "At least one map identifier must be specified.") + let tileSetComponent = mapIdentifiers.joined(separator: ",") + + let position: String + if let centerCoordinate = centerCoordinate { + position = "\(centerCoordinate.longitude),\(centerCoordinate.latitude),\(zoomLevel ?? 0)" + } else { + position = "auto" + } + + if let zoomLevel = zoomLevel { + assert(zoomLevel >= 0, "minimum zoom is 0") + assert(zoomLevel <= 20, "maximum zoom is 20") + } + + assert(size.width <= 1_280, "maximum width is 1,280 points") + assert(size.height <= 1_280, "maximum height is 1,280 points") + + assert(overlays.count <= 100, "maximum number of overlays is 100") + + let overlaysComponent: String + if overlays.isEmpty { + overlaysComponent = "" + } else { + overlaysComponent = "/" + overlays.map { return "\($0)" }.joined(separator: ",") + } + + return "/v4/\(tileSetComponent)\(overlaysComponent)/\(position)/\(Int(round(size.width)))x\(Int(round(size.height)))\(scale > 1 ? "@2x" : "").\(format)" + } + + /** + 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. + */ + open var params: [URLQueryItem] { + return [] + } +} diff --git a/MapboxStatic/MarkerOptions.swift b/MapboxStatic/MarkerOptions.swift new file mode 100644 index 0000000..693168b --- /dev/null +++ b/MapboxStatic/MarkerOptions.swift @@ -0,0 +1,107 @@ +#if os(OSX) + import Cocoa +#elseif os(watchOS) + import WatchKit +#else + import UIKit +#endif + +/** + A structure that configures a standalone marker image and how it is formatted. A standalone marker image is produced by the [classic Mapbox Static API](https://www.mapbox.com/api-documentation/?language=Swift#static-classic). + */ +@objc(MBMarkerOptions) +open 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. + */ + open var scale: CGFloat = NSScreen.main()?.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. + */ + open var scale: CGFloat = WKInterfaceDevice.current().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. + */ + open var scale: CGFloat = UIScreen.main.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. + */ + fileprivate 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/) v0.5.0 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. + */ + open 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. + */ + open var params: [URLQueryItem] { + return [] + } +} diff --git a/MapboxStatic/Overlay.swift b/MapboxStatic/Overlay.swift index d5ebc7d..802c98c 100644 --- a/MapboxStatic/Overlay.swift +++ b/MapboxStatic/Overlay.swift @@ -35,11 +35,19 @@ open class MarkerImage: NSObject { */ @objc(MBMarkerSize) public enum Size: Int, CustomStringConvertible { - /// Small. + /** + A small marker. + + A small marker in a snapshot is the same size as a medium-sized marker in a classic snapshot. + */ case small - /// Medium. + /** + A medium-sized marker. + + A medium-sized marker in a snapshot is the same size as a large marker in a classic snapshot. + */ case medium - /// Large. + /// A large marker. case large public var description: String { @@ -61,9 +69,9 @@ open class MarkerImage: NSObject { /// A number from 0 through 99. case number(Int) /** - The name of a [Maki](https://www.mapbox.com/maki-icons/) v0.5.0 icon. + The name of a [Maki](https://www.mapbox.com/maki-icons/) icon. - Valid values are identified by the `icon` values in [this JSON file](https://github.com/mapbox/maki/blob/v0.5.0/_includes/maki.json). + The Static API uses Maki v4.0.0. See valid values at the [Maki](https://www.mapbox.com/maki-icons/) website. The classic Static API uses Maki v0.5.0. Valid values for classic snapshots are identified by the `icon` values in [this JSON file](https://github.com/mapbox/maki/blob/v0.5.0/_includes/maki.json). */ case iconName(String) @@ -177,6 +185,8 @@ open class Marker: MarkerImage, Point { 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/). + The Static API uses Maki v4.0.0. See valid values at the [Maki](https://www.mapbox.com/maki-icons/) website. The classic Static API uses Maki v0.5.0. Valid values for classic snapshots are identified by the `icon` values in [this JSON file](https://github.com/mapbox/maki/blob/v0.5.0/_includes/maki.json). + - parameter coordinate: The geographic coordinate to place the marker at. - 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. @@ -212,8 +222,6 @@ open class CustomMarker: NSObject, Overlay { /** The HTTP or HTTPS URL of the image. - The image must be less than 160,000 points in area (width multiplied by height). - The API caches custom marker images according to the `Expires` and `Cache-Control` headers. If you host the image on your own server, make sure that at least one of these headers is set to an proper value to prevent repeated requests for the image. */ open var url: URL @@ -256,11 +264,8 @@ open class GeoJSON: NSObject, Overlay { - parameter object: A valid GeoJSON object. - returns: A GeoJSON overlay, or `nil` if the given object is not a valid JSON object. This initializer does not check whether the object is valid GeoJSON, but invalid GeoJSON will cause the request to fail. */ - public init?(object: [String: AnyObject]) { - // This should be a throwing initializer rather than a failiable initializer, but inheriting from Objective-C triggers a warning: no calls to throwing functions occur within 'try' expression - guard let data = try? JSONSerialization.data(withJSONObject: object, options: []) else { - return nil - } + public init(object: [String: Any]) throws { + let data = try JSONSerialization.data(withJSONObject: object, options: []) objectString = String(data: data, encoding: .utf8)! } diff --git a/MapboxStatic/Snapshot.swift b/MapboxStatic/Snapshot.swift index e408cc6..c13f56d 100644 --- a/MapboxStatic/Snapshot.swift +++ b/MapboxStatic/Snapshot.swift @@ -66,315 +66,9 @@ public protocol SnapshotOptionsProtocol: NSObjectProtocol { } /** - A structure that determines what a snapshot depicts and how it is formatted. - */ -@objc(MBSnapshotOptions) -open class SnapshotOptions: NSObject, SnapshotOptionsProtocol { - /** - An image format supported by the classic Static API. - */ - @objc(MBSnapshotFormat) - public enum Format: Int, CustomStringConvertible { - /// True-color Portable Network Graphics format. - case png - /// 32-color color-indexed Portable Network Graphics format. - case png32 - /// 64-color color-indexed Portable Network Graphics format. - case png64 - /// 128-color color-indexed Portable Network Graphics format. - case png128 - /// 256-color color-indexed Portable Network Graphics format. - case png256 - /// JPEG format at default quality. - case jpeg - /// JPEG format at 70% quality. - case jpeg70 - /// JPEG format at 80% quality. - case jpeg80 - /// JPEG format at 90% quality. - case jpeg90 - - public var description: String { - switch self { - case .png: - return "png" - case .png32: - return "png32" - case .png64: - return "png64" - case .png128: - return "png128" - case .png256: - return "png256" - case .jpeg: - return "jpg" - case .jpeg70: - return "jpg70" - case .jpeg80: - return "jpg80" - case .jpeg90: - return "jpg90" - } - } - } - - // MARK: Configuring the Map Data - - /** - An array of [map identifiers](https://www.mapbox.com/help/define-map-id/) of the form `username.id`, identifying the [tile sets](https://www.mapbox.com/help/define-tileset/) to display in the snapshot. This array may not be empty. - - The order of the map identifiers in the array reflects their visible order in the snapshot, with the tile set identified at index 0 being the backmost tile set. - */ - open var mapIdentifiers: [String] - - /** - An array of overlays to draw atop the map. - - The order in which the overlays are drawn on the map is undefined. - */ - open var overlays: [Overlay] = [] - - /** - The geographic coordinate at the center of the snapshot. - - If the value of this property is `nil`, the `zoomLevel` property is ignored and a center coordinate and zoom level are automatically chosen to fit any overlays specified in the `overlays` property. If the `overlays` property is also empty, the behavior is undefined. - - The default value of this property is `nil`. - */ - open var centerCoordinate: CLLocationCoordinate2D? - - /** - The zoom level of the snapshot. - - In addition to affecting the visual size and detail of features on the map, the zoom level may affect style properties that depend on the zoom level. - - At zoom level 0, the entire world map is 256 points wide and 256 points tall; at zoom level 1, it is 512×512 points; at zoom level 2, it is 1,024×1,024 points; and so on. - */ - open var zoomLevel: Int? - - // MARK: Configuring the Image Output - - /** - The format of the image to output. - - The default value of this property is `SnapshotOptions.Format.png`, causing the image to be output in true-color Portable Network Graphics format. - */ - open var format: Format = .png - - /** - The logical size of the image to output, measured in points. - */ - open var size: CGSize - - #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. - */ - open var scale: CGFloat = NSScreen.main()?.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. - */ - open var scale: CGFloat = WKInterfaceDevice.current().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. - */ - open var scale: CGFloat = UIScreen.main.scale - #endif - - /** - Initializes a snapshot options instance that causes a snapshotter object to automatically choose a center coordinate and zoom level that fits any overlays. - - After initializing a snapshot options instance with this initializer, set the `overlays` property to specify the overlays to fit the snapshot to. - - - parameter mapIdentifiers: An array of [map identifiers](https://www.mapbox.com/help/define-map-id/) of the form `username.id`, identifying the [tile sets](https://www.mapbox.com/help/define-tileset/) to display in the snapshot. This array may not be empty. - - parameter size: The logical size of the image to output, measured in points. - */ - public init(mapIdentifiers: [String], size: CGSize) { - self.mapIdentifiers = mapIdentifiers - self.size = size - } - - /** - Initializes a snapshot options instance that results in a snapshot centered at the given geographical coordinate and showing the given zoom level. - - - parameter mapIdentifiers: An array of [map identifiers](https://www.mapbox.com/help/define-map-id/) of the form `username.id`, identifying the [tile sets](https://www.mapbox.com/help/define-tileset/) to display in the snapshot. This array may not be empty. - - parameter centerCoordinate: The geographic coordinate at the center of the snapshot. - - parameter zoomLevel: The zoom level of the snapshot. - - parameter size: The logical size of the image to output, measured in points. - */ - public init(mapIdentifiers: [String], centerCoordinate: CLLocationCoordinate2D, zoomLevel: Int, size: CGSize) { - self.mapIdentifiers = mapIdentifiers - self.centerCoordinate = centerCoordinate - self.zoomLevel = zoomLevel - self.size = size - } - - /** - The path of the HTTP request URL corresponding to the options in this instance. - - - returns: An HTTP URL path. - */ - open var path: String { - assert(!mapIdentifiers.isEmpty, "At least one map identifier must be specified.") - let tileSetComponent = mapIdentifiers.joined(separator: ",") - - let position: String - if let centerCoordinate = centerCoordinate { - position = "\(centerCoordinate.longitude),\(centerCoordinate.latitude),\(zoomLevel ?? 0)" - } else { - position = "auto" - } - - if let zoomLevel = zoomLevel { - assert(zoomLevel >= 0, "minimum zoom is 0") - assert(zoomLevel <= 20, "maximum zoom is 20") - } - - assert(size.width <= 1_280, "maximum width is 1,280 points") - assert(size.height <= 1_280, "maximum height is 1,280 points") - - assert(overlays.count <= 100, "maximum number of overlays is 100") - - let overlaysComponent: String - if overlays.isEmpty { - overlaysComponent = "" - } else { - overlaysComponent = "/" + overlays.map { return "\($0)" }.joined(separator: ",") - } - - return "/v4/\(tileSetComponent)\(overlaysComponent)/\(position)/\(Int(round(size.width)))x\(Int(round(size.height)))\(scale > 1 ? "@2x" : "").\(format)" - } - - /** - 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. - */ - open var params: [URLQueryItem] { - return [] - } -} - -/** - A structure that configures a standalone marker image and how it is formatted. - */ -@objc(MBMarkerOptions) -open 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. - */ - open var scale: CGFloat = NSScreen.main()?.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. - */ - open var scale: CGFloat = WKInterfaceDevice.current().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. - */ - open var scale: CGFloat = UIScreen.main.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. - */ - fileprivate 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. - */ - open 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. - */ - open var params: [URLQueryItem] { - return [] - } -} - -/** - A `Snapshot` instance represents a static snapshot of a map made by compositing one or more [raster tile sets](https://www.mapbox.com/help/define-tileset/#raster-tilesets) with optional overlays. With a snapshot instance, you can synchronously or asynchronously generate an image based on the options you provide via an HTTP request, or you can get the URL used to make this request. The image is obtained on demand from the [classic Mapbox Static API](https://www.mapbox.com/api-documentation/?language=Swift#static-classic). - - The snapshot image can be used in an image view (`UIImage` on iOS and tvOS, `NSImage` on macOS, `WKImage` on watchOS). To add interactivity, use the `MGLMapView` class provided by the [Mapbox iOS SDK](https://www.mapbox.com/ios-sdk/) or [Mapbox macOS SDK](https://github.com/mapbox/mapbox-gl-native/tree/master/platform/macos/). See the “[Custom raster style](https://www.mapbox.com/ios-sdk/examples/raster-styles/)” example to display a raster tile set in an `MGLMapView`. + A `Snapshot` instance represents a static snapshot of a map with optional overlays. With a snapshot instance, you can synchronously or asynchronously generate an image based on the options you provide via an HTTP request, or you can get the URL used to make this request. The image is obtained on demand from the [Mapbox Static API](https://www.mapbox.com/api-documentation/#static) or the [classic Mapbox Static API](https://www.mapbox.com/api-documentation/?language=Swift#static-classic), depending on whether you use a `SnapshotOptions` object or a `ClassicSnapshotOptions` object. - If you use `Snapshot` to display a [vector tile set](https://www.mapbox.com/help/define-tileset/#vector-tilesets), the snapshot image will depict a wireframe representation of the tile set. To generate a static, styled image of a vector tile set, use the [vector Mapbox Static API](https://www.mapbox.com/api-documentation/?language=Swift#static). + The snapshot image can be used in an image view (`UIImage` on iOS and tvOS, `NSImage` on macOS, `WKImage` on watchOS). The image does not respond to user gestures. To add interactivity, use the [Mapbox iOS SDK](https://www.mapbox.com/ios-sdk/) or [Mapbox macOS SDK](https://github.com/mapbox/mapbox-gl-native/tree/master/platform/macos/), which can optionally display raster tiles. */ @objc(MBSnapshot) open class Snapshot: NSObject { @@ -396,17 +90,17 @@ open class Snapshot: NSObject { open let options: SnapshotOptionsProtocol /// The API endpoint to request the image from. - fileprivate var apiEndpoint: String + internal var apiEndpoint: URL /// The Mapbox access token to associate the request with. - fileprivate let accessToken: String + internal let accessToken: String /** Initializes a newly created snapshot instance with the given options and an optional access token and host. - 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. - - parameter host: An optional hostname to the server API. The classic Mapbox Static API endpoint is used by default. + - parameter host: An optional hostname to the server API. The official Mapbox API endpoint is used by default. */ public init(options: SnapshotOptionsProtocol, accessToken: String?, host: String?) { let accessToken = accessToken ?? defaultAccessToken @@ -418,13 +112,13 @@ open class Snapshot: NSObject { var baseURLComponents = URLComponents() baseURLComponents.scheme = "https" baseURLComponents.host = host ?? "api.mapbox.com" - apiEndpoint = baseURLComponents.string! + apiEndpoint = baseURLComponents.url! } /** Initializes a newly created snapshot instance with the given options and an optional access token. - The snapshot instance sends requests to the classic Mapbox Static API endpoint. + The snapshot instance sends requests to the official Mapbox API endpoint. - 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. @@ -436,7 +130,7 @@ open class Snapshot: NSObject { /** Initializes a newly created snapshot instance with the given options and the default access token. - The snapshot instance sends requests to the classic Mapbox Static API endpoint. + The snapshot instance sends requests to the official Mapbox API endpoint. - parameter options: Options that determine the contents and format of the output image. */ @@ -450,7 +144,7 @@ open class Snapshot: NSObject { open var url: URL { var components = URLComponents() components.queryItems = params - return URL(string: "\(apiEndpoint)\(options.path)?\(components.percentEncodedQuery!)")! + return URL(string: "\(options.path)?\(components.percentEncodedQuery!)", relativeTo: apiEndpoint)! } /** diff --git a/MapboxStatic/SnapshotOptions.swift b/MapboxStatic/SnapshotOptions.swift new file mode 100644 index 0000000..24459e7 --- /dev/null +++ b/MapboxStatic/SnapshotOptions.swift @@ -0,0 +1,338 @@ +#if os(OSX) + import Cocoa +#elseif os(watchOS) + import WatchKit +#else + import UIKit +#endif + +/** + A structure defining the viewpoint from which a snapshot is taken. + */ +@objc(MBSnapshotCamera) +open class SnapshotCamera: NSObject { + /** + Vertical field of view, measured in degrees, for determining the altitude of the viewpoint. + */ + static let angularFieldOfView: CLLocationDegrees = 30 + + /** + Radius of the Earth, measured in meters. + */ + static let radiusOfEarth: CLLocationDistance = 6_378_137 + + /** + Size of a tile. + */ + static let tileSize = CGSize(width: 512, height: 512) + + /** + The geographic coordinate at the center of the snapshot. + + If the value of this property is `nil`, the `zoomLevel` property is ignored and a center coordinate and zoom level are automatically chosen to fit any overlays specified in the `overlays` property. If the `overlays` property is also empty, the behavior is undefined. + */ + open var centerCoordinate: CLLocationCoordinate2D + + /** + The distance (in meters) from the center coordinate at ground level to the viewpoint. + + If `zoomLevel` is specified, this property is ignored. + */ + open var altitude: CLLocationDistance? + + /** + The zoom level of the snapshot. + + In addition to affecting the visual size and detail of features on the map, the zoom level may affect style properties that depend on the zoom level. + + `SnapshotCamera` zoom levels differ from `ClassicSnapshotOptions` zoom levels. At zoom level 0, the entire world map is 512 points wide and 512 points tall; at zoom level 1, it is 1,024×1,024 points; at zoom level 2, it is 2,048×2,048 points; and so on. When the map is tilted, the zoom level affects the viewing distance from the viewer to the center coordinate. + + The zoom level may not be less than 0 or greater than 20. Fractional zoom levels are rounded to two decimal places. + */ + open var zoomLevel: CGFloat? + + /** + The heading measured in degrees clockwise from true north. + */ + open var heading: CLLocationDirection + + /** + The pitch toward the horizon measured in degrees, with 0 degrees resulting in a two-dimensional map. + + The pitch may not be less than 0 or greater than 60. + */ + open var pitch: CGFloat + + /** + Initializes a snapshot camera instance based on the given center coordinate, distance, pitch, and heading. + + - parameter centerCoordinate: The geographic coordinate on which the shapshot should be centered. + - parameter distance: The straight-line distance from the viewpoint to the `centerCoordinate`. + - parameter pitch: The viewing angle of the camera, measured in degrees. A value of `0` results in a camera pointed straight down at the map. Angles greater than `0` result in a camera angled toward the horizon. + - parameter heading: The camera’s heading, measured in degrees clockwise from true north. A value of `0` means that the top edge of the map view corresponds to true north. The value `90` means the top of the map is pointing due east. The value `180` means the top of the map points due south, and so on. + */ + @nonobjc + public required init(lookingAtCenter centerCoordinate: CLLocationCoordinate2D, fromDistance distance: CLLocationDistance, pitch: CGFloat = 0, heading: CLLocationDirection = 0) { + self.centerCoordinate = centerCoordinate + self.altitude = distance + self.pitch = pitch + self.heading = heading + } + + /** + Initializes a snapshot camera instance based on the given center coordinate and zoom level. + + - parameter centerCoordinate: The geographic coordinate on which the shapshot should be centered. + - parameter zoomLevel: The zoom level of the snapshot. + - parameter pitch: The viewing angle of the camera, measured in degrees. A value of `0` results in a camera pointed straight down at the map. Angles greater than `0` result in a camera angled toward the horizon. + - parameter heading: The camera’s heading, measured in degrees clockwise from true north. A value of `0` means that the top edge of the map view corresponds to true north. The value `90` means the top of the map is pointing due east. The value `180` means the top of the map points due south, and so on. + */ + @nonobjc + public required init(lookingAtCenter centerCoordinate: CLLocationCoordinate2D, zoomLevel: CGFloat, pitch: CGFloat = 0, heading: CLLocationDirection = 0) { + self.centerCoordinate = centerCoordinate + self.zoomLevel = zoomLevel + self.pitch = pitch + self.heading = heading + } + + /** + Creates and returns a snapshot camera instance based on the given center coordinate and zoom level. + + This factory method is intended for use in Objective-C. In Swift, use the `init(lookingAtCenter:zoomLevel:)` initializer instead. + + - parameter centerCoordinate: The geographic coordinate on which the shapshot should be centered. + - parameter zoomLevel: The zoom level of the snapshot. + - returns: A snapshot camera based on the given center coordinate and zoom level. + */ + @objc(cameraLookingAtCenterCoordinate:zoomLevel:) + public static func camera(lookingAtCenter centerCoordinate: CLLocationCoordinate2D, zoomLevel: CGFloat) -> Self { + return self.init(lookingAtCenter: centerCoordinate, zoomLevel: zoomLevel) + } + + func string(size: CGSize) -> String { + assert(-90...90 ~= centerCoordinate.latitude, "Center latitude must be between −90° and 90°.") + assert(-180...180 ~= centerCoordinate.latitude, "Center longitude must be between −180° and 180°.") + + var components = [centerCoordinate.longitude, centerCoordinate.latitude] + + var zoomLevel: CGFloat = 0 + if let level = self.zoomLevel { + zoomLevel = level + } else if let altitude = altitude { + zoomLevel = SnapshotCamera.zoomLevelForAltitude(altitude, pitch: pitch, latitude: centerCoordinate.latitude, size: size) + } + assert(0...20 ~= zoomLevel, "Zoom level must be between 0 and 20.") + components.append(Double(zoomLevel)) + + if heading > 0 { + components.append(heading) + } else if pitch > 0 { + components.append(0) + } + + assert(0...60 ~= pitch, "Pitch must be between 0° and 60°.") + if pitch > 0 { + components.append(Double(pitch)) + } + + return components.map { "\($0)" }.joined(separator: ",") + } + + /** + Converts a camera altitude to a map zoom level. + + - parameter altitude: The altitude to convert, measured in meters. + - parameter pitch: The camera pitch, measured in degrees. + - parameter latitude: The latitude of the point at the center of the viewport. + - parameter size: The size of the viewport. + - returns: A zero-based zoom level. + */ + static func zoomLevelForAltitude(_ altitude: CLLocationDistance, pitch: CGFloat, latitude: CLLocationDegrees, size: CGSize) -> CGFloat { + let eyeAltitude: CLLocationDistance = altitude / sin(.pi / 2 - radians(degrees: Double(pitch))) * sin(.pi / 2) + let metersTall: CLLocationDistance = eyeAltitude * 2 * tan(radians(degrees: angularFieldOfView) / 2) + let metersPerPixel: CLLocationDistance = metersTall / Double(size.height) + let mapPixelWidthAtZoom = cos(radians(degrees: latitude)) * .pi * 2 * radiusOfEarth / metersPerPixel + return CGFloat(log2(mapPixelWidthAtZoom / Double(tileSize.width))) + } + + /** + Returns radians, converted from degrees. + */ + static func radians(degrees: Double) -> Double { + return degrees * .pi / 180.0 + } +} + +/** + A structure that determines what a snapshot depicts and how it is formatted. A static snapshot is made by compositing a [style](https://www.mapbox.com/help/define-style/) with optional overlays using the [Mapbox Static API](https://www.mapbox.com/api-documentation/#static). You can use a [Mapbox-designed style](https://www.mapbox.com/api-documentation/#styles) or design your own custom style using [Mapbox Studio](https://www.mapbox.com/studio/). You can only snapshot a style hosted by Mapbox. + + To generate a static, styled image of a tile set, especially a raster tile set, use a `Classic SnapshotOptions` object. + + The Static API always outputs images in true-color Portable Network Graphics (PNG) format. For other image formats, use a `ClassicSnapshotOptions` object. + */ +@objc(MBSnapshotOptions) +open class SnapshotOptions: NSObject, SnapshotOptionsProtocol { + // MARK: Configuring the Map Data + + /** + The [style URL](https://www.mapbox.com/help/define-style-url/) of the style to snapshot. + + Only `mapbox:` URLs are supported. You can only snapshot a style hosted by Mapbox, such as a [Mapbox-designed style](https://www.mapbox.com/api-documentation/#styles). + */ + open var styleURL: URL + + /** + An array of overlays to draw atop the map. + + The order in which the overlays are drawn on the map is undefined. + */ + open var overlays: [Overlay] = [] + + /** + The identifier of the [style layer](https://www.mapbox.com/help/define-layer/) below which any overlays should be inserted. + + This property allows you to insert overlays at any level of the map, not necessarily at the top. For example, if you are adding `Path` overlays to the snapshot, you may want to place them below any [symbol layers](https://www.mapbox.com/mapbox-gl-js/style-spec/#layer-type) to ensure that street and point of interest labels remain legible. + + If this property is set to `nil`, any overlays are placed atop any layers defined by the style. By default, this property is set to `nil`. + + Layer identifiers are not guaranteed to exist across styles or different versions of the same style. To find out the layer identifiers in a particular style, view the style in [Mapbox Studio](https://www.mapbox.com/studio/). + */ + open var identifierOfLayerAboveOverlays: String? + + /** + The viewpoint from which the snapshot is taken. + + If the value of this property is `nil`, a center coordinate and zoom level are automatically chosen to fit any overlays specified in the `overlays` property, and no heading or pitch is applied. If the `overlays` property is also empty, the behavior is undefined. + + The default value of this property is `nil`. + */ + open var camera: SnapshotCamera? + + // MARK: Configuring the Image Output + + /** + The logical size of the image to output, measured in points. + + The width may not be less than 1 point or greater than 1,280 points. Likewise, the height may not be less than 1 point or greater than 1,280 points. + */ + open var size: CGSize + + #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 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. + */ + open var scale: CGFloat = NSScreen.main()?.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 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. + */ + open var scale: CGFloat = WKInterfaceDevice.current().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 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. + */ + open var scale: CGFloat = UIScreen.main.scale + #endif + + /** + A Boolean determining whether the resulting image includes the Mapbox logo. + + When shown, the Mapbox logo is located in the lower-left corner of the image. By default, this property is set to `true`. + + - note: The Mapbox terms of service [requires](https://www.mapbox.com/help/attribution/) the [Mapbox logo](https://www.mapbox.com/about/press/brand-guidelines/) to accompany the snapshot. In general, you should not set this property to `false`. Contact your Mapbox sales representative for information about waiving this requirement. This requirement is distinct from the requirement that governs the `showsAttribution` property. + */ + open var showsLogo = true + + /** + A Boolean determining whether the resulting image includes legally required copyright notices. + + When shown, the attribution is located in the bottom-right corner of the image. By default, this property is set to `true`. + + - note: The Mapbox terms of service, which governs the use of Mapbox-hosted vector tiles and styles, [requires](https://www.mapbox.com/help/attribution/) these copyright notices to accompany any map that features Mapbox-designed styles, OpenStreetMap data, or other Mapbox data such as satellite or terrain data. If this requirement applies to the shapshot and you set this property to `false`, you must provide [proper attribution](https://www.mapbox.com/help/attribution/#static--print) near the snapshot. This requirement is distinct from the requirement that governs the `showsLogo` property. + */ + open var showsAttribution = true + + /** + Initializes a snapshot options instance that causes a snapshotter object to automatically choose a center coordinate and zoom level that fits any overlays. + + After initializing a snapshot options instance with this initializer, set the `overlays` property to specify the overlays to fit the snapshot to. + + - parameter styleURL: The [style URL](https://www.mapbox.com/help/define-style-url/) of the style to snapshot. Only `mapbox:` URLs are supported. You can only snapshot a style hosted by Mapbox, such as a [Mapbox-designed style](https://www.mapbox.com/api-documentation/#styles). + - parameter size: The logical size of the image to output, measured in points. + */ + public init(styleURL: URL, size: CGSize) { + self.styleURL = styleURL + self.size = size + } + + /** + Initializes a snapshot options instance that results in a snapshot centered at the given geographical coordinate and showing the given zoom level. + + - parameter styleURL: The [style URL](https://www.mapbox.com/help/define-style-url/) of the style to snapshot. Only `mapbox:` URLs are supported. You can only snapshot a style hosted by Mapbox, such as a [Mapbox-designed style](https://www.mapbox.com/api-documentation/#styles). + - parameter camera: The viewpoint from which the snapshot is taken. + - parameter size: The logical size of the image to output, measured in points. + */ + public init(styleURL: URL, camera: SnapshotCamera, size: CGSize) { + self.styleURL = styleURL + self.camera = camera + self.size = size + } + + /** + The path of the HTTP request URL corresponding to the options in this instance. + + - returns: An HTTP URL path. + */ + open var path: String { + assert(styleURL.scheme == "mapbox", "Only mapbox: URLs are supported. See https://www.mapbox.com/help/define-style-url/ or https://www.mapbox.com/api-documentation/#styles for valid style URLs.") + assert(styleURL.host == "styles", "Invalid mapbox: URL. See https://www.mapbox.com/help/define-style-url/ or https://www.mapbox.com/api-documentation/#styles for valid style URLs.") + let styleIdentifierComponent = "\(styleURL.path)/static" + + let position = camera?.string(size: size) ?? "auto" + + assert(1...1_280 ~= size.width, "Width must be between 1 and 1,280 points.") + assert(1...1_280 ~= size.height, "Height must be between 1 and 1,280 points.") + + assert(overlays.count <= 100, "maximum number of overlays is 100") + + let overlaysComponent: String + if overlays.isEmpty { + overlaysComponent = "" + } else { + overlaysComponent = "/" + overlays.map { return "\($0)" }.joined(separator: ",") + } + + return "/styles/v1\(styleIdentifierComponent)\(overlaysComponent)/\(position)/\(Int(round(size.width)))x\(Int(round(size.height)))\(scale > 1 ? "@2x" : "")" + } + + /** + 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. + */ + open var params: [URLQueryItem] { + var params: [URLQueryItem] = [] + if let identifierOfLayerAboveOverlays = identifierOfLayerAboveOverlays { + params.append(URLQueryItem(name: "before_layer", value: identifierOfLayerAboveOverlays)) + } + if !showsLogo { + params.append(URLQueryItem(name: "logo", value: String(showsLogo))) + } + if !showsAttribution { + params.append(URLQueryItem(name: "attribution", value: String(showsAttribution))) + } + return params + } +} diff --git a/MapboxStaticTests/ClassicOverlayTests.swift b/MapboxStaticTests/ClassicOverlayTests.swift new file mode 100644 index 0000000..b709310 --- /dev/null +++ b/MapboxStaticTests/ClassicOverlayTests.swift @@ -0,0 +1,156 @@ +import Foundation +import XCTest +import OHHTTPStubs +import CoreLocation +@testable import MapboxStatic + +#if os(OSX) + typealias Color = NSColor +#else + typealias Color = UIColor +#endif + +class ClassicOverlayTests: XCTestCase { + override func tearDown() { + OHHTTPStubs.removeAllStubs() + super.tearDown() + } + + func testBuiltinMarker() { + let markerOverlay = Marker( + coordinate: CLLocationCoordinate2D(latitude: 45.52, longitude: -122.681944), + size: .medium, + iconName: "cafe") + markerOverlay.color = .brown + + let options = ClassicSnapshotOptions( + mapIdentifiers: ["mapbox.streets"], + size: CGSize(width: 200, height: 200)) + options.overlays = [markerOverlay] + options.scale = 1 + + stub(condition: isHost("api.mapbox.com") + && isPath("/v4/mapbox.streets/pin-m-cafe+996633(-122.681944,45.52)/auto/200x200.png") + && containsQueryParams(["access_token": BogusToken])) { request in + let path = Bundle(for: type(of: self)).path(forResource: "marker", ofType: "png")! + return fixture(filePath: path, headers: ["Content-Type": "image/png"]) + } + + XCTAssertNotNil(Snapshot(options: options, accessToken: BogusToken).image) + } + + func testCustomMarker() { + let coordinate = CLLocationCoordinate2D(latitude: 45.522, longitude: -122.69) + let markerURL = URL(string: "https://www.mapbox.com/help/img/screenshots/rocket.png")! + + let customMarker = CustomMarker(coordinate: coordinate, url: markerURL) + + let options = ClassicSnapshotOptions( + mapIdentifiers: ["mapbox.streets"], + size: CGSize(width: 200, height: 200)) + options.overlays = [customMarker] + options.scale = 1 + + stub(condition: isHost("api.mapbox.com") + && isPath("/v4/mapbox.streets/url-\(markerURL)(-122.69,45.522)/auto/200x200.png") + && containsQueryParams(["access_token": BogusToken])) { request in + let path = Bundle(for: type(of: self)).path(forResource: "rocket", ofType: "png")! + return fixture(filePath: path, headers: ["Content-Type": "image/png"]) + } + + XCTAssertNotNil(Snapshot(options: options, accessToken: BogusToken).image) + } + + func testGeoJSONString() { + let geoJSONURL = Bundle(for: type(of: self)).url(forResource: "polyline", withExtension: "geojson")! + + let geoJSONString = try! String(contentsOf: geoJSONURL, encoding: .utf8) + let geoJSONOverlay = GeoJSON(objectString: geoJSONString) + + let options = ClassicSnapshotOptions( + mapIdentifiers: ["mapbox.streets"], + size: CGSize(width: 200, height: 200)) + options.overlays = [geoJSONOverlay] + options.scale = 1 + + stub(condition: isHost("api.mapbox.com") + && isPath("/v4/mapbox.streets/geojson(\(geoJSONString))/auto/200x200.png") + && containsQueryParams(["access_token": BogusToken])) { request in + let path = Bundle(for: type(of: self)).path(forResource: "geojson", ofType: "png")! + return fixture(filePath: path, headers: ["Content-Type": "image/png"]) + } + + XCTAssertNotNil(Snapshot(options: options, accessToken: BogusToken).image) + } + + func testGeoJSONObject() { + let geoJSONURL = Bundle(for: type(of: self)).url(forResource: "polyline", withExtension: "geojson")! + + let data = try! Data(contentsOf: geoJSONURL) + let geoJSON = try! JSONSerialization.jsonObject(with: data, options: []) as! [String: Any] + let geoJSONOverlay = try! GeoJSON(object: geoJSON) + + let options = ClassicSnapshotOptions( + mapIdentifiers: ["mapbox.streets"], + size: CGSize(width: 200, height: 200)) + options.overlays = [geoJSONOverlay] + options.scale = 1 + + let geoJSONString = "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"properties\":{\"stroke-width\":3,\"stroke-opacity\":1,\"stroke\":\"#00f\"},\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[-122.6978445053101,45.51863175803531],[-122.6909136772156,45.52165369248977],[-122.68630027771,45.51891742047702],[-122.6850986480713,45.51631633525551],[-122.6823306083679,45.51950377568216]]}}]}" + + stub(condition: isHost("api.mapbox.com") + && isPath("/v4/mapbox.streets/geojson(\(geoJSONString))/auto/200x200.png") + && containsQueryParams(["access_token": BogusToken])) { request in + let path = Bundle(for: type(of: self)).path(forResource: "geojson", ofType: "png")! + return fixture(filePath: path, headers: ["Content-Type": "image/png"]) + } + + XCTAssertNotNil(Snapshot(options: options, accessToken: BogusToken).image) + } + + func testPath() { + let path = Path( + coordinates: [ + 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 + ) + ]) + path.strokeWidth = 2 + path.strokeColor = .black + path.strokeOpacity = 0.75 + path.fillColor = .red + path.fillOpacity = 0.25 + + let options = ClassicSnapshotOptions( + mapIdentifiers: ["mapbox.streets"], + size: CGSize(width: 200, height: 200)) + options.overlays = [path] + options.scale = 1 + + let encodedPolyline = "upztG`jxkVn@al@bo@nFWzuAaTcAyZen@" + + stub(condition: isHost("api.mapbox.com") + && isPath("/v4/mapbox.streets/path-2+000000-0.75+ff0000-0.25(\(encodedPolyline))/auto/200x200.png") + && containsQueryParams(["access_token": BogusToken])) { request in + let path = Bundle(for: type(of: self)).path(forResource: "path", ofType: "png")! + return fixture(filePath: path, headers: ["Content-Type": "image/png"]) + } + + XCTAssertNotNil(Snapshot(options: options, accessToken: BogusToken).image) + } +} diff --git a/MapboxStaticTests/ClassicSnapshotTests.swift b/MapboxStaticTests/ClassicSnapshotTests.swift new file mode 100644 index 0000000..54c2cd3 --- /dev/null +++ b/MapboxStaticTests/ClassicSnapshotTests.swift @@ -0,0 +1,178 @@ +import Foundation +import XCTest +import OHHTTPStubs +import CoreLocation +@testable import MapboxStatic + +class ClassicSnapshotTests: XCTestCase { + override func tearDown() { + OHHTTPStubs.removeAllStubs() + super.tearDown() + } + + func testBasicMap() { + let options = ClassicSnapshotOptions(mapIdentifiers: ["mapbox.mapbox-streets-v6"], size: CGSize(width: 200, height: 200)) + options.scale = 1 + + stub(condition: isHost("api.mapbox.com") + && isPath("/v4/mapbox.mapbox-streets-v6/auto/200x200.png") + && containsQueryParams(["access_token": BogusToken])) { request in + let path = Bundle(for: type(of: self)).path(forResource: "basic", ofType: "png")! + return fixture(filePath: path, headers: ["Content-Type": "image/png"]) + } + + stub(condition: isHost("api.mapbox.com") + && isPath("/v4/mapbox.mapbox-streets-v6/auto/200x200@2x.png") + && containsQueryParams(["access_token": BogusToken])) { request in + let path = Bundle(for: type(of: self)).path(forResource: "basic@2x", ofType: "png")! + return fixture(filePath: path, headers: ["Content-Type": "image/png"]) + } + + let loDPIImage = Snapshot(options: options, accessToken: BogusToken).image + XCTAssertNotNil(loDPIImage) + XCTAssertEqual(loDPIImage?.size.width, 200) + XCTAssertEqual(loDPIImage?.size.height, 200) + + options.scale = 2 + let hiDPIImage = Snapshot(options: options, accessToken: BogusToken).image + XCTAssertNotNil(hiDPIImage) + XCTAssertEqual(hiDPIImage?.size.width, 400, "LoDPI image should be half the width of HiDPI image.") + XCTAssertEqual(hiDPIImage?.size.height, 400, "LoDPI image should be half the height of HiDPI image.") + } + + func testCenter() { + let options = ClassicSnapshotOptions( + mapIdentifiers: ["mapbox.mapbox-streets-v6"], + centerCoordinate: CLLocationCoordinate2D(latitude: 5.971389, longitude: 116.095278), + zoomLevel: 0, + size: CGSize(width: 200, height: 200)) + options.scale = 1 + + stub(condition: isHost("api.mapbox.com") + && isPath("/v4/mapbox.mapbox-streets-v6/116.095278,5.971389,0/200x200.png") + && containsQueryParams(["access_token": BogusToken])) { request in + let path = Bundle(for: type(of: self)).path(forResource: "center", ofType: "png")! + return fixture(filePath: path, headers: ["Content-Type": "image/png"]) + } + + XCTAssertNotNil(Snapshot(options: options, accessToken: BogusToken).image) + } + + func testZoom() { + let options = ClassicSnapshotOptions( + mapIdentifiers: ["mapbox.mapbox-streets-v6"], + centerCoordinate: CLLocationCoordinate2D(latitude: 0, longitude: 0), + zoomLevel: 6, + size: CGSize(width: 300, height: 300)) + options.scale = 1 + + stub(condition: isHost("api.mapbox.com") + && isPath("/v4/mapbox.mapbox-streets-v6/0.0,0.0,6/300x300.png") + && containsQueryParams(["access_token": BogusToken])) { request in + let path = Bundle(for: type(of: self)).path(forResource: "zoom", ofType: "png")! + return fixture(filePath: path, headers: ["Content-Type": "image/png"]) + } + + XCTAssertNotNil(Snapshot(options: options, accessToken: BogusToken).image) + } + + func testSize() { + let min: UInt32 = 1 + let max: UInt32 = 1280 + + let width = arc4random_uniform(max - min) + min + let height = arc4random_uniform(max - min) + min + + let options = ClassicSnapshotOptions( + mapIdentifiers: ["mapbox.mapbox-streets-v6"], + size: CGSize(width: CGFloat(width), height: CGFloat(height))) + options.scale = 1 + + stub(condition: isHost("api.mapbox.com") + && isPath("/v4/mapbox.mapbox-streets-v6/auto/\(width)x\(height).png") + && containsQueryParams(["access_token": BogusToken])) { request in + let path = Bundle(for: type(of: self)).path(forResource: "basic", ofType: "png")! + return fixture(filePath: path, headers: ["Content-Type": "image/png"]) + } + + XCTAssertNotNil(Snapshot(options: options, accessToken: BogusToken).image) + // Can’t test the image size here because the fixture is fixed-size but the tests chooses the size at random. + } + + func testFormats() { + let allFormats: [ClassicSnapshotOptions.Format] = [ + .png, .png32, .png64, .png128, .png256, + .jpeg, .jpeg70, .jpeg80, .jpeg90, + ] + + for format in allFormats { + let pathExtension: String + let mimeType: String + switch format { + case .png: + pathExtension = "png" + mimeType = "image/png" + case .png32: + pathExtension = "png32" + mimeType = "image/png" + case .png64: + pathExtension = "png64" + mimeType = "image/png" + case .png128: + pathExtension = "png128" + mimeType = "image/png" + case .png256: + pathExtension = "png256" + mimeType = "image/png" + case .jpeg: + pathExtension = "jpg" + mimeType = "image/jpeg" + case .jpeg70: + pathExtension = "jpg70" + mimeType = "image/jpeg" + case .jpeg80: + pathExtension = "jpg80" + mimeType = "image/jpeg" + case .jpeg90: + pathExtension = "jpg90" + mimeType = "image/jpeg" + // If you get a “Switch must be exhaustive, consider adding a default clause” error here, allFormats is also missing a format. + } + + testFormat(format, pathExtension: pathExtension, mimeType: mimeType) + } + } + + func testFormat(_ format: ClassicSnapshotOptions.Format, pathExtension: String, mimeType: String) { + let options = ClassicSnapshotOptions( + mapIdentifiers: ["mapbox.streets"], + size: CGSize(width: 200, height: 200)) + options.format = format + options.scale = 1 + + stub(condition: isHost("api.mapbox.com") + && pathStartsWith("/v4/mapbox.streets/auto/200x200") + && isExtension(pathExtension) + && containsQueryParams(["access_token": BogusToken])) { request in + let path = Bundle(for: type(of: self)).path(forResource: "format", ofType: pathExtension)! + return fixture(filePath: path, headers: ["Content-Type": mimeType]) + } + + XCTAssertNotNil(Snapshot(options: options, accessToken: BogusToken).image) + } + + func testStandaloneMarker() { + let options = MarkerOptions(size: .medium, iconName: "cafe") + options.color = .brown + options.scale = 1 + + stub(condition: isHost("api.mapbox.com") + && isPath("/v4/marker/pin-m-cafe+996633.png") + && containsQueryParams(["access_token": BogusToken])) { request in + let path = Bundle(for: type(of: self)).path(forResource: "cafe", ofType: "png")! + return fixture(filePath: path, headers: ["Content-Type": "image/png"]) + } + + XCTAssertNotNil(Snapshot(options: options, accessToken: BogusToken).image) + } +} diff --git a/MapboxStaticTests/MapboxStaticTests.swift b/MapboxStaticTests/MapboxStaticTests.swift index c20ef40..80bdbf2 100644 --- a/MapboxStaticTests/MapboxStaticTests.swift +++ b/MapboxStaticTests/MapboxStaticTests.swift @@ -4,479 +4,24 @@ import CoreLocation import Foundation @testable import MapboxStatic -#if os(OSX) - typealias Color = NSColor -#else - typealias Color = UIColor -#endif +let BogusToken = "pk.feedCafeDadeDeadBeef-BadeBede.FadeCafeDadeDeed-BadeBede" class MapboxStaticTests: XCTestCase { - let mapIdentifiers = ["mapbox.mapbox-streets-v6"] - let accessToken = "pk.feedCafeDadeDeadBeef-BadeBede.FadeCafeDadeDeed-BadeBede" - let serviceHost = "api.mapbox.com" - override func setUp() { super.setUp() - OHHTTPStubs.removeAllStubs() // Make sure tests run in all time zones NSTimeZone.default = TimeZone(secondsFromGMT: 0)! } - - fileprivate func parseQueryString(_ request: URLRequest) -> Dictionary { - var result = Dictionary() - let pairs = request.url!.query!.components(separatedBy: "&") - for pair in pairs { - let parts = pair.components(separatedBy: "=") - result[parts[0]] = parts[1] - } - return result - } - - func testBasicMap() { - // passed args - let mapIDExp = expectation(description: "mapID should be passed") - let sizeExp = expectation(description: "size should be passed") - let accessTokenExp = expectation(description: "access token should be passed") - - // implicit args - let versionExp = expectation(description: "API version should be v4") - let formatExp = expectation(description: "format should default to PNG") - let retinaExp = expectation(description: "retina should default to disabled") - let overlaysExp = expectation(description: "overlays should default to empty") - let autoFitExp = expectation(description: "auto-fit should default to enabled") - - let options = SnapshotOptions(mapIdentifiers: mapIdentifiers, size: CGSize(width: 200, height: 200)) - - let scale: CGFloat - #if os(OSX) - scale = NSScreen.main()?.backingScaleFactor ?? 1 - #else - scale = UIScreen.main.scale - #endif - - stub(condition: isHost(serviceHost)) { [unowned self] request in - if let p = request.url?.pathComponents { - if p[1] == "v4" { - versionExp.fulfill() - } - if p[2] == self.mapIdentifiers.joined(separator: ",") { - mapIDExp.fulfill() - } - if p[3] == "auto" { - overlaysExp.fulfill() - autoFitExp.fulfill() - } - if p[4].components(separatedBy: ".").first == "200x200" && scale == 1 { - retinaExp.fulfill() - sizeExp.fulfill() - } - else if p[4].components(separatedBy: ".").first == "200x200@2x" && scale > 1 { - retinaExp.fulfill() - sizeExp.fulfill() - } - if p[4].components(separatedBy: ".").last == "png" { - formatExp.fulfill() - } - } - - let query = self.parseQueryString(request) - if query["access_token"] == self.accessToken { - accessTokenExp.fulfill() - } - if query.keys.contains("access_token") && query.count == 1 { - } - - return OHHTTPStubsResponse() - } - - _ = Snapshot(options: options, accessToken: accessToken).image - - waitForExpectations(timeout: 1, handler: nil) - } - - func testCenter() { - let center = CLLocationCoordinate2D(latitude: 5.971389, longitude: 116.095278) - - let centerExp = expectation(description: "center should get passed intact") - - let options = SnapshotOptions( - mapIdentifiers: mapIdentifiers, - centerCoordinate: center, - zoomLevel: 0, - size: CGSize(width: 200, height: 200)) - - stub(condition: isHost(serviceHost)) { request in - if let p = request.url?.pathComponents { - let n = p[3].components(separatedBy: ",") - if n[0] == String(center.longitude) && n[1] == String(center.latitude) { - centerExp.fulfill() - } - } - - return OHHTTPStubsResponse() - } - - _ = Snapshot(options: options, accessToken: accessToken).image - - waitForExpectations(timeout: 1, handler: nil) - } - - func testZoom() { - let zoom = 6 - - let zoomExp = expectation(description: "zoom should get passed intact") - - let options = SnapshotOptions( - mapIdentifiers: mapIdentifiers, - centerCoordinate: CLLocationCoordinate2D(latitude: 0, longitude: 0), - zoomLevel: zoom, - size: CGSize(width: 300, height: 300)) - - stub(condition: isHost(serviceHost)) { request in - if let p = request.url?.pathComponents { - let n = p[3].components(separatedBy: ",") - if n[2] == String(zoom) { - zoomExp.fulfill() - } - } - - return OHHTTPStubsResponse() - } - - _ = Snapshot(options: options, accessToken: accessToken).image - - waitForExpectations(timeout: 1, handler: nil) - } - - func testSize() { - let min: UInt32 = 1 - let max: UInt32 = 1280 - - let width = arc4random_uniform(max - min) + min - let height = arc4random_uniform(max - min) + min - - let sizeExp = expectation(description: "size should pass intact for non-retina") - - let options = SnapshotOptions( - mapIdentifiers: mapIdentifiers, - size: CGSize(width: CGFloat(width), height: CGFloat(height))) - options.scale = 1 - - stub(condition: isHost(serviceHost)) { request in - if let p = request.url?.pathComponents, - let f = p.last, - let s = f.components(separatedBy: ".").first?.components(separatedBy: "x"), s[0] == String(width) && s[1] == String(height) { - sizeExp.fulfill() - } - - return OHHTTPStubsResponse() - } - - _ = Snapshot(options: options, accessToken: accessToken).image - - waitForExpectations(timeout: 1, handler: nil) - } - - func testFormats() { - var expectations = [XCTestExpectation]() - var optionses = [SnapshotOptions]() - - let allFormats: [SnapshotOptions.Format] = [ - .png, .png32, .png64, .png128, .png256, - .jpeg, .jpeg70, .jpeg80, .jpeg90, - ] - - for format in allFormats { - let exp = expectation(description: "\(format.rawValue) extension should be requested") - expectations.append(exp) - - let pathExtension: String - switch format { - case .png: - pathExtension = "png" - case .png32: - pathExtension = "png32" - case .png64: - pathExtension = "png64" - case .png128: - pathExtension = "png128" - case .png256: - pathExtension = "png256" - case .jpeg: - pathExtension = "jpg" - case .jpeg70: - pathExtension = "jpg70" - case .jpeg80: - pathExtension = "jpg80" - case .jpeg90: - pathExtension = "jpg90" - // If you get a “Switch must be exhaustive, consider adding a default clause” error here, allFormats is also missing a format. - } - stub(condition: isExtension(pathExtension)) { request in - exp.fulfill() - return OHHTTPStubsResponse() - } - - let options = SnapshotOptions( - mapIdentifiers: mapIdentifiers, - size: CGSize(width: 200, height: 200)) - options.format = format - optionses.append(options) - } - - for options in optionses { - _ = Snapshot(options: options, accessToken: accessToken).image - } - - waitForExpectations(timeout: 1, handler: nil) - } - - func testRetina() { - let retinaExp = expectation(description: "retina should request @2x asset") - - let options = SnapshotOptions( - mapIdentifiers: mapIdentifiers, - size: CGSize(width: 200, height: 200)) - options.scale = 2 - - stub(condition: isHost(serviceHost)) { request in - if let p = request.url?.pathComponents, - let f = p.last, - let e = f.components(separatedBy: "@").last, - let s = e.components(separatedBy: ".").first, s == "2x" { - retinaExp.fulfill() - } - - return OHHTTPStubsResponse() - } - - _ = Snapshot(options: options, accessToken: accessToken).image - - waitForExpectations(timeout: 1, handler: nil) - } - - func testOverlayBuiltinMarker() { - let lat = 45.52 - let lon = -122.681944 - let size = "m" - let label = "cafe" - let color = Color.brown - let colorRaw = "996633" - - let markerExp = expectation(description: "builtin marker argument should format Maki request properly") - - let markerOverlay = Marker( - coordinate: CLLocationCoordinate2D(latitude: lat, longitude: lon), - size: .medium, - iconName: "cafe") - markerOverlay.color = color - - let options = SnapshotOptions( - mapIdentifiers: mapIdentifiers, - size: CGSize(width: 200, height: 200)) - options.overlays = [markerOverlay] - - stub(condition: isHost(serviceHost)) { request in - if let p = request.url?.pathComponents, p[3] == "pin-" + size + "-" + label + - "+" + colorRaw + "(\(lon),\(lat))" { - markerExp.fulfill() - } - - return OHHTTPStubsResponse() - } - - _ = Snapshot(options: options, accessToken: accessToken).image - - waitForExpectations(timeout: 1, handler: nil) - } - - func testOverlayCustomMarker() { - let coordinate = CLLocationCoordinate2D(latitude: 45.522, longitude: -122.69) - let markerURL = URL(string: "https://mapbox.com/guides/img/rocket.png")! - let encodedMarker = "https:%2F%2Fmapbox.com%2Fguides%2Fimg%2Frocket.png" - - let markerExp = expectation(description: "custom marker argument should properly encode request") - - let customMarker = CustomMarker( - coordinate: coordinate, - url: markerURL) - - let options = SnapshotOptions( - mapIdentifiers: mapIdentifiers, - size: CGSize(width: 200, height: 200)) - options.overlays = [customMarker] - - stub(condition: isHost(serviceHost)) { request in - // We need to examine the URL string here manually since URL.pathComponents - // decodes the percent escaping, which does us no good. - if let requestString = request.url?.absoluteString { - let m = requestString.components(separatedBy: "/") - if m[5] == "url-\(encodedMarker)(\(coordinate.longitude),\(coordinate.latitude))" { - markerExp.fulfill() - } - } - - return OHHTTPStubsResponse() - } - - _ = Snapshot(options: options, accessToken: accessToken).image - - waitForExpectations(timeout: 1, handler: nil) - } - - func testOverlayGeoJSON() { - let geojsonURL = URL(string: "http://git.io/vCv9U")! - let encodedGeoJSON = "geojson(%7B%0A%20%20%22type%22:%20%22FeatureCollection%22,%0A%20%20%22features%22:%20%5B%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%22type%22:%20%22Feature%22,%0A%20%20%20%20%20%20%22properties%22:%20%7B%0A%20%20%20%20%20%20%20%20%22stroke%22:%20%22%2300f%22,%0A%20%20%20%20%20%20%20%20%22stroke-width%22:%203,%0A%20%20%20%20%20%20%20%20%22stroke-opacity%22:%201%0A%20%20%20%20%20%20%7D,%0A%20%20%20%20%20%20%22geometry%22:%20%7B%0A%20%20%20%20%20%20%20%20%22type%22:%20%22LineString%22,%0A%20%20%20%20%20%20%20%20%22coordinates%22:%20%5B%0A%20%20%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20-122.69784450531006,%0A%20%20%20%20%20%20%20%20%20%20%20%2045.51863175803531%0A%20%20%20%20%20%20%20%20%20%20%5D,%0A%20%20%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20-122.69091367721559,%0A%20%20%20%20%20%20%20%20%20%20%20%2045.52165369248977%0A%20%20%20%20%20%20%20%20%20%20%5D,%0A%20%20%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20-122.68630027770996,%0A%20%20%20%20%20%20%20%20%20%20%20%2045.518917420477024%0A%20%20%20%20%20%20%20%20%20%20%5D,%0A%20%20%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20-122.68509864807127,%0A%20%20%20%20%20%20%20%20%20%20%20%2045.51631633525551%0A%20%20%20%20%20%20%20%20%20%20%5D,%0A%20%20%20%20%20%20%20%20%20%20%5B%0A%20%20%20%20%20%20%20%20%20%20%20%20-122.68233060836793,%0A%20%20%20%20%20%20%20%20%20%20%20%2045.51950377568216%0A%20%20%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%20%20%5D%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%20%20%5D%0A%7D)" - - let geojsonExp = expectation(description: "GeoJSON argument should properly encode request") - - stub(condition: isHost(geojsonURL.host!)) { request in - return fixture(filePath: Bundle(for: type(of: self)).path(forResource: "polyline", ofType: "geojson")!, - headers: nil) - } - - let geojsonString = try! String(contentsOf: geojsonURL, encoding: .utf8) - let geojsonOverlay = GeoJSON(objectString: geojsonString) - - let options = SnapshotOptions( - mapIdentifiers: mapIdentifiers, - size: CGSize(width: 200, height: 200)) - options.overlays = [geojsonOverlay] - - stub(condition: isHost(serviceHost)) { request in - // We need to examine the URL string here manually since URL.pathComponents - // decodes the percent escaping, which does us no good. - if let requestString = request.url?.absoluteString { - let m = requestString.components(separatedBy: "/") - if m[5] == encodedGeoJSON { - geojsonExp.fulfill() - } - } - - return OHHTTPStubsResponse() - } - - _ = Snapshot(options: options, accessToken: accessToken).image - - waitForExpectations(timeout: 1, handler: nil) - } - - func testOverlayPath() { - let strokeWidth = 2 - let strokeColor = Color.black - let strokeColorRaw = "000000" - let strokeOpacity = 0.75 - let fillColor = Color.red - let fillColorRaw = "ff0000" - let fillOpacity = 0.25 - let encodedPolyline = "(upztG%60jxkVn@al@bo@nFWzuAaTcAyZen@)" - - let path = Path( - coordinates: [ - 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 - ) - ]) - path.strokeWidth = strokeWidth - path.strokeColor = strokeColor - path.strokeOpacity = strokeOpacity - path.fillColor = fillColor - path.fillOpacity = fillOpacity - - let pathExp = expectation(description: "raw path argument should properly encode request") - - let options = SnapshotOptions( - mapIdentifiers: mapIdentifiers, - size: CGSize(width: 200, height: 200)) - options.overlays = [path] - - stub(condition: isHost(serviceHost)) { request in - // We need to examine the URL string here manually since URL.pathComponents - // decodes the percent escaping, which does us no good. - if let requestString = request.url?.absoluteString { - let p = requestString.components(separatedBy: "/") - if p[5] == "path-\(strokeWidth)+\(strokeColorRaw)-\(strokeOpacity)+" + - "\(fillColorRaw)-\(fillOpacity)" + encodedPolyline { - pathExp.fulfill() - } - } - - return OHHTTPStubsResponse() - } - - _ = Snapshot(options: options, accessToken: accessToken).image - - waitForExpectations(timeout: 1, handler: nil) - } - - func testAutoFit() { - let autoFitExp = expectation(description: "auto-fit should pass correct argument") - - let markerOverlay = Marker( - coordinate: CLLocationCoordinate2D(latitude: 45.52, longitude: -122.681944), - size: .medium, - iconName: "cafe") - markerOverlay.color = .brown - - let options = SnapshotOptions( - mapIdentifiers: mapIdentifiers, - size: CGSize(width: 200, height: 200)) - options.overlays = [markerOverlay] - - stub(condition: isHost(serviceHost)) { request in - if let p = request.url?.pathComponents, p[4] == "auto" { - autoFitExp.fulfill() - } - - return OHHTTPStubsResponse() - } - - _ = Snapshot(options: options, accessToken: accessToken).image - - waitForExpectations(timeout: 1, handler: nil) - } - func testStandaloneMarker() { - let size = "m" - let label = "cafe" - let color = Color.brown - let colorRaw = "996633" - - let markerExp = expectation(description: "builtin marker argument should format Maki request properly") - - let options = MarkerOptions( - size: .medium, - iconName: "cafe") - options.color = color - - stub(condition: isHost(serviceHost)) { request in - let scaleSuffix = options.scale == 1 ? "" : "@2x" - if let p = request.url?.pathComponents, p[3] == "pin-\(size)-\(label)+\(colorRaw)\(scaleSuffix).png" { - markerExp.fulfill() - } - - return OHHTTPStubsResponse() - } - - _ = Snapshot(options: options, accessToken: accessToken).image - - waitForExpectations(timeout: 1, handler: nil) + func testConfiguration() { + let styleURL = URL(string: "mapbox://styles/mapbox/streets-v9")! + let camera = SnapshotCamera(lookingAtCenter: CLLocationCoordinate2D(latitude: 0, longitude: 0), zoomLevel: 0) + let options = SnapshotOptions(styleURL: styleURL, camera: camera, size: CGSize(width: 200, height: 200)) + let snapshot = Snapshot(options: options, accessToken: BogusToken) + + XCTAssertEqual(snapshot.accessToken, BogusToken) + XCTAssertEqual(snapshot.apiEndpoint.absoluteString, "https://api.mapbox.com") } func testRateLimitErrorParsing() { diff --git a/MapboxStaticTests/SnapshotTests.swift b/MapboxStaticTests/SnapshotTests.swift new file mode 100644 index 0000000..5838c4b --- /dev/null +++ b/MapboxStaticTests/SnapshotTests.swift @@ -0,0 +1,168 @@ +import Foundation +import XCTest +import OHHTTPStubs +import CoreLocation +@testable import MapboxStatic + +class SnapshotTests: XCTestCase { + override func tearDown() { + OHHTTPStubs.removeAllStubs() + super.tearDown() + } + + let styleURL = URL(string: "mapbox://styles/mapbox/streets-v9")! + + func testConvertingAltitudes() { + let tallSize = CGSize(width: 600, height: 1200) + let midSize = CGSize(width: 600, height: 800) + let shortSize = CGSize(width: 600, height: 400) + + XCTAssertLessThan(SnapshotCamera.zoomLevelForAltitude(1800, pitch: 0, latitude: 0, size: midSize), SnapshotCamera.zoomLevelForAltitude(1800, pitch: 0, latitude: 0, size: tallSize)) + XCTAssertGreaterThan(SnapshotCamera.zoomLevelForAltitude(1800, pitch: 0, latitude: 0, size: midSize), SnapshotCamera.zoomLevelForAltitude(1800, pitch: 0, latitude: 0, size: shortSize)) + } + + func testBasicMap() { + let options = SnapshotOptions(styleURL: styleURL, size: CGSize(width: 200, height: 200)) + options.scale = 1 + + stub(condition: isHost("api.mapbox.com") + && isPath("/styles/v1/mapbox/streets-v9/static/auto/200x200") + && containsQueryParams(["access_token": BogusToken])) { request in + let path = Bundle(for: type(of: self)).path(forResource: "basic-gl", ofType: "png")! + return fixture(filePath: path, headers: ["Content-Type": "image/png"]) + } + + stub(condition: isHost("api.mapbox.com") + && isPath("/styles/v1/mapbox/streets-v9/static/auto/200x200@2x") + && containsQueryParams(["access_token": BogusToken])) { request in + let path = Bundle(for: type(of: self)).path(forResource: "basic-gl@2x", ofType: "png")! + return fixture(filePath: path, headers: ["Content-Type": "image/png"]) + } + + let loDPIImage = Snapshot(options: options, accessToken: BogusToken).image + XCTAssertNotNil(loDPIImage) + XCTAssertEqual(loDPIImage?.size.width, 200) + XCTAssertEqual(loDPIImage?.size.height, 200) + + options.scale = 2 + let hiDPIImage = Snapshot(options: options, accessToken: BogusToken).image + XCTAssertNotNil(hiDPIImage) + XCTAssertEqual(hiDPIImage?.size.width, 400, "LoDPI image should be half the width of HiDPI image.") + XCTAssertEqual(hiDPIImage?.size.height, 400, "LoDPI image should be half the height of HiDPI image.") + } + + func testCenter() { + let camera = SnapshotCamera(lookingAtCenter: CLLocationCoordinate2D(latitude: 5.971389, longitude: 116.095278), zoomLevel: 0) + let options = SnapshotOptions(styleURL: styleURL, camera: camera, size: CGSize(width: 200, height: 200)) + options.scale = 1 + + stub(condition: isHost("api.mapbox.com") + && isPath("/styles/v1/mapbox/streets-v9/static/116.095278,5.971389,0.0/200x200") + && containsQueryParams(["access_token": BogusToken])) { request in + let path = Bundle(for: type(of: self)).path(forResource: "center-gl", ofType: "png")! + return fixture(filePath: path, headers: ["Content-Type": "image/png"]) + } + + XCTAssertNotNil(Snapshot(options: options, accessToken: BogusToken).image) + } + + func testZoom() { + let camera = SnapshotCamera(lookingAtCenter: CLLocationCoordinate2D(latitude: 0, longitude: 0), zoomLevel: 6) + let options = SnapshotOptions(styleURL: styleURL, camera: camera, size: CGSize(width: 300, height: 300)) + options.scale = 1 + + stub(condition: isHost("api.mapbox.com") + && isPath("/styles/v1/mapbox/streets-v9/static/0.0,0.0,6.0/300x300") + && containsQueryParams(["access_token": BogusToken])) { request in + let path = Bundle(for: type(of: self)).path(forResource: "zoom-gl", ofType: "png")! + return fixture(filePath: path, headers: ["Content-Type": "image/png"]) + } + + XCTAssertNotNil(Snapshot(options: options, accessToken: BogusToken).image) + } + + func testRotate() { + let camera = SnapshotCamera(lookingAtCenter: CLLocationCoordinate2D(latitude: 0, longitude: 0), zoomLevel: 0) + camera.heading = 45 + let options = SnapshotOptions(styleURL: styleURL, camera: camera, size: CGSize(width: 300, height: 300)) + options.scale = 1 + + stub(condition: isHost("api.mapbox.com") + && isPath("/styles/v1/mapbox/streets-v9/static/0.0,0.0,0.0,45.0/300x300") + && containsQueryParams(["access_token": BogusToken])) { request in + let path = Bundle(for: type(of: self)).path(forResource: "rotate", ofType: "png")! + return fixture(filePath: path, headers: ["Content-Type": "image/png"]) + } + + XCTAssertNotNil(Snapshot(options: options, accessToken: BogusToken).image) + } + + func testTilt() { + let camera = SnapshotCamera(lookingAtCenter: CLLocationCoordinate2D(latitude: 0, longitude: 0), zoomLevel: 0) + camera.pitch = 60 + let options = SnapshotOptions(styleURL: styleURL, camera: camera, size: CGSize(width: 300, height: 300)) + options.scale = 1 + + stub(condition: isHost("api.mapbox.com") + && isPath("/styles/v1/mapbox/streets-v9/static/0.0,0.0,0.0,0.0,60.0/300x300") + && containsQueryParams(["access_token": BogusToken])) { request in + let path = Bundle(for: type(of: self)).path(forResource: "tilt", ofType: "png")! + return fixture(filePath: path, headers: ["Content-Type": "image/png"]) + } + + XCTAssertNotNil(Snapshot(options: options, accessToken: BogusToken).image) + } + + func testSize() { + let min: UInt32 = 1 + let max: UInt32 = 1280 + + let width = arc4random_uniform(max - min) + min + let height = arc4random_uniform(max - min) + min + + let options = SnapshotOptions( + styleURL: styleURL, + size: CGSize(width: CGFloat(width), height: CGFloat(height))) + options.scale = 1 + + stub(condition: isHost("api.mapbox.com") + && isPath("/styles/v1/mapbox/streets-v9/static/auto/\(width)x\(height)") + && containsQueryParams(["access_token": BogusToken])) { request in + let path = Bundle(for: type(of: self)).path(forResource: "basic-gl", ofType: "png")! + return fixture(filePath: path, headers: ["Content-Type": "image/png"]) + } + + XCTAssertNotNil(Snapshot(options: options, accessToken: BogusToken).image) + // Can’t test the image size here because the fixture is fixed-size but the tests chooses the size at random. + } + + func testHidingLogo() { + let options = SnapshotOptions(styleURL: styleURL, size: CGSize(width: 200, height: 200)) + options.showsLogo = false + options.scale = 1 + + stub(condition: isHost("api.mapbox.com") + && isPath("/styles/v1/mapbox/streets-v9/static/auto/200x200") + && containsQueryParams(["access_token": BogusToken, "logo": "false"])) { request in + let path = Bundle(for: type(of: self)).path(forResource: "no-logo", ofType: "png")! + return fixture(filePath: path, headers: ["Content-Type": "image/png"]) + } + + XCTAssertNotNil(Snapshot(options: options, accessToken: BogusToken).image) + } + + func testHidingAttribution() { + let options = SnapshotOptions(styleURL: styleURL, size: CGSize(width: 200, height: 200)) + options.showsAttribution = false + options.scale = 1 + + stub(condition: isHost("api.mapbox.com") + && isPath("/styles/v1/mapbox/streets-v9/static/auto/200x200") + && containsQueryParams(["access_token": BogusToken, "attribution": "false"])) { request in + let path = Bundle(for: type(of: self)).path(forResource: "no-attribution", ofType: "png")! + return fixture(filePath: path, headers: ["Content-Type": "image/png"]) + } + + XCTAssertNotNil(Snapshot(options: options, accessToken: BogusToken).image) + } +} diff --git a/MapboxStaticTests/fixtures/basic-gl.png b/MapboxStaticTests/fixtures/basic-gl.png new file mode 100644 index 0000000..c97dd72 Binary files /dev/null and b/MapboxStaticTests/fixtures/basic-gl.png differ diff --git a/MapboxStaticTests/fixtures/basic-gl@2x.png b/MapboxStaticTests/fixtures/basic-gl@2x.png new file mode 100644 index 0000000..8e2f84a Binary files /dev/null and b/MapboxStaticTests/fixtures/basic-gl@2x.png differ diff --git a/MapboxStaticTests/fixtures/basic.png b/MapboxStaticTests/fixtures/basic.png new file mode 100644 index 0000000..cd424c2 Binary files /dev/null and b/MapboxStaticTests/fixtures/basic.png differ diff --git a/MapboxStaticTests/fixtures/basic@2x.png b/MapboxStaticTests/fixtures/basic@2x.png new file mode 100644 index 0000000..d43cd0e Binary files /dev/null and b/MapboxStaticTests/fixtures/basic@2x.png differ diff --git a/MapboxStaticTests/fixtures/cafe.png b/MapboxStaticTests/fixtures/cafe.png new file mode 100644 index 0000000..d01767d Binary files /dev/null and b/MapboxStaticTests/fixtures/cafe.png differ diff --git a/MapboxStaticTests/fixtures/center-gl.png b/MapboxStaticTests/fixtures/center-gl.png new file mode 100644 index 0000000..cb72728 Binary files /dev/null and b/MapboxStaticTests/fixtures/center-gl.png differ diff --git a/MapboxStaticTests/fixtures/center.png b/MapboxStaticTests/fixtures/center.png new file mode 100644 index 0000000..e3bded4 Binary files /dev/null and b/MapboxStaticTests/fixtures/center.png differ diff --git a/MapboxStaticTests/fixtures/format.jpg b/MapboxStaticTests/fixtures/format.jpg new file mode 100644 index 0000000..d50d21c Binary files /dev/null and b/MapboxStaticTests/fixtures/format.jpg differ diff --git a/MapboxStaticTests/fixtures/format.jpg70 b/MapboxStaticTests/fixtures/format.jpg70 new file mode 100644 index 0000000..6e53ef0 Binary files /dev/null and b/MapboxStaticTests/fixtures/format.jpg70 differ diff --git a/MapboxStaticTests/fixtures/format.jpg80 b/MapboxStaticTests/fixtures/format.jpg80 new file mode 100644 index 0000000..d50d21c Binary files /dev/null and b/MapboxStaticTests/fixtures/format.jpg80 differ diff --git a/MapboxStaticTests/fixtures/format.jpg90 b/MapboxStaticTests/fixtures/format.jpg90 new file mode 100644 index 0000000..c5e0a29 Binary files /dev/null and b/MapboxStaticTests/fixtures/format.jpg90 differ diff --git a/MapboxStaticTests/fixtures/format.png b/MapboxStaticTests/fixtures/format.png new file mode 100644 index 0000000..0b98ef7 Binary files /dev/null and b/MapboxStaticTests/fixtures/format.png differ diff --git a/MapboxStaticTests/fixtures/format.png128 b/MapboxStaticTests/fixtures/format.png128 new file mode 100644 index 0000000..6383086 Binary files /dev/null and b/MapboxStaticTests/fixtures/format.png128 differ diff --git a/MapboxStaticTests/fixtures/format.png256 b/MapboxStaticTests/fixtures/format.png256 new file mode 100644 index 0000000..0b98ef7 Binary files /dev/null and b/MapboxStaticTests/fixtures/format.png256 differ diff --git a/MapboxStaticTests/fixtures/format.png32 b/MapboxStaticTests/fixtures/format.png32 new file mode 100644 index 0000000..37454c8 Binary files /dev/null and b/MapboxStaticTests/fixtures/format.png32 differ diff --git a/MapboxStaticTests/fixtures/format.png64 b/MapboxStaticTests/fixtures/format.png64 new file mode 100644 index 0000000..0cd97b7 Binary files /dev/null and b/MapboxStaticTests/fixtures/format.png64 differ diff --git a/MapboxStaticTests/fixtures/geojson.png b/MapboxStaticTests/fixtures/geojson.png new file mode 100644 index 0000000..9f4b58b Binary files /dev/null and b/MapboxStaticTests/fixtures/geojson.png differ diff --git a/MapboxStaticTests/fixtures/marker.png b/MapboxStaticTests/fixtures/marker.png new file mode 100644 index 0000000..3666b8a Binary files /dev/null and b/MapboxStaticTests/fixtures/marker.png differ diff --git a/MapboxStaticTests/fixtures/no-attribution.png b/MapboxStaticTests/fixtures/no-attribution.png new file mode 100644 index 0000000..266c65a Binary files /dev/null and b/MapboxStaticTests/fixtures/no-attribution.png differ diff --git a/MapboxStaticTests/fixtures/no-logo.png b/MapboxStaticTests/fixtures/no-logo.png new file mode 100644 index 0000000..57e3523 Binary files /dev/null and b/MapboxStaticTests/fixtures/no-logo.png differ diff --git a/MapboxStaticTests/fixtures/path.png b/MapboxStaticTests/fixtures/path.png new file mode 100644 index 0000000..d4dd79e Binary files /dev/null and b/MapboxStaticTests/fixtures/path.png differ diff --git a/MapboxStaticTests/fixtures/rocket.png b/MapboxStaticTests/fixtures/rocket.png new file mode 100644 index 0000000..dd866b4 Binary files /dev/null and b/MapboxStaticTests/fixtures/rocket.png differ diff --git a/MapboxStaticTests/fixtures/rotate.png b/MapboxStaticTests/fixtures/rotate.png new file mode 100644 index 0000000..bbc5b43 Binary files /dev/null and b/MapboxStaticTests/fixtures/rotate.png differ diff --git a/MapboxStaticTests/fixtures/tilt.png b/MapboxStaticTests/fixtures/tilt.png new file mode 100644 index 0000000..df3f5c9 Binary files /dev/null and b/MapboxStaticTests/fixtures/tilt.png differ diff --git a/MapboxStaticTests/fixtures/zoom-gl.png b/MapboxStaticTests/fixtures/zoom-gl.png new file mode 100644 index 0000000..f0c435d Binary files /dev/null and b/MapboxStaticTests/fixtures/zoom-gl.png differ diff --git a/MapboxStaticTests/fixtures/zoom.png b/MapboxStaticTests/fixtures/zoom.png new file mode 100644 index 0000000..0bd249f Binary files /dev/null and b/MapboxStaticTests/fixtures/zoom.png differ diff --git a/README.md b/README.md index a0a0032..6ed47c2 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,9 @@ [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)     [![CocoaPods](https://img.shields.io/cocoapods/v/MapboxStatic.swift.svg)](http://cocoadocs.org/docsets/MapboxStatic.swift/) -MapboxStatic.swift makes it easy to connect your iOS, macOS, tvOS, or watchOS application to the [classic Mapbox Static API](https://www.mapbox.com/api-documentation/#static-classic). Quickly generate a static map image with overlays by fetching it synchronously or asynchronously over the Web using first-class Swift or Objective-C data types. +MapboxStatic.swift makes it easy to connect your iOS, macOS, tvOS, or watchOS application to the [Mapbox Static API](https://www.mapbox.com/api-documentation/#static) or the [classic Mapbox Static API](https://www.mapbox.com/api-documentation/#static-classic). Quickly generate a map snapshot – a static map image with overlays – by fetching it synchronously or asynchronously over the Web using first-class Swift or Objective-C 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. They are created in one HTTP request, so overlays are all added *server-side*. +A snapshot is a flattened PNG or JPEG image, ideal for use in a table or image view, user notification, sharing service, printed document, or anyplace else you’d like a quick, custom map without the overhead of an interactive view. A static map is created in a single HTTP request. Overlays are added server-side. MapboxStatic.swift pairs well with [MapboxDirections.swift](https://github.com/mapbox/MapboxDirections.swift), [MapboxGeocoder.swift](https://github.com/mapbox/MapboxGeocoder.swift), and the [Mapbox iOS SDK](https://www.mapbox.com/ios-sdk/) or [macOS SDK](https://mapbox.github.io/mapbox-gl-native/macos/). @@ -18,13 +18,13 @@ MapboxStatic.swift pairs well with [MapboxDirections.swift](https://github.com/m Specify the following dependency in your [Carthage](https://github.com/Carthage/Carthage/) Cartfile: ```sh -github "mapbox/MapboxStatic.swift" "~> 0.7" +github "mapbox/MapboxStatic.swift" "master" ``` Or in your [CocoaPods](http://cocoapods.org/) Podfile: ```podspec -pod 'MapboxStatic.swift', '~> 0.7' +pod 'MapboxStatic.swift', :git => 'https://github.com/mapbox/MapboxStatic.swift.git', :branch => 'master' ``` Then `import MapboxStatic` or `@import MapboxStatic;`. @@ -35,20 +35,28 @@ This repository includes an example iOS application written in Swift, as well as ## Usage -You will need a [map ID](https://www.mapbox.com/help/define-map-id/) from a [custom map style](https://www.mapbox.com/help/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. +You can generate a snapshot from either a Mapbox-hosted [style](https://www.mapbox.com/help/define-style/) or a raw [tile set](https://www.mapbox.com/help/define-tileset/). Using a style gives you more visual options like rotation, while using a tile set gives you a choice of image formats. If you’re working with vector data, you’ll want to use a style; if you’re working with a single raster imagery source, you may want to use a tile set. + +To generate a snapshot from a style, you’ll need its [style URL](https://www.mapbox.com/help/define-style-url/). You can either choose a [Mapbox-designed style](https://www.mapbox.com/api-documentation/#styles) or design one yourself in [Mapbox Studio](https://www.mapbox.com/studio/styles/). You can use the same style in the Mapbox iOS SDK or Mapbox macOS SDK. + +To generate a snapshot from a tile set, you’ll need a [map ID](https://www.mapbox.com/help/define-map-id/). You can either choose a [Mapbox-maintained tile set](https://www.mapbox.com/api-documentation/#maps) or upload your own to [Mapbox Studio](https://www.mapbox.com/studio/tilesets/). + +You’ll also need an [access token](https://www.mapbox.com/help/define-access-token/) with the `styles:tiles` scope enabled in order to use this library. You can specify your access token inline or by setting the `MGLMapboxAccessToken` key in your application’s Info.plist file. ### Basics -The main static map class is `Snapshot` in Swift or `MBSnapshot` in Objective-C. To create a basic snapshot, create a `SnapshotOptions` or `MBSnapshotOptions` object, specifying the center coordinates, [zoom level](https://www.mapbox.com/help/how-web-maps-work/#tiles-and-zoom-levels), and point size: +The main static map class is `Snapshot` in Swift or `MBSnapshot` in Objective-C. To create a basic snapshot, create a `SnapshotOptions` object, specifying snapshot camera (viewpoint) and point size: ```swift // main.swift import MapboxStatic +let camera = SnapshotCamera( + lookingAtCenter: CLLocationCoordinate2D(latitude: 45.52, longitude: -122.681944), + zoomLevel: 12) let options = SnapshotOptions( - mapIdentifiers: ["<#your map ID#>"], - centerCoordinate: CLLocationCoordinate2D(latitude: 45.52, longitude: -122.681944), - zoomLevel: 13, + styleURL: URL(string: "<#your mapbox: style URL#>")!, + camera: camera, size: CGSize(width: 200, height: 200)) let snapshot = Snapshot( options: options, @@ -59,11 +67,14 @@ let snapshot = Snapshot( // main.m @import MapboxStatic; -MBSnapshotOptions *options = [[MBSnapshotOptions alloc] initWithMapIdentifiers:@[@"<#your map ID#>"] - centerCoordinate:CLLocationCoordinate2DMake(45.52, -122.681944) - zoomLevel:13 - size:CGSizeMake(200, 200)]; -MBSnapshot *snapshot = [[MBSnapshot alloc] initWithOptions:options accessToken:@"<#your access token#>"]; +NSURL *styleURL = [NSURL URLWithString:@"<#your mapbox: style URL#>"]; +MBSnapshotCamera *camera = [MBSnapshotCamera cameraLookingAtCenterCoordinate:CLLocationCoordinate2DMake(45.52, -122.681944) + zoomLevel:12]; +MBSnapshotOptions *options = [[MBSnapshotOptions alloc] initWithStyleURL:styleURL + camera:camera + size:CGSizeMake(200, 200)]; +MBSnapshot *snapshot = [[MBSnapshot alloc] initWithOptions:options + accessToken:@"<#your access token#>"]; ``` Then, you can either retrieve an image synchronously (blocking the calling thread): @@ -78,7 +89,7 @@ imageView.image = snapshot.image imageView.image = snapshot.image; ``` -![](./screenshots/map.png) + Or you can pass a completion handler to update the UI thread after the image is retrieved: @@ -108,11 +119,36 @@ let imageURL = snapshot.url NSURL *imageURL = snapshot.url; ``` +To create a basic classic snapshot, create a `ClassicSnapshotOptions` or `MBClassicSnapshotOptions` object, specifying the center coordinates, [zoom level](https://www.mapbox.com/help/how-web-maps-work/#tiles-and-zoom-levels), and size in points: + +```swift +// main.swift +let options = ClassicSnapshotOptions( + mapIdentifiers: ["<#your map ID#>"], + centerCoordinate: CLLocationCoordinate2D(latitude: 45.52, longitude: -122.681944), + zoomLevel: 13, + size: CGSize(width: 300, height: 200)) +let snapshot = Snapshot( + options: options, + accessToken: "<#your access token#>") +imageView.image = snapshot.image +``` + +```objc +// main.m +MBSnapshotOptions *options = [[MBClassicSnapshotOptions alloc] initWithMapIdentifiers:@[@"<#your map ID#>"] + centerCoordinate:CLLocationCoordinate2DMake(45.52, -122.681944) + zoomLevel:13 + size:CGSizeMake(200, 200)]; +MBSnapshot *snapshot = [[MBSnapshot alloc] initWithOptions:options accessToken:@"<#your access token#>"]; +imageView.image = snapshot.image; +``` + ### Overlays -Overlays are where things get interesting! You can add [Maki markers](https://www.mapbox.com/maki/), custom marker imagery, GeoJSON geometries, and even paths made of bare coordinates. +Overlays are where things get interesting! You can add [Maki markers](https://www.mapbox.com/maki-icons/), custom marker imagery, GeoJSON geometries, and even paths made of bare coordinates. -You add overlays to the `overlays` field in the `SnapshotOptions` or `MBSnapshotOptions` object. Here are some versions of our snapshot with various overlays added. +You add overlays to the `overlays` field in the `SnapshotOptions` or `MBSnapshotOptions` object. Here are some versions of our snapshot with various overlays added. #### Marker @@ -138,7 +174,7 @@ MBMarker *markerOverlay = [[MBMarker alloc] initWithCoordinate:CLLocationCoordin #endif ``` -![](./screenshots/marker.png) + #### Custom marker @@ -152,35 +188,36 @@ let customMarker = CustomMarker( ```objc // main.m +NSURL *url = [NSURL URLWithString:@"https://www.mapbox.com/help/img/screenshots/rocket.png"]; MBCustomMarker *customMarker = [[MBCustomMarker alloc] initWithCoordinate:CLLocationCoordinate2DMake(45.522, -122.69) - url:[NSURL URLWithString:@"https://www.mapbox.com/help/img/screenshots/rocket.png"]]; + url:url]; ``` -![](./screenshots/custom.png) + #### GeoJSON ```swift // main.swift -let geojsonOverlay: GeoJSON +let geoJSONOverlay: GeoJSON do { - let geojsonURL = URL(string: "http://git.io/vCv9U")! - let geojsonString = try String(contentsOf: geojsonURL, encoding: .utf8) - geojsonOverlay = GeoJSON(objectString: geojsonString) + let geoJSONURL = URL(string: "http://git.io/vCv9U")! + let geoJSONString = try String(contentsOf: geoJSONURL, encoding: .utf8) + geoJSONOverlay = GeoJSON(objectString: geoJSONString) } ``` ```objc // main.m -NSURL *geojsonURL = [NSURL URLWithString:@"http://git.io/vCv9U"]; -NSString *geojsonString = [[NSString alloc] initWithContentsOfURL:geojsonURL +NSURL *geoJSONURL = [NSURL URLWithString:@"http://git.io/vCv9U"]; +NSString *geoJSONString = [[NSString alloc] initWithContentsOfURL:geoJSONURL encoding:NSUTF8StringEncoding error:NULL]; -MBGeoJSON *geojsonOverlay = [[MBGeoJSON alloc] initWithObjectString:geojsonString]; +MBGeoJSON *geoJSONOverlay = [[MBGeoJSON alloc] initWithObjectString:geoJSONString]; ``` -![](./screenshots/geojson.png) + #### Path @@ -237,30 +274,68 @@ path.strokeWidth = 2; path.fillOpacity = 0.25; ``` -![](./screenshots/path.png) + ### Other options +#### Rotation and tilt + +To rotate and tilt a snapshot, set its camera’s heading and pitch: + +```swift +// main.swift +let camera = SnapshotCamera( + lookingAtCenter: CLLocationCoordinate2D(latitude: 45.52, longitude: -122.681944), + zoomLevel: 13) +camera.heading = 45 +camera.pitch = 60 +let options = SnapshotOptions( + styleURL: URL(string: "<#your mapbox: style URL#>")!, + camera: camera, + size: CGSize(width: 200, height: 200)) +let snapshot = Snapshot( + options: options, + accessToken: "<#your access token#>") +``` + +```objc +// main.m +NSURL *styleURL = [NSURL URLWithString:@"<#your mapbox: style URL#>"]; +MBSnapshotCamera *camera = [MBSnapshotCamera cameraLookingAtCenterCoordinate:CLLocationCoordinate2DMake(45.52, -122.681944) + zoomLevel:13]; +camera.heading = 45; +camera.pitch = 60; +MBSnapshotOptions *options = [[MBSnapshotOptions alloc] initWithStyleURL:styleURL + camera:camera + size:CGSizeMake(200, 200)]; +MBSnapshot *snapshot = [[MBSnapshot alloc] initWithOptions:options + accessToken:@"<#your access token#>"]; +``` + + + #### Auto-fitting features -If you’re adding overlays to your map, leave out the center coordinate and zoom level to automatically calculate the center and zoom level that best shows them off. +If you’re adding overlays to a snapshot, leave out the center coordinate and zoom level to automatically calculate the center and zoom level that best shows them off. ```swift // main.swift -var options = SnapshotOptions( - mapIdentifiers: ["<#your map ID#>"], + +let options = SnapshotOptions( + styleURL: URL(string: "<#your mapbox: style URL#>")!, size: CGSize(width: 500, height: 300)) options.overlays = [path, geojsonOverlay, markerOverlay, customMarker] ``` ```objc // main.m -MBSnapshotOptions *options = [[MBSnapshotOptions alloc] initWithMapIdentifiers:@[@"<#your map ID#>"] - size:CGSizeMake(500, 300)]; +NSURL *styleURL = [NSURL URLWithString:@"<#your mapbox: style URL#>"]; +MBSnapshotOptions *options = [[MBClassicSnapshotOptions alloc] initWithStyleURL:styleURL + size:CGSizeMake(500, 300)]; options.overlays = @[path, geojsonOverlay, markerOverlay, customMarker]; ``` -![](screenshots/autofit.png) + #### Standalone markers @@ -292,7 +367,7 @@ MBSnapshot *snapshot = [[MBSnapshot alloc] initWithOptions:options #### 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). +When creating a classic snapshot, 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). #### Attribution diff --git a/iOS.playground/Contents.swift b/iOS.playground/Contents.swift index 397829b..4053276 100644 --- a/iOS.playground/Contents.swift +++ b/iOS.playground/Contents.swift @@ -5,30 +5,37 @@ import MapboxStatic /*: # MapboxStatic.swift - MapboxStatic.swift makes it easy to connect your iOS application to the [classic Mapbox Static API](https://www.mapbox.com/api-documentation/#static-classic). Quickly generate a static map image with overlays, asynchronous imagery fetching, and first-class Swift data types. + MapboxStatic.swift makes it easy to connect your iOS Cocoa Touch application to the [Mapbox Static API](https://www.mapbox.com/api-documentation/#static) or the [classic Mapbox Static API](https://www.mapbox.com/api-documentation/#static-classic). Quickly generate a map snapshot – a static map image with overlays – by fetching it synchronously or asynchronously over the Web using first-class Swift or Objective-C 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. They are created in one HTTP request, so overlays are all added *server-side*. + A snapshot is a flattened PNG or JPEG image, ideal for use in a table or image view, user notification, or anyplace else you’d like a quick, custom map without the overhead of an interactive view. A static map is created in a single HTTP request. Overlays are added server-side. ## Usage - You will need a [map ID](https://www.mapbox.com/help/define-map-id/) from a [custom map style](https://www.mapbox.com/help/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. + You can either generate a _snapshot_ from a Mapbox-hosted [style](https://www.mapbox.com/help/define-style/), or you can generate a _classic snapshot_ from a raw [tile set](https://www.mapbox.com/help/define-tileset/). Using a style gives you more visual options like rotation, while using a tile set gives you a choice of image formats. If you’re working with vector data, you’ll want to use a style; if you’re working with a single raster imagery source, you may want to use a tile set. - You can specify your access token inline or by setting the `MGLMapboxAccessToken` key in your application’s Info.plist file. + To generate a snapshot from a style, you’ll need its [style URL](https://www.mapbox.com/help/define-style-url/). You can either choose a [Mapbox-designed style](https://www.mapbox.com/api-documentation/#styles) or design one yourself in [Mapbox Studio](https://www.mapbox.com/studio/styles/). You can use the same style in the Mapbox iOS SDK. + + To generate a snapshot from a tile set, you’ll need a [map ID](https://www.mapbox.com/help/define-map-id/). You can either choose a [Mapbox-maintained tile set](https://www.mapbox.com/api-documentation/#maps) or upload your own to [Mapbox Studio](https://www.mapbox.com/studio/tilesets/). + + You’ll also need an [access token](https://www.mapbox.com/help/define-access-token/) with the `styles:tiles` scope enabled in order to use this library. You can specify your access token inline or by setting the `MGLMapboxAccessToken` key in your application’s Info.plist file. */ -let mapIdentifiers = ["justin.tm2-basemap"] -let accessToken = "pk.eyJ1IjoianVzdGluIiwiYSI6IlpDbUJLSUEifQ.4mG8vhelFMju6HpIY-Hi5A" +let styleURL = URL(string: "mapbox://styles/mapbox/streets-v9")! +let mapIdentifiers = ["mapbox.satellite"] +let accessToken = "pk.eyJ1IjoibWFwYm94IiwiYSI6ImNqMHFiNXN4ZDAxazMyd253cmt3a2hmN2cifQ.q0ntnAWEdwckfZnT0IEy5A" /*: ## Basics - The main static map class is `Snapshot`. To create a basic snapshot, create a `SnapshotOptions` object, specifying the center coordinates, [zoom level](https://www.mapbox.com/help/how-web-maps-work/#tiles-and-zoom-levels), and point size: + The main static map class is `Snapshot`. To create a basic snapshot, create a `SnapshotOptions` object, specifying snapshot camera (viewpoint) and point size: */ +var camera = SnapshotCamera( + lookingAtCenter: CLLocationCoordinate2D(latitude: 45.52, longitude: -122.681944), + zoomLevel: 13) var options = SnapshotOptions( - mapIdentifiers: mapIdentifiers, - centerCoordinate: CLLocationCoordinate2D(latitude: 45.52, longitude: -122.681944), - zoomLevel: 13, + styleURL: styleURL, + camera: camera, size: CGSize(width: 300, height: 200)) var snapshot = Snapshot( options: options, @@ -52,10 +59,24 @@ snapshot.image { (image, error) in */ snapshot.url +/*: + To create a basic classic snapshot, create a `ClassicSnapshotOptions` object instead of a `SnapshotOptions` object, specifying the center coordinates, [zoom level](https://www.mapbox.com/help/how-web-maps-work/#tiles-and-zoom-levels), and size in points: + */ + +let classicOptions = ClassicSnapshotOptions( + mapIdentifiers: mapIdentifiers, + centerCoordinate: CLLocationCoordinate2D(latitude: 45.52, longitude: -122.681944), + zoomLevel: 13, + size: CGSize(width: 300, height: 200)) +snapshot = Snapshot( + options: classicOptions, + accessToken: accessToken) +snapshot.image + /*: ## Overlays - Overlays are where things get interesting! You can add [Maki markers](https://www.mapbox.com/maki/), custom marker imagery, GeoJSON geometries, and even paths made of bare coordinates. + Overlays are where things get interesting! You can add [Maki markers](https://www.mapbox.com/maki-icons/), custom marker imagery, GeoJSON geometries, and even paths made of bare coordinates. You add overlays to the `overlays` field in the `SnapshotOptions` object. Here are some versions of our snapshot with various overlays added. @@ -87,13 +108,13 @@ snapshot.image /*: ### GeoJSON */ -let geojsonOverlay: GeoJSON +let geoJSONOverlay: GeoJSON do { - let geojsonURL = URL(string: "http://git.io/vCv9U")! - let geojsonString = try String(contentsOf: geojsonURL, encoding: .utf8) - geojsonOverlay = GeoJSON(objectString: geojsonString as String) + let geoJSONURL = URL(string: "http://git.io/vCv9U")! + let geoJSONString = try String(contentsOf: geoJSONURL, encoding: .utf8) + geoJSONOverlay = GeoJSON(objectString: geoJSONString as String) } -options.overlays = [geojsonOverlay] +options.overlays = [geoJSONOverlay] snapshot = Snapshot( options: options, accessToken: accessToken) @@ -136,11 +157,30 @@ snapshot.image /*: ## Other options + ### Rotation and tilt + + To rotate and tilt a snapshot, set its camera’s heading and pitch: + */ +camera.heading = 45 +camera.pitch = 60 +options = SnapshotOptions( + styleURL: styleURL, + camera: camera, + size: CGSize(width: 300, height: 200)) +snapshot = Snapshot( + options: options, + accessToken: accessToken) +snapshot.image + +/*: ### Auto-fitting features - If you’re adding overlays to your map, leave out the center coordinate and zoom level to automatically calculate the center and zoom level that best shows them off. + If you’re adding overlays to a snapshot, leave out the center coordinate and zoom level to automatically calculate the center and zoom level that best shows them off. */ -options.overlays = [path, geojsonOverlay, markerOverlay, customMarker] +options = SnapshotOptions( + styleURL: styleURL, + size: CGSize(width: 500, height: 300)) +options.overlays = [path, geoJSONOverlay, markerOverlay, customMarker] snapshot = Snapshot( options: options, accessToken: accessToken) @@ -163,9 +203,9 @@ snapshot.image /*: ### 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). + When creating a classic snapshot, 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). ### Attribution - Be sure to [attribute your map](https://www.mapbox.com/help/attribution/) properly in your app. You can also [find out more](https://www.mapbox.com/about/maps/) about where Mapbox’s map data comes from. + Be sure to [attribute your map](https://www.mapbox.com/help/attribution/) properly in your application. You can also [find out more](https://www.mapbox.com/about/maps/) about where Mapbox’s map data comes from. */ diff --git a/iOS.playground/timeline.xctimeline b/iOS.playground/timeline.xctimeline index 661c1be..fdadfdf 100644 --- a/iOS.playground/timeline.xctimeline +++ b/iOS.playground/timeline.xctimeline @@ -3,92 +3,157 @@ version = "3.0"> + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macOS.playground/Contents.swift b/macOS.playground/Contents.swift index 7912246..fa13de1 100644 --- a/macOS.playground/Contents.swift +++ b/macOS.playground/Contents.swift @@ -5,30 +5,37 @@ import MapboxStatic /*: # MapboxStatic.swift - MapboxStatic.swift makes it easy to connect your macOS Cocoa application to the [classic Mapbox Static API](https://www.mapbox.com/api-documentation/#static-classic). Quickly generate a static map image with overlays, asynchronous imagery fetching, and first-class Swift data types. + MapboxStatic.swift makes it easy to connect your macOS Cocoa application to the [Mapbox Static API](https://www.mapbox.com/api-documentation/#static) or the [classic Mapbox Static API](https://www.mapbox.com/api-documentation/#static-classic). Quickly generate a map snapshot – a static map image with overlays – by fetching it synchronously or asynchronously over the Web using first-class Swift or Objective-C 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. They are created in one HTTP request, so overlays are all added *server-side*. + A snapshot is a flattened PNG or JPEG image, ideal for use in a table or image view, sharing service, printed document, or anyplace else you’d like a quick, custom map without the overhead of an interactive view. A static map is created in a single HTTP request. Overlays are added server-side. ## Usage - You will need a [map ID](https://www.mapbox.com/help/define-map-id/) from a [custom map style](https://www.mapbox.com/help/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. + You can either generate a _snapshot_ from a Mapbox-hosted [style](https://www.mapbox.com/help/define-style/), or you can generate a _classic snapshot_ from a raw [tile set](https://www.mapbox.com/help/define-tileset/). Using a style gives you more visual options like rotation, while using a tile set gives you a choice of image formats. If you’re working with vector data, you’ll want to use a style; if you’re working with a single raster imagery source, you may want to use a tile set. - You can specify your access token inline or by setting the `MGLMapboxAccessToken` key in your application’s Info.plist file. + To generate a snapshot from a style, you’ll need its [style URL](https://www.mapbox.com/help/define-style-url/). You can either choose a [Mapbox-designed style](https://www.mapbox.com/api-documentation/#styles) or design one yourself in [Mapbox Studio](https://www.mapbox.com/studio/styles/). You can use the same style in the Mapbox macOS SDK. + + To generate a snapshot from a tileset, you’ll need a [map ID](https://www.mapbox.com/help/define-map-id/). You can either choose a [Mapbox-maintained tileset](https://www.mapbox.com/api-documentation/#maps) or upload your own to [Mapbox Studio](https://www.mapbox.com/studio/tilesets/). + + You’ll also need an [access token](https://www.mapbox.com/help/define-access-token/) with the `styles:tiles` scope enabled in order to use this library. You can specify your access token inline or by setting the `MGLMapboxAccessToken` key in your application’s Info.plist file. */ -let mapIdentifiers = ["justin.tm2-basemap"] -let accessToken = "pk.eyJ1IjoianVzdGluIiwiYSI6IlpDbUJLSUEifQ.4mG8vhelFMju6HpIY-Hi5A" +let styleURL = URL(string: "mapbox://styles/mapbox/streets-v9")! +let mapIdentifiers = ["mapbox.satellite"] +let accessToken = "pk.eyJ1IjoibWFwYm94IiwiYSI6ImNqMHFiNXN4ZDAxazMyd253cmt3a2hmN2cifQ.q0ntnAWEdwckfZnT0IEy5A" /*: ## Basics - The main static map class is `Snapshot`. To create a basic snapshot, create a `SnapshotOptions` object, specifying the center coordinates, [zoom level](https://www.mapbox.com/help/how-web-maps-work/#tiles-and-zoom-levels), and point size: + The main static map class is `Snapshot`. To create a basic snapshot, create a `SnapshotOptions` object, specifying snapshot camera (viewpoint) and point size: */ +var camera = SnapshotCamera( + lookingAtCenter: CLLocationCoordinate2D(latitude: 45.52, longitude: -122.681944), + zoomLevel: 13) var options = SnapshotOptions( - mapIdentifiers: mapIdentifiers, - centerCoordinate: CLLocationCoordinate2D(latitude: 45.52, longitude: -122.681944), - zoomLevel: 13, + styleURL: styleURL, + camera: camera, size: CGSize(width: 300, height: 200)) var snapshot = Snapshot( options: options, @@ -52,10 +59,24 @@ snapshot.image { (image, error) in */ snapshot.url +/*: + To create a basic classic snapshot, create a `ClassicSnapshotOptions` object instead of a `SnapshotOptions` object, specifying the center coordinates, [zoom level](https://www.mapbox.com/help/how-web-maps-work/#tiles-and-zoom-levels), and size in points: + */ + +let classicOptions = ClassicSnapshotOptions( + mapIdentifiers: mapIdentifiers, + centerCoordinate: CLLocationCoordinate2D(latitude: 45.52, longitude: -122.681944), + zoomLevel: 13, + size: CGSize(width: 300, height: 200)) +snapshot = Snapshot( + options: classicOptions, + accessToken: accessToken) +snapshot.image + /*: ## Overlays - Overlays are where things get interesting! You can add [Maki markers](https://www.mapbox.com/maki/), custom marker imagery, GeoJSON geometries, and even paths made of bare coordinates. + Overlays are where things get interesting! You can add [Maki markers](https://www.mapbox.com/maki-icons/), custom marker imagery, GeoJSON geometries, and even paths made of bare coordinates. You add overlays to the `overlays` field in the `SnapshotOptions` object. Here are some versions of our snapshot with various overlays added. @@ -87,13 +108,13 @@ snapshot.image /*: ### GeoJSON */ -let geojsonOverlay: GeoJSON +let geoJSONOverlay: GeoJSON do { - let geojsonURL = URL(string: "http://git.io/vCv9U")! - let geojsonString = try String(contentsOf: geojsonURL, encoding: .utf8) - geojsonOverlay = GeoJSON(objectString: geojsonString) + let geoJSONURL = URL(string: "http://git.io/vCv9U")! + let geoJSONString = try String(contentsOf: geoJSONURL, encoding: .utf8) + geoJSONOverlay = GeoJSON(objectString: geoJSONString) } -options.overlays = [geojsonOverlay] +options.overlays = [geoJSONOverlay] snapshot = Snapshot( options: options, accessToken: accessToken) @@ -136,11 +157,30 @@ snapshot.image /*: ## Other options + ### Rotation and tilt + + To rotate and tilt a snapshot, set its camera’s heading and pitch: + */ +camera.heading = 45 +camera.pitch = 60 +options = SnapshotOptions( + styleURL: styleURL, + camera: camera, + size: CGSize(width: 300, height: 200)) +snapshot = Snapshot( + options: options, + accessToken: accessToken) +snapshot.image + +/*: ### Auto-fitting features - If you’re adding overlays to your map, leave out the center coordinate and zoom level to automatically calculate the center and zoom level that best shows them off. + If you’re adding overlays to a snapshot, leave out the center coordinate and zoom level to automatically calculate the center and zoom level that best shows them off. */ -options.overlays = [path, geojsonOverlay, markerOverlay, customMarker] +options = SnapshotOptions( + styleURL: styleURL, + size: CGSize(width: 500, height: 300)) +options.overlays = [path, geoJSONOverlay, markerOverlay, customMarker] snapshot = Snapshot( options: options, accessToken: accessToken) @@ -163,9 +203,9 @@ snapshot.image /*: ### 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). + When creating a classic snapshot, 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). ### Attribution - Be sure to [attribute your map](https://www.mapbox.com/help/attribution/) properly in your app. You can also [find out more](https://www.mapbox.com/about/maps/) about where Mapbox’s map data comes from. + Be sure to [attribute your map](https://www.mapbox.com/help/attribution/) properly in your application. You can also [find out more](https://www.mapbox.com/about/maps/) about where Mapbox’s map data comes from. */ diff --git a/macOS.playground/timeline.xctimeline b/macOS.playground/timeline.xctimeline index 13ca729..babb79a 100644 --- a/macOS.playground/timeline.xctimeline +++ b/macOS.playground/timeline.xctimeline @@ -3,37 +3,37 @@ version = "3.0"> diff --git a/screenshots/autofit.png b/screenshots/autofit.png index db31668..2674e4a 100644 Binary files a/screenshots/autofit.png and b/screenshots/autofit.png differ diff --git a/screenshots/custom.png b/screenshots/custom.png index f72d257..b2a0ae4 100644 Binary files a/screenshots/custom.png and b/screenshots/custom.png differ diff --git a/screenshots/geojson.png b/screenshots/geojson.png index 3dc9394..cc71868 100644 Binary files a/screenshots/geojson.png and b/screenshots/geojson.png differ diff --git a/screenshots/map.png b/screenshots/map.png index 3052a1c..cdaf37f 100644 Binary files a/screenshots/map.png and b/screenshots/map.png differ diff --git a/screenshots/marker.png b/screenshots/marker.png index 2c190b2..8489f22 100644 Binary files a/screenshots/marker.png and b/screenshots/marker.png differ diff --git a/screenshots/path.png b/screenshots/path.png index 9179964..f511cfd 100644 Binary files a/screenshots/path.png and b/screenshots/path.png differ diff --git a/screenshots/rotate.png b/screenshots/rotate.png new file mode 100644 index 0000000..a849d50 Binary files /dev/null and b/screenshots/rotate.png differ