summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile8
-rw-r--r--README.md2
-rw-r--r--docs/BUILD_OSX92
-rw-r--r--docs/BUILD_OSX.md32
-rw-r--r--docs/DEVELOP_IOS_OSX.md2
-rw-r--r--docs/DEVELOP_OSX.md12
-rw-r--r--gyp/common.gypi2
-rw-r--r--gyp/osx.gyp2
-rw-r--r--gyp/platform-ios.gypi49
-rw-r--r--gyp/platform-osx.gypi63
-rw-r--r--include/mbgl/darwin/MGLAnnotation.h (renamed from include/mbgl/ios/MGLAnnotation.h)0
-rw-r--r--include/mbgl/darwin/MGLGeometry.h (renamed from include/mbgl/ios/MGLGeometry.h)6
-rw-r--r--include/mbgl/darwin/MGLMultiPoint.h (renamed from include/mbgl/ios/MGLMultiPoint.h)0
-rw-r--r--include/mbgl/darwin/MGLOverlay.h (renamed from include/mbgl/ios/MGLOverlay.h)0
-rw-r--r--include/mbgl/darwin/MGLPointAnnotation.h (renamed from include/mbgl/ios/MGLPointAnnotation.h)0
-rw-r--r--include/mbgl/darwin/MGLPolygon.h (renamed from include/mbgl/ios/MGLPolygon.h)0
-rw-r--r--include/mbgl/darwin/MGLPolyline.h (renamed from include/mbgl/ios/MGLPolyline.h)0
-rw-r--r--include/mbgl/darwin/MGLShape.h (renamed from include/mbgl/ios/MGLShape.h)0
-rw-r--r--include/mbgl/darwin/MGLStyle.h (renamed from include/mbgl/ios/MGLStyle.h)0
-rw-r--r--include/mbgl/darwin/MGLTypes.h52
-rw-r--r--include/mbgl/ios/MGLTypes.h49
-rw-r--r--include/mbgl/osx/MGLAccountManager.h26
-rw-r--r--include/mbgl/osx/MGLAnnotationImage.h17
-rw-r--r--include/mbgl/osx/MGLMapView+IBAdditions.h44
-rw-r--r--include/mbgl/osx/MGLMapView.h85
-rw-r--r--include/mbgl/osx/MGLMapViewDelegate.h42
-rw-r--r--include/mbgl/osx/Mapbox.h15
-rw-r--r--ios/app/app-info.plist6
-rw-r--r--platform/darwin/MGLGeometry.m (renamed from platform/ios/MGLGeometry.m)0
-rw-r--r--platform/darwin/MGLGeometry_Private.h26
-rw-r--r--platform/darwin/MGLMultiPoint.mm (renamed from platform/ios/MGLMultiPoint.mm)37
-rw-r--r--platform/darwin/MGLMultiPoint_Private.h38
-rw-r--r--platform/darwin/MGLPointAnnotation.m (renamed from platform/ios/MGLPointAnnotation.m)0
-rw-r--r--platform/darwin/MGLPolygon.mm28
-rw-r--r--platform/darwin/MGLPolyline.mm28
-rw-r--r--platform/darwin/MGLShape.m (renamed from platform/ios/MGLShape.m)0
-rw-r--r--platform/darwin/MGLStyle.mm (renamed from platform/ios/MGLStyle.mm)0
-rw-r--r--platform/darwin/MGLTypes.m (renamed from platform/ios/MGLTypes.m)0
-rw-r--r--platform/darwin/NSException+MGLAdditions.h (renamed from platform/ios/NSException+MGLAdditions.h)0
-rw-r--r--platform/darwin/NSString+MGLAdditions.h (renamed from platform/ios/NSString+MGLAdditions.h)0
-rw-r--r--platform/darwin/NSString+MGLAdditions.m (renamed from platform/ios/NSString+MGLAdditions.m)4
-rw-r--r--platform/ios/MGLMapView.mm196
-rw-r--r--platform/ios/MGLMultiPoint_Private.h10
-rw-r--r--platform/ios/MGLPolygon.m15
-rw-r--r--platform/ios/MGLPolyline.m15
-rw-r--r--platform/osx/Info.plist47
-rw-r--r--platform/osx/app/AppDelegate.h7
-rw-r--r--platform/osx/app/AppDelegate.m462
-rw-r--r--platform/osx/app/Credits.rtf9
-rw-r--r--platform/osx/app/DroppedPinAnnotation.h10
-rw-r--r--platform/osx/app/DroppedPinAnnotation.m63
-rw-r--r--platform/osx/app/Icon.icns (renamed from platform/osx/Icon.icns)bin64088 -> 64088 bytes
-rw-r--r--platform/osx/app/Info.plist49
-rw-r--r--platform/osx/app/LocationCoordinate2DTransformer.h5
-rw-r--r--platform/osx/app/LocationCoordinate2DTransformer.m42
-rw-r--r--platform/osx/app/MainMenu.xib683
-rw-r--r--platform/osx/app/NSValue+Additions.h10
-rw-r--r--platform/osx/app/NSValue+Additions.m15
-rw-r--r--platform/osx/app/TimeIntervalTransformer.h5
-rw-r--r--platform/osx/app/TimeIntervalTransformer.m55
-rw-r--r--platform/osx/app/main.m5
-rw-r--r--platform/osx/app/mapboxgl-app.gypi48
-rw-r--r--platform/osx/main.mm212
-rw-r--r--platform/osx/mapboxgl-app.gypi63
-rw-r--r--platform/osx/sdk/MGLAccountManager.m66
-rw-r--r--platform/osx/sdk/MGLAccountManager_Private.h10
-rw-r--r--platform/osx/sdk/MGLAnnotationImage.m25
-rw-r--r--platform/osx/sdk/MGLAttributionButton.h11
-rw-r--r--platform/osx/sdk/MGLAttributionButton.m46
-rw-r--r--platform/osx/sdk/MGLCompassCell.h5
-rw-r--r--platform/osx/sdk/MGLCompassCell.m31
-rw-r--r--platform/osx/sdk/MGLMapView+IBAdditions.m114
-rw-r--r--platform/osx/sdk/MGLMapView.mm1636
-rw-r--r--platform/osx/sdk/MGLMapView_Private.h12
-rw-r--r--platform/osx/sdk/MGLOpenGLLayer.h10
-rw-r--r--platform/osx/sdk/MGLOpenGLLayer.mm49
-rw-r--r--platform/osx/sdk/NSBundle+MGLAdditions.h16
-rw-r--r--platform/osx/sdk/NSBundle+MGLAdditions.m22
-rw-r--r--platform/osx/sdk/NSProcessInfo+MGLAdditions.h9
-rw-r--r--platform/osx/sdk/NSProcessInfo+MGLAdditions.m11
-rw-r--r--platform/osx/sdk/resources/MGLAnnotationCallout.xib53
-rw-r--r--platform/osx/sdk/resources/default_marker.pdfbin0 -> 2601 bytes
-rw-r--r--platform/osx/sdk/resources/mapbox.pdfbin0 -> 3762 bytes
-rwxr-xr-xscripts/ios/package.sh3
-rwxr-xr-xscripts/osx/package.sh78
-rwxr-xr-xscripts/osx/run.sh6
86 files changed, 4424 insertions, 582 deletions
diff --git a/Makefile b/Makefile
index 3b0824a2bd..5a3c574764 100644
--- a/Makefile
+++ b/Makefile
@@ -56,6 +56,10 @@ ipackage-sim: Xcode/ios ; @JOBS=$(JOBS) ./scripts/ios/package.sh sim
ipackage-no-bitcode: Xcode/ios ; @JOBS=$(JOBS) ./scripts/ios/package.sh no-bitcode
iframework: ipackage-strip ; ./scripts/ios/framework.sh
itest: ipackage-sim ; ./scripts/ios/test.sh
+
+.PHONY: xpackage xpackage-strip
+xpackage: Xcode/osx ; @JOBS=$(JOBS) ./scripts/osx/package.sh
+xpackage-strip: Xcode/osx ; @JOBS=$(JOBS) ./scripts/osx/package.sh strip
endif
#### All platforms targets #####################################################
@@ -128,10 +132,12 @@ ifeq ($(BUILD), osx)
if [ $$CUSTOM_DD ]; then \
echo clearing files in $$CUSTOM_DD older than one day; \
find $$CUSTOM_DD/mapboxgl-app-* -mtime +1 | xargs rm -rf; \
+ find $$CUSTOM_DD/osxapp-* -mtime +1 | xargs rm -rf; \
fi; \
if [ -d ~/Library/Developer/Xcode/DerivedData/ ] && [ ! $$CUSTOM_DD ]; then \
- echo 'clearing files in ~/Library/Developer/Xcode/DerivedData/mapboxgl-app-* older than one day'; \
+ echo 'clearing files in ~/Library/Developer/Xcode/DerivedData/{mapboxgl-app,osxapp}-* older than one day'; \
find ~/Library/Developer/Xcode/DerivedData/mapboxgl-app-* -mtime +1 | xargs rm -rf; \
+ find ~/Library/Developer/Xcode/DerivedData/osxapp-* -mtime +1 | xargs rm -rf; \
fi
endif
diff --git a/README.md b/README.md
index d1e425237e..1cd576cf1c 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@ If you want to use products _based on_ Mapbox GL, check out:
## Targets
* Ubuntu Linux
-* OS X 10.9+
+* OS X 10.10+
* iOS 7.0+
* iPhone 4S and above (5, 5c, 5s, 6, 6 Plus)
* iPad 2 and above (3, 4, Mini, Air, Mini 2, Air 2)
diff --git a/docs/BUILD_OSX b/docs/BUILD_OSX
new file mode 100644
index 0000000000..6a43025201
--- /dev/null
+++ b/docs/BUILD_OSX
@@ -0,0 +1,92 @@
+# Building Mapbox GL Native for iOS
+
+This section is for people contributing to Mapbox GL directly in the context of their own app.
+
+### Build
+
+1. Install [appledoc](http://appledoc.gentlebytes.com/appledoc/) for API docs generation.
+
+ ```
+ curl -L -o appledoc.zip https://github.com/tomaz/appledoc/releases/download/v2.2-963/appledoc.zip
+ unzip appledoc.zip
+ cp appledoc /usr/local/bin
+ cp -Rf Templates/ ~/.appledoc
+ ```
+
+1. Run `make ipackage`. The packaging script will produce the statically-linked `libMapbox.a`, `Mapbox.bundle` for resources, a `Headers` folder, and a `Docs` folder with HTML API documentation.
+
+### Access Tokens
+
+_The demo applications use Mapbox vector tiles, which require a Mapbox account and API access token. Obtain an access token on the [Mapbox account page](https://www.mapbox.com/account/apps/)._
+
+Set up the access token by editing the scheme for the application target, then adding an environment variable with the name `MAPBOX_ACCESS_TOKEN`.
+
+![edit scheme](https://cloud.githubusercontent.com/assets/98601/5460702/c4610262-8519-11e4-873a-8597821da468.png)
+
+![setting access token in Xcode scheme](https://cloud.githubusercontent.com/assets/162976/5349358/0a086f00-7f8c-11e4-8433-bdbaccda2b58.png)
+
+### Test
+
+In the context of your own app, you can now either:
+
+#### CocoaPods
+
+Currently, until [#1437](https://github.com/mapbox/mapbox-gl-native/issues/1437) is completed, to install a _development version_ of Mapbox GL using CocoaPods you will need to build it from source manually per above.
+
+1. Zip up the build product.
+
+ ```
+ cd build/ios/pkg/static
+ ZIP=mapbox-ios-sdk.zip
+ rm -f ../${ZIP}
+ zip -r ../${ZIP} *
+ ```
+
+1. Modify a custom `Mapbox-iOS-SDK.podspec` to download this zip file.
+
+ ```rb
+ {...}
+
+ m.source = {
+ :http => "http://{...}/mapbox-ios-sdk.zip",
+ :flatten => true
+ }
+
+ {...}
+ ```
+
+1. Update your app's `Podfile` to point to the `Mapbox-iOS-SDK.podspec`.
+
+ ```rb
+ pod 'Mapbox-iOS-SDK', :podspec => 'http://{...}/Mapbox-iOS-SDK.podspec'
+ ```
+
+1. Run `pod update` to grab the newly-built library.
+
+#### Binary
+
+1. Built from source manually per above.
+
+1. Copy the contents of `build/ios/pkg/static` into your project. It should happen automatically, but ensure that:
+
+ - `Headers` is in your *Header Search Paths* (`HEADER_SEARCH_PATHS`) build setting.
+ - `Mapbox.bundle` is in your target's *Copy Bundle Resources* build phase.
+ - `libMapbox.a` is in your target's *Link Binary With Libraries* build phase.
+
+1. Add the following Cocoa framework dependencies to your target's *Link Binary With Libraries* build phase:
+
+ - `GLKit.framework`
+ - `ImageIO.framework`
+ - `MobileCoreServices.framework`
+ - `QuartzCore.framework`
+ - `SystemConfiguration.framework`
+ - `libc++.dylib`
+ - `libsqlite3.dylib`
+ - `libz.dylib`
+ - `CoreTelephony.framework` (optional, telemetry-only)
+
+1. Add `-ObjC` to your target's "Other Linker Flags" build setting (`OTHER_LDFLAGS`).
+
+## Troubleshooting
+
+On OS X, you can also try clearing the Xcode cache with `make clear_xcode_cache`.
diff --git a/docs/BUILD_OSX.md b/docs/BUILD_OSX.md
new file mode 100644
index 0000000000..5b4f6e6327
--- /dev/null
+++ b/docs/BUILD_OSX.md
@@ -0,0 +1,32 @@
+# Building Mapbox GL Native for OS X
+
+This project provides an OS X SDK analogous to the Mapbox iOS SDK. Mapbox does not officially support it to the same extent as the iOS SDK; however, bug reports and pull requests are certainly welcome. This document explains how to build the OS X SDK and integrate it into your own Cocoa application.
+
+### Build
+
+1. Run `make xpackage`. The packaging script will produce the statically-linked `libMapbox.a`, `Mapbox.bundle` for resources, and a `Headers` folder.
+
+### Install
+
+1. Copy the contents of `build/osx/pkg/static` into your project. It should happen automatically, but ensure that:
+
+ - `Headers` is in your *Header Search Paths* (`HEADER_SEARCH_PATHS`) build setting.
+ - `Mapbox.bundle` is in your target's *Copy Bundle Resources* build phase.
+ - `libMapbox.a` is in your target's *Link Binary With Libraries* build phase.
+
+1. Add the following Cocoa framework dependencies to your target's *Link Binary With Libraries* build phase:
+
+ - `SystemConfiguration.framework`
+ - `libc++.tbd`
+ - `libsqlite3.tbd`
+ - `libz.tbd`
+
+1. Add `-ObjC` to your target's "Other Linker Flags" build setting (`OTHER_LDFLAGS`).
+
+1. Mapbox vector tiles require a Mapbox account and API access token. In the project editor, select the application target. In the Info tab, set `MGLMapboxAccessToken` to your access token. You can obtain one from the [Mapbox account page](https://www.mapbox.com/account/apps/).
+
+1. In a XIB or storyboard, add a Custom View and set its custom class to `MGLMapView`. If you need to manipulate the map view programmatically, import the `Mapbox` module (Swift) or `Mapbox.h` umbrella header (Objective-C).
+
+## Troubleshooting
+
+You can also try clearing the Xcode cache with `make clear_xcode_cache`.
diff --git a/docs/DEVELOP_IOS_OSX.md b/docs/DEVELOP_IOS_OSX.md
index 183fd15705..9cc27bc581 100644
--- a/docs/DEVELOP_IOS_OSX.md
+++ b/docs/DEVELOP_IOS_OSX.md
@@ -14,7 +14,7 @@ Run
make iproj
-Which which will create and open an Xcode project which can build the entire library from source, as well as an Objective-C test app.
+Which will create and open an Xcode project that can build the entire library from source, as well as an Objective-C test app.
If you don't have an Apple Developer account, change the destination from "My Mac" to a simulator such as "iPhone 6" before you run and build the app.
diff --git a/docs/DEVELOP_OSX.md b/docs/DEVELOP_OSX.md
index 02d0865726..3ca96ebf24 100644
--- a/docs/DEVELOP_OSX.md
+++ b/docs/DEVELOP_OSX.md
@@ -2,7 +2,7 @@
To create projects, you can run:
-- `make xproj`: Creates an Xcode project with OS X-specific handlers for HTTP downloads and settings storage. It uses [GLFW](http://www.glfw.org) for window handling.
+- `make xproj`: Creates an Xcode project with a native OS X application for testing changes to mapbox-gl-native on the desktop.
- `make lproj`: Creates an Xcode project with platform-independent handlers for downloads and settings storage. This is what is also being built on Linux.
- `make osx run-osx`: Builds and runs the OS X application on the command line with `xcodebuild`.
@@ -10,14 +10,8 @@ Note that you can't have more than one project in Xcode open at a time because t
### Access Tokens
-_The demo applications use Mapbox vector tiles, which require a Mapbox account and API access token. Obtain an access token on the [Mapbox account page](https://www.mapbox.com/studio/account/tokens/)._
-
-Set up the access token by editing the scheme for the application target, then adding an environment variable with the name `MAPBOX_ACCESS_TOKEN`.
-
-![edit scheme](https://cloud.githubusercontent.com/assets/98601/5460702/c4610262-8519-11e4-873a-8597821da468.png)
-
-![setting access token in Xcode scheme](https://cloud.githubusercontent.com/assets/162976/5349358/0a086f00-7f8c-11e4-8433-bdbaccda2b58.png)
+The demo applications use Mapbox vector tiles, which require a Mapbox account and API access token. Obtain an access token on the [Mapbox account page](https://www.mapbox.com/studio/account/tokens/). You will be prompted for this access token the first time you launch the demo application.
### Usage
-Keyboard shortcuts for testing functionality are logged to the console when the test app is started.
+Through the OS X SDK, the demo application supports a variety of standard gestures and keyboard shortcuts. For more details, open Mapbox GL Help from the Help menu.
diff --git a/gyp/common.gypi b/gyp/common.gypi
index b3834934d3..6b7f2aea7e 100644
--- a/gyp/common.gypi
+++ b/gyp/common.gypi
@@ -24,7 +24,7 @@
],
'GCC_WARN_PEDANTIC': 'YES',
'GCC_WARN_UNINITIALIZED_AUTOS': 'YES_AGGRESSIVE',
- 'MACOSX_DEPLOYMENT_TARGET': '10.9',
+ 'MACOSX_DEPLOYMENT_TARGET': '10.10',
},
}, {
'cflags_cc': [
diff --git a/gyp/osx.gyp b/gyp/osx.gyp
index 8810144a80..2df9d4818d 100644
--- a/gyp/osx.gyp
+++ b/gyp/osx.gyp
@@ -1,6 +1,6 @@
{
'includes': [
- '../platform/osx/mapboxgl-app.gypi',
+ '../platform/osx/app/mapboxgl-app.gypi',
'../platform/linux/mapboxgl-app.gypi',
'../test/test.gypi',
'../bin/render.gypi',
diff --git a/gyp/platform-ios.gypi b/gyp/platform-ios.gypi
index 5dae54ad90..070dd55c31 100644
--- a/gyp/platform-ios.gypi
+++ b/gyp/platform-ios.gypi
@@ -20,6 +20,29 @@
'../platform/darwin/image.mm',
'../platform/darwin/nsthread.mm',
'../platform/darwin/reachability.m',
+ '../platform/darwin/NSException+MGLAdditions.h',
+ '../platform/darwin/NSString+MGLAdditions.h',
+ '../platform/darwin/NSString+MGLAdditions.m',
+ '../include/mbgl/darwin/MGLTypes.h',
+ '../platform/darwin/MGLTypes.m',
+ '../include/mbgl/darwin/MGLStyle.h',
+ '../platform/darwin/MGLStyle.mm',
+ '../include/mbgl/darwin/MGLGeometry.h',
+ '../platform/darwin/MGLGeometry_Private.h',
+ '../platform/darwin/MGLGeometry.m',
+ '../include/mbgl/darwin/MGLAnnotation.h',
+ '../include/mbgl/darwin/MGLShape.h',
+ '../platform/darwin/MGLShape.m',
+ '../include/mbgl/darwin/MGLMultiPoint.h',
+ '../platform/darwin/MGLMultiPoint_Private.h',
+ '../platform/darwin/MGLMultiPoint.mm',
+ '../include/mbgl/darwin/MGLOverlay.h',
+ '../include/mbgl/darwin/MGLPointAnnotation.h',
+ '../platform/darwin/MGLPointAnnotation.m',
+ '../include/mbgl/darwin/MGLPolyline.h',
+ '../platform/darwin/MGLPolyline.mm',
+ '../include/mbgl/darwin/MGLPolygon.h',
+ '../platform/darwin/MGLPolygon.mm',
'../include/mbgl/ios/Mapbox.h',
'../platform/ios/MGLMapboxEvents.h',
'../platform/ios/MGLMapboxEvents.m',
@@ -31,41 +54,19 @@
'../include/mbgl/ios/MGLAccountManager.h',
'../platform/ios/MGLAccountManager_Private.h',
'../platform/ios/MGLAccountManager.m',
- '../include/mbgl/ios/MGLAnnotation.h',
'../include/mbgl/ios/MGLUserLocation.h',
'../platform/ios/MGLUserLocation_Private.h',
'../platform/ios/MGLUserLocation.m',
'../platform/ios/MGLUserLocationAnnotationView.h',
'../platform/ios/MGLUserLocationAnnotationView.m',
- '../include/mbgl/ios/MGLTypes.h',
- '../platform/ios/MGLTypes.m',
- '../include/mbgl/ios/MGLGeometry.h',
- '../platform/ios/MGLGeometry.m',
- '../include/mbgl/ios/MGLMultiPoint.h',
- '../platform/ios/MGLMultiPoint_Private.h',
- '../platform/ios/MGLMultiPoint.mm',
- '../include/mbgl/ios/MGLOverlay.h',
- '../include/mbgl/ios/MGLPointAnnotation.h',
- '../platform/ios/MGLPointAnnotation.m',
- '../include/mbgl/ios/MGLPolyline.h',
- '../platform/ios/MGLPolyline.m',
- '../include/mbgl/ios/MGLPolygon.h',
- '../platform/ios/MGLPolygon.m',
- '../include/mbgl/ios/MGLShape.h',
- '../platform/ios/MGLShape.m',
'../include/mbgl/ios/MGLAnnotationImage.h',
'../platform/ios/MGLAnnotationImage.m',
- '../include/mbgl/ios/MGLStyle.h',
- '../platform/ios/MGLStyle.mm',
'../platform/ios/MGLCategoryLoader.h',
'../platform/ios/MGLCategoryLoader.m',
'../platform/ios/NSBundle+MGLAdditions.h',
'../platform/ios/NSBundle+MGLAdditions.m',
- '../platform/ios/NSException+MGLAdditions.h',
'../platform/ios/NSProcessInfo+MGLAdditions.h',
'../platform/ios/NSProcessInfo+MGLAdditions.m',
- '../platform/ios/NSString+MGLAdditions.h',
- '../platform/ios/NSString+MGLAdditions.m',
'../platform/ios/vendor/SMCalloutView/SMCalloutView.h',
'../platform/ios/vendor/SMCalloutView/SMCalloutView.m',
'../platform/ios/vendor/Fabric/FABAttributes.h',
@@ -96,6 +97,8 @@
},
'include_dirs': [
+ '../include/mbgl/ios',
+ '../include/mbgl/darwin',
'../include',
'../src',
],
@@ -115,6 +118,8 @@
'direct_dependent_settings': {
'include_dirs': [
+ '../include/mbgl/ios',
+ '../include/mbgl/darwin',
'../include',
],
'mac_bundle_resources': [
diff --git a/gyp/platform-osx.gypi b/gyp/platform-osx.gypi
index 1ccbd3d59b..1462d6e364 100644
--- a/gyp/platform-osx.gypi
+++ b/gyp/platform-osx.gypi
@@ -19,26 +19,76 @@
'../platform/darwin/asset_root.mm',
'../platform/darwin/image.mm',
'../platform/darwin/nsthread.mm',
+ '../platform/darwin/reachability.m',
+ '../platform/darwin/NSException+MGLAdditions.h',
+ '../platform/darwin/NSString+MGLAdditions.h',
+ '../platform/darwin/NSString+MGLAdditions.m',
+ '../include/mbgl/darwin/MGLTypes.h',
+ '../platform/darwin/MGLTypes.m',
+ '../include/mbgl/darwin/MGLStyle.h',
+ '../platform/darwin/MGLStyle.mm',
+ '../include/mbgl/darwin/MGLGeometry.h',
+ '../platform/darwin/MGLGeometry_Private.h',
+ '../platform/darwin/MGLGeometry.m',
+ '../include/mbgl/darwin/MGLAnnotation.h',
+ '../include/mbgl/darwin/MGLShape.h',
+ '../platform/darwin/MGLShape.m',
+ '../include/mbgl/darwin/MGLMultiPoint.h',
+ '../platform/darwin/MGLMultiPoint_Private.h',
+ '../platform/darwin/MGLMultiPoint.mm',
+ '../include/mbgl/darwin/MGLOverlay.h',
+ '../include/mbgl/darwin/MGLPointAnnotation.h',
+ '../platform/darwin/MGLPointAnnotation.m',
+ '../include/mbgl/darwin/MGLPolyline.h',
+ '../platform/darwin/MGLPolyline.mm',
+ '../include/mbgl/darwin/MGLPolygon.h',
+ '../platform/darwin/MGLPolygon.mm',
+ '../include/mbgl/osx/Mapbox.h',
+ '../include/mbgl/osx/MGLAccountManager.h',
+ '../platform/osx/sdk/MGLAccountManager_Private.h',
+ '../platform/osx/sdk/MGLAccountManager.m',
+ '../include/mbgl/osx/MGLMapView.h',
+ '../platform/osx/sdk/MGLMapView_Private.h',
+ '../platform/osx/sdk/MGLMapView.mm',
+ '../include/mbgl/osx/MGLMapView+IBAdditions.h',
+ '../platform/osx/sdk/MGLMapView+IBAdditions.m',
+ '../include/mbgl/osx/MGLMapViewDelegate.h',
+ '../platform/osx/sdk/MGLOpenGLLayer.h',
+ '../platform/osx/sdk/MGLOpenGLLayer.mm',
+ '../platform/osx/sdk/MGLCompassCell.h',
+ '../platform/osx/sdk/MGLCompassCell.m',
+ '../platform/osx/sdk/MGLAttributionButton.h',
+ '../platform/osx/sdk/MGLAttributionButton.m',
+ '../include/mbgl/osx/MGLAnnotationImage.h',
+ '../platform/osx/sdk/MGLAnnotationImage.m',
+ '../platform/osx/sdk/NSBundle+MGLAdditions.h',
+ '../platform/osx/sdk/NSBundle+MGLAdditions.m',
+ '../platform/osx/sdk/NSProcessInfo+MGLAdditions.h',
+ '../platform/osx/sdk/NSProcessInfo+MGLAdditions.m',
+ '../platform/osx/sdk/resources/',
],
'variables': {
'cflags_cc': [
'<@(libuv_cflags)',
'<@(boost_cflags)',
+ '<@(variant_cflags)',
],
'libraries': [
'<@(libuv_static_libs)',
],
'ldflags': [
- '-framework Foundation',
- '-framework ImageIO',
- '-framework CoreServices',
+ '-framework Cocoa',
+ '-framework CoreLocation',
'-framework OpenGL',
- '-framework ApplicationServices',
+ '-framework QuartzCore',
+ '-framework SystemConfiguration',
],
},
'include_dirs': [
+ '../include/mbgl/osx',
+ '../include/mbgl/darwin',
'../include',
'../src',
],
@@ -58,8 +108,13 @@
'direct_dependent_settings': {
'include_dirs': [
+ '../include/mbgl/osx',
+ '../include/mbgl/darwin',
'../include',
],
+ 'mac_bundle_resources': [
+ '<!@(find ../platform/osx/sdk/resources -type f \! -name "README")',
+ ],
},
},
],
diff --git a/include/mbgl/ios/MGLAnnotation.h b/include/mbgl/darwin/MGLAnnotation.h
index aef21ecbc6..aef21ecbc6 100644
--- a/include/mbgl/ios/MGLAnnotation.h
+++ b/include/mbgl/darwin/MGLAnnotation.h
diff --git a/include/mbgl/ios/MGLGeometry.h b/include/mbgl/darwin/MGLGeometry.h
index a2ae9c991c..5337dca6b8 100644
--- a/include/mbgl/ios/MGLGeometry.h
+++ b/include/mbgl/darwin/MGLGeometry.h
@@ -83,14 +83,12 @@ NS_INLINE NSString *MGLStringFromCoordinateBounds(MGLCoordinateBounds bounds) {
}
/** Returns radians, converted from degrees. */
-NS_INLINE CGFloat MGLRadiansFromDegrees(CLLocationDegrees degrees)
-{
+NS_INLINE CGFloat MGLRadiansFromDegrees(CLLocationDegrees degrees) {
return (CGFloat)(degrees * M_PI) / 180;
}
/** Returns degrees, converted from radians. */
-NS_INLINE CLLocationDegrees MGLDegreesFromRadians(CGFloat radians)
-{
+NS_INLINE CLLocationDegrees MGLDegreesFromRadians(CGFloat radians) {
return radians * 180 / M_PI;
}
diff --git a/include/mbgl/ios/MGLMultiPoint.h b/include/mbgl/darwin/MGLMultiPoint.h
index 5c58016df4..5c58016df4 100644
--- a/include/mbgl/ios/MGLMultiPoint.h
+++ b/include/mbgl/darwin/MGLMultiPoint.h
diff --git a/include/mbgl/ios/MGLOverlay.h b/include/mbgl/darwin/MGLOverlay.h
index ab827c0d22..ab827c0d22 100644
--- a/include/mbgl/ios/MGLOverlay.h
+++ b/include/mbgl/darwin/MGLOverlay.h
diff --git a/include/mbgl/ios/MGLPointAnnotation.h b/include/mbgl/darwin/MGLPointAnnotation.h
index d186cbff18..d186cbff18 100644
--- a/include/mbgl/ios/MGLPointAnnotation.h
+++ b/include/mbgl/darwin/MGLPointAnnotation.h
diff --git a/include/mbgl/ios/MGLPolygon.h b/include/mbgl/darwin/MGLPolygon.h
index a19b984f1c..a19b984f1c 100644
--- a/include/mbgl/ios/MGLPolygon.h
+++ b/include/mbgl/darwin/MGLPolygon.h
diff --git a/include/mbgl/ios/MGLPolyline.h b/include/mbgl/darwin/MGLPolyline.h
index c849bc9876..c849bc9876 100644
--- a/include/mbgl/ios/MGLPolyline.h
+++ b/include/mbgl/darwin/MGLPolyline.h
diff --git a/include/mbgl/ios/MGLShape.h b/include/mbgl/darwin/MGLShape.h
index f54d4bfb74..f54d4bfb74 100644
--- a/include/mbgl/ios/MGLShape.h
+++ b/include/mbgl/darwin/MGLShape.h
diff --git a/include/mbgl/ios/MGLStyle.h b/include/mbgl/darwin/MGLStyle.h
index 88cb8aaa83..88cb8aaa83 100644
--- a/include/mbgl/ios/MGLStyle.h
+++ b/include/mbgl/darwin/MGLStyle.h
diff --git a/include/mbgl/darwin/MGLTypes.h b/include/mbgl/darwin/MGLTypes.h
new file mode 100644
index 0000000000..b86ecc35e6
--- /dev/null
+++ b/include/mbgl/darwin/MGLTypes.h
@@ -0,0 +1,52 @@
+#import <Foundation/Foundation.h>
+
+#pragma once
+
+#if !__has_feature(nullability)
+ #define NS_ASSUME_NONNULL_BEGIN
+ #define NS_ASSUME_NONNULL_END
+ #define nullable
+ #define nonnull
+ #define null_resettable
+#endif
+
+NS_ASSUME_NONNULL_BEGIN
+
+extern NSString * const MGLErrorDomain;
+
+/** The mode used to track the user location on the map. */
+typedef NS_ENUM(NSUInteger, MGLUserTrackingMode) {
+ /** The map does not follow the user location. */
+ MGLUserTrackingModeNone = 0,
+ /** The map follows the user location. */
+ MGLUserTrackingModeFollow,
+ /** The map follows the user location and rotates when the heading changes. */
+ MGLUserTrackingModeFollowWithHeading,
+ /** The map follows the user location and rotates when the course changes. */
+ MGLUserTrackingModeFollowWithCourse,
+};
+
+NS_ASSUME_NONNULL_END
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wvariadic-macros"
+ #ifndef NS_ARRAY_OF
+ // Foundation collection classes adopted lightweight generics in iOS 9.0 and OS X 10.11 SDKs.
+ #if __has_feature(objc_generics) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= 90000 || __MAC_OS_X_VERSION_MAX_ALLOWED >= 101100)
+ /** Inserts a type specifier for a pointer to a lightweight generic with the given collection and object classes. Use a `*` for any non-`id` object classes but no `*` for the collection class. */
+ #define NS_ARRAY_OF(ObjectClass...) NSArray <ObjectClass>
+ #define NS_MUTABLE_ARRAY_OF(ObjectClass...) NSMutableArray <ObjectClass>
+ #define NS_SET_OF(ObjectClass...) NSSet <ObjectClass>
+ #define NS_MUTABLE_SET_OF(ObjectClass...) NSMutableSet <ObjectClass>
+ #define NS_DICTIONARY_OF(ObjectClass...) NSDictionary <ObjectClass>
+ #define NS_MUTABLE_DICTIONARY_OF(ObjectClass...) NSMutableDictionary <ObjectClass>
+ #else
+ #define NS_ARRAY_OF(ObjectClass...) NSArray
+ #define NS_MUTABLE_ARRAY_OF(ObjectClass...) NSMutableArray
+ #define NS_SET_OF(ObjectClass...) NSSet
+ #define NS_MUTABLE_SET_OF(ObjectClass...) NSMutableSet
+ #define NS_DICTIONARY_OF(ObjectClass...) NSDictionary
+ #define NS_MUTABLE_DICTIONARY_OF(ObjectClass...) NSMutableDictionary
+ #endif
+ #endif
+#pragma clang diagnostic pop
diff --git a/include/mbgl/ios/MGLTypes.h b/include/mbgl/ios/MGLTypes.h
deleted file mode 100644
index f36fc3f44e..0000000000
--- a/include/mbgl/ios/MGLTypes.h
+++ /dev/null
@@ -1,49 +0,0 @@
-#import <Foundation/Foundation.h>
-
-#pragma once
-
-#if !__has_feature(nullability)
- #define NS_ASSUME_NONNULL_BEGIN
- #define NS_ASSUME_NONNULL_END
- #define nullable
- #define nonnull
- #define null_resettable
-#endif
-
-NS_ASSUME_NONNULL_BEGIN
-
-extern NSString * const MGLErrorDomain;
-
-/** The mode used to track the user location on the map. */
-typedef NS_ENUM(NSUInteger, MGLUserTrackingMode) {
- /** The map does not follow the user location. */
- MGLUserTrackingModeNone = 0,
- /** The map follows the user location. */
- MGLUserTrackingModeFollow,
- /** The map follows the user location and rotates when the heading changes. */
- MGLUserTrackingModeFollowWithHeading,
- /** The map follows the user location and rotates when the course changes. */
- MGLUserTrackingModeFollowWithCourse,
-};
-
-NS_ASSUME_NONNULL_END
-
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wvariadic-macros"
- #if __has_feature(objc_generics)
- /** Inserts a type specifier for a pointer to a lightweight generic with the given collection and object classes. Use a `*` for any non-`id` object classes but no `*` for the collection class. */
- #define NS_ARRAY_OF(ObjectClass...) NSArray <ObjectClass>
- #define NS_MUTABLE_ARRAY_OF(ObjectClass...) NSMutableArray <ObjectClass>
- #define NS_SET_OF(ObjectClass...) NSSet <ObjectClass>
- #define NS_MUTABLE_SET_OF(ObjectClass...) NSMutableSet <ObjectClass>
- #define NS_DICTIONARY_OF(ObjectClass...) NSDictionary <ObjectClass>
- #define NS_MUTABLE_DICTIONARY_OF(ObjectClass...) NSMutableDictionary <ObjectClass>
- #else
- #define NS_ARRAY_OF(ObjectClass...) NSArray
- #define NS_MUTABLE_ARRAY_OF(ObjectClass...) NSMutableArray
- #define NS_SET_OF(ObjectClass...) NSSet
- #define NS_MUTABLE_SET_OF(ObjectClass...) NSMutableSet
- #define NS_DICTIONARY_OF(ObjectClass...) NSDictionary
- #define NS_MUTABLE_DICTIONARY_OF(ObjectClass...) NSMutableDictionary
- #endif
-#pragma clang diagnostic pop
diff --git a/include/mbgl/osx/MGLAccountManager.h b/include/mbgl/osx/MGLAccountManager.h
new file mode 100644
index 0000000000..c185f29b2e
--- /dev/null
+++ b/include/mbgl/osx/MGLAccountManager.h
@@ -0,0 +1,26 @@
+#import <Foundation/Foundation.h>
+
+#import "MGLTypes.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** The MGLAccountManager object provides a global way to set a Mapbox API access token, as well as other settings used framework-wide. */
+@interface MGLAccountManager : NSObject
+
+/** @name Authorizing Access */
+
+/** Set the Mapbox API access token for the framework.
+*
+* You can set an access token on MGLAccountManager or on an individual map view. The same token is used throughout the framework.
+* @param accessToken The Mapbox API access token. */
++ (void)setAccessToken:(nullable NSString *)accessToken;
+
+/** Retreive the Mapbox API access token for the framework.
+*
+* You can set an access token on MGLAccountManager or on an individual map view. The same token is used throughout the framework.
+* @return accessToken The Mapbox API access token. */
++ (nullable NSString *)accessToken;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/include/mbgl/osx/MGLAnnotationImage.h b/include/mbgl/osx/MGLAnnotationImage.h
new file mode 100644
index 0000000000..e42e062022
--- /dev/null
+++ b/include/mbgl/osx/MGLAnnotationImage.h
@@ -0,0 +1,17 @@
+#import <AppKit/AppKit.h>
+
+#import "MGLTypes.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface MGLAnnotationImage : NSObject
+
++ (instancetype)annotationImageWithImage:(NSImage *)image reuseIdentifier:(NSString *)reuseIdentifier;
+
+@property (nonatomic, readonly) NSImage *image;
+@property (nonatomic, readonly) NSString *reuseIdentifier;
+@property (nonatomic, getter=isSelectable) BOOL selectable;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/include/mbgl/osx/MGLMapView+IBAdditions.h b/include/mbgl/osx/MGLMapView+IBAdditions.h
new file mode 100644
index 0000000000..8dacd5ec7b
--- /dev/null
+++ b/include/mbgl/osx/MGLMapView+IBAdditions.h
@@ -0,0 +1,44 @@
+#import <Foundation/Foundation.h>
+
+#import "MGLMapView.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface MGLMapView (IBAdditions)
+
+// Core properties that can be manipulated in the Attributes inspector in
+// Interface Builder. These redeclarations merely add the IBInspectable keyword.
+// They appear here to ensure that they appear above the convenience properties;
+// inspectables declared in MGLMapView.h are always sorted before those in
+// MGLMapView+IBAdditions.h, due to ASCII sort order.
+
+#if TARGET_INTERFACE_BUILDER
+// We want this property to look like a URL bar in the Attributes inspector, but
+// just calling it styleURL would violate Cocoa naming conventions and conflict
+// with the existing NSURL property. Fortunately, IB strips out the two
+// underscores for display.
+@property (nonatomic, nullable) IBInspectable NSString *styleURL__;
+#endif
+
+// Convenience properties related to the initial viewport. These properties
+// are not meant to be used outside of Interface Builder. latitude and longitude
+// are backed by properties of type CLLocationDegrees, but these declarations
+// must use the type double because Interface Builder is unaware that
+// CLLocationDegrees is a typedef for double.
+
+@property (nonatomic) IBInspectable double latitude;
+@property (nonatomic) IBInspectable double longitude;
+@property (nonatomic) IBInspectable double zoomLevel;
+
+// Renamed properties. Interface Builder derives the display name of each
+// inspectable from the runtime name, but runtime names don’t always make sense
+// in UI.
+
+@property (nonatomic) IBInspectable BOOL allowsZooming;
+@property (nonatomic) IBInspectable BOOL allowsScrolling;
+@property (nonatomic) IBInspectable BOOL allowsRotating;
+@property (nonatomic) IBInspectable BOOL allowsTilting;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/include/mbgl/osx/MGLMapView.h b/include/mbgl/osx/MGLMapView.h
new file mode 100644
index 0000000000..27c67bd38e
--- /dev/null
+++ b/include/mbgl/osx/MGLMapView.h
@@ -0,0 +1,85 @@
+#import <Cocoa/Cocoa.h>
+#import <CoreLocation/CoreLocation.h>
+
+#import "MGLGeometry.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+typedef NS_OPTIONS(NSUInteger, MGLMapDebugMaskOptions) {
+ MGLMapDebugTileBoundariesMask = 1 << 1,
+ MGLMapDebugTileInfoMask = 1 << 2,
+ MGLMapDebugTimestampsMask = 1 << 3,
+ MGLMapDebugCollisionBoxesMask = 1 << 4,
+};
+
+@class MGLAnnotationImage;
+
+@protocol MGLAnnotation;
+@protocol MGLMapViewDelegate;
+@protocol MGLOverlay;
+
+IB_DESIGNABLE
+@interface MGLMapView : NSView
+
+- (instancetype)initWithFrame:(CGRect)frame styleURL:(nullable NSURL *)styleURL;
+
+@property (nonatomic, weak, nullable) IBOutlet id <MGLMapViewDelegate> delegate;
+
+@property (nonatomic, null_resettable) NSURL *styleURL;
+
+- (IBAction)reloadStyle:(id)sender;
+
+@property (nonatomic, readonly) NSSegmentedControl *zoomControls;
+@property (nonatomic, readonly) NSSlider *compass;
+@property (nonatomic, readonly) NSImageView *logoView;
+@property (nonatomic, readonly) NSView *attributionView;
+
+@property (nonatomic) CLLocationCoordinate2D centerCoordinate;
+
+- (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated;
+
+@property (nonatomic) double zoomLevel;
+@property (nonatomic, readonly) double maximumZoomLevel;
+@property (nonatomic, readonly) double minimumZoomLevel;
+
+- (void)setZoomLevel:(double)zoomLevel animated:(BOOL)animated;
+
+@property (nonatomic) CLLocationDirection direction;
+
+- (void)setDirection:(CLLocationDirection)direction animated:(BOOL)animated;
+
+@property (nonatomic) MGLCoordinateBounds visibleCoordinateBounds;
+
+@property (nonatomic, getter=isScrollEnabled) BOOL scrollEnabled;
+@property (nonatomic, getter=isZoomEnabled) BOOL zoomEnabled;
+@property (nonatomic, getter=isRotateEnabled) BOOL rotateEnabled;
+@property (nonatomic, getter=isPitchEnabled) BOOL pitchEnabled;
+
+@property (nonatomic, readonly, nullable) NS_ARRAY_OF(id <MGLAnnotation>) *annotations;
+
+- (void)addAnnotation:(id <MGLAnnotation>)annotation;
+- (void)addAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)annotations;
+- (void)removeAnnotation:(id <MGLAnnotation>)annotation;
+- (void)removeAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)annotations;
+
+- (nullable MGLAnnotationImage *)dequeueReusableAnnotationImageWithIdentifier:(NSString *)identifier;
+
+@property (nonatomic, copy) NS_ARRAY_OF(id <MGLAnnotation>) *selectedAnnotations;
+
+- (void)selectAnnotation:(id <MGLAnnotation>)annotation animated:(BOOL)animated;
+- (void)deselectAnnotation:(id <MGLAnnotation>)annotation animated:(BOOL)animated;
+
+- (void)addOverlay:(id <MGLOverlay>)overlay;
+- (void)addOverlays:(NS_ARRAY_OF(id <MGLOverlay>) *)overlays;
+- (void)removeOverlay:(id <MGLOverlay>)overlay;
+- (void)removeOverlays:(NS_ARRAY_OF(id <MGLOverlay>) *)overlays;
+
+- (CLLocationCoordinate2D)convertPoint:(NSPoint)point toCoordinateFromView:(nullable NSView *)view;
+- (NSPoint)convertCoordinate:(CLLocationCoordinate2D)coordinate toPointToView:(nullable NSView *)view;
+- (CLLocationDistance)metersPerPixelAtLatitude:(CLLocationDegrees)latitude;
+
+@property (nonatomic) MGLMapDebugMaskOptions debugMask;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/include/mbgl/osx/MGLMapViewDelegate.h b/include/mbgl/osx/MGLMapViewDelegate.h
new file mode 100644
index 0000000000..c2de6ec52c
--- /dev/null
+++ b/include/mbgl/osx/MGLMapViewDelegate.h
@@ -0,0 +1,42 @@
+#import <Foundation/Foundation.h>
+
+#import "MGLTypes.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class MGLMapView;
+@class MGLAnnotationImage;
+@class MGLPolygon;
+@class MGLPolyline;
+@class MGLShape;
+
+@protocol MGLMapViewDelegate <NSObject>
+
+@optional
+
+- (void)mapView:(MGLMapView *)mapView regionWillChangeAnimated:(BOOL)animated;
+- (void)mapViewRegionIsChanging:(MGLMapView *)mapView;
+- (void)mapView:(MGLMapView *)mapView regionDidChangeAnimated:(BOOL)animated;
+
+- (void)mapViewWillStartLoadingMap:(MGLMapView *)mapView;
+- (void)mapViewDidFinishLoadingMap:(MGLMapView *)mapView;
+
+- (void)mapViewWillStartRenderingMap:(MGLMapView *)mapView;
+- (void)mapViewDidFinishRenderingMap:(MGLMapView *)mapView fullyRendered:(BOOL)fullyRendered;
+- (void)mapViewWillStartRenderingFrame:(MGLMapView *)mapView;
+- (void)mapViewDidFinishRenderingFrame:(MGLMapView *)mapView fullyRendered:(BOOL)fullyRendered;
+
+- (nullable MGLAnnotationImage *)mapView:(MGLMapView *)mapView imageForAnnotation:(id <MGLAnnotation>)annotation;
+- (CGFloat)mapView:(MGLMapView *)mapView alphaForShapeAnnotation:(MGLShape *)annotation;
+- (NSColor *)mapView:(MGLMapView *)mapView strokeColorForShapeAnnotation:(MGLShape *)annotation;
+- (NSColor *)mapView:(MGLMapView *)mapView fillColorForPolygonAnnotation:(MGLPolygon *)annotation;
+- (CGFloat)mapView:(MGLMapView *)mapView lineWidthForPolylineAnnotation:(MGLPolyline *)annotation;
+
+- (BOOL)mapView:(MGLMapView *)mapView annotationCanShowCallout:(id <MGLAnnotation>)annotation;
+- (nullable NSViewController *)mapView:(MGLMapView *)mapView calloutViewControllerForAnnotation:(id <MGLAnnotation>)annotation;
+- (void)mapView:(MGLMapView *)mapView didSelectAnnotation:(id <MGLAnnotation>)annotation;
+- (void)mapView:(MGLMapView *)mapView didDeselectAnnotation:(id <MGLAnnotation>)annotation;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/include/mbgl/osx/Mapbox.h b/include/mbgl/osx/Mapbox.h
new file mode 100644
index 0000000000..57d11bcaa9
--- /dev/null
+++ b/include/mbgl/osx/Mapbox.h
@@ -0,0 +1,15 @@
+#import "MGLAccountManager.h"
+#import "MGLAnnotation.h"
+#import "MGLAnnotationImage.h"
+#import "MGLGeometry.h"
+#import "MGLMapView.h"
+#import "MGLMapView+IBAdditions.h"
+#import "MGLMapViewDelegate.h"
+#import "MGLMultiPoint.h"
+#import "MGLOverlay.h"
+#import "MGLPointAnnotation.h"
+#import "MGLPolygon.h"
+#import "MGLPolyline.h"
+#import "MGLShape.h"
+#import "MGLStyle.h"
+#import "MGLTypes.h"
diff --git a/ios/app/app-info.plist b/ios/app/app-info.plist
index d497d09e76..309702d990 100644
--- a/ios/app/app-info.plist
+++ b/ios/app/app-info.plist
@@ -39,8 +39,10 @@
</dict>
<key>NSHumanReadableCopyright</key>
<string>(c) 2014 Mapbox</string>
+ <key>NSLocationAlwaysUsageDescription</key>
+ <string>The map will ALWAYS display the user's location.</string>
<key>NSLocationWhenInUseUsageDescription</key>
- <string>The map will display the user&apos;s location.</string>
+ <string>The map will display the user's location.</string>
<key>UILaunchStoryboardName</key>
<string>Default</string>
<key>UIRequiredDeviceCapabilities</key>
@@ -60,7 +62,5 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
- <key>NSLocationAlwaysUsageDescription</key>
- <string>The map will ALWAYS display the user&apos;s location.</string>
</dict>
</plist>
diff --git a/platform/ios/MGLGeometry.m b/platform/darwin/MGLGeometry.m
index 9eab5565fa..9eab5565fa 100644
--- a/platform/ios/MGLGeometry.m
+++ b/platform/darwin/MGLGeometry.m
diff --git a/platform/darwin/MGLGeometry_Private.h b/platform/darwin/MGLGeometry_Private.h
new file mode 100644
index 0000000000..25f222e3ce
--- /dev/null
+++ b/platform/darwin/MGLGeometry_Private.h
@@ -0,0 +1,26 @@
+#import "MGLGeometry.h"
+
+#include <mbgl/util/geo.hpp>
+
+NS_INLINE mbgl::LatLng MGLLatLngFromLocationCoordinate2D(CLLocationCoordinate2D coordinate) {
+ return mbgl::LatLng(coordinate.latitude, coordinate.longitude);
+}
+
+NS_INLINE CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) {
+ return CLLocationCoordinate2DMake(latLng.latitude, latLng.longitude);
+}
+
+NS_INLINE MGLCoordinateBounds MGLCoordinateBoundsFromLatLngBounds(mbgl::LatLngBounds latLngBounds) {
+ return MGLCoordinateBoundsMake(MGLLocationCoordinate2DFromLatLng(latLngBounds.sw),
+ MGLLocationCoordinate2DFromLatLng(latLngBounds.ne));
+}
+
+NS_INLINE mbgl::LatLngBounds MGLLatLngBoundsFromCoordinateBounds(MGLCoordinateBounds coordinateBounds) {
+ return mbgl::LatLngBounds(MGLLatLngFromLocationCoordinate2D(coordinateBounds.sw),
+ MGLLatLngFromLocationCoordinate2D(coordinateBounds.ne));
+}
+
+NS_INLINE BOOL MGLCoordinateInCoordinateBounds(CLLocationCoordinate2D coordinate, MGLCoordinateBounds coordinateBounds) {
+ mbgl::LatLngBounds bounds = MGLLatLngBoundsFromCoordinateBounds(coordinateBounds);
+ return bounds.contains(MGLLatLngFromLocationCoordinate2D(coordinate));
+}
diff --git a/platform/ios/MGLMultiPoint.mm b/platform/darwin/MGLMultiPoint.mm
index 702932f066..fd27cf7819 100644
--- a/platform/ios/MGLMultiPoint.mm
+++ b/platform/darwin/MGLMultiPoint.mm
@@ -1,8 +1,17 @@
-#import "MGLMultiPoint.h"
-#import "MGLGeometry.h"
+#import "MGLMultiPoint_Private.h"
+#import "MGLGeometry_Private.h"
#import <mbgl/util/geo.hpp>
+mbgl::Color MGLColorObjectFromCGColorRef(CGColorRef cgColor) {
+ if (!cgColor) {
+ return {{ 0, 0, 0, 0 }};
+ }
+ NSCAssert(CGColorGetNumberOfComponents(cgColor) >= 4, @"Color must have at least 4 components");
+ const CGFloat *components = CGColorGetComponents(cgColor);
+ return {{ (float)components[0], (float)components[1], (float)components[2], (float)components[3] }};
+}
+
@implementation MGLMultiPoint
{
CLLocationCoordinate2D *_coords;
@@ -100,4 +109,28 @@
return _bounds.intersects(area);
}
+- (void)addShapeAnnotationObjectToCollection:(std::vector<mbgl::ShapeAnnotation> &)shapes withDelegate:(id <MGLMultiPointDelegate>)delegate {
+ NSUInteger count = self.pointCount;
+ if (count == 0) {
+ return;
+ }
+
+ CLLocationCoordinate2D *coordinates = (CLLocationCoordinate2D *)malloc(count * sizeof(CLLocationCoordinate2D));
+ NSAssert(coordinates, @"Unable to allocate annotation with %lu points", (unsigned long)count);
+ [self getCoordinates:coordinates range:NSMakeRange(0, count)];
+
+ mbgl::AnnotationSegment segment;
+ segment.reserve(count);
+ for (NSUInteger i = 0; i < count; i++) {
+ segment.push_back(MGLLatLngFromLocationCoordinate2D(coordinates[i]));
+ }
+ free(coordinates);
+ shapes.emplace_back(mbgl::AnnotationSegments {{ segment }},
+ [self shapeAnnotationPropertiesObjectWithDelegate:delegate]);
+}
+
+- (mbgl::ShapeAnnotation::Properties)shapeAnnotationPropertiesObjectWithDelegate:(__unused id <MGLMultiPointDelegate>)delegate {
+ return mbgl::ShapeAnnotation::Properties();
+}
+
@end
diff --git a/platform/darwin/MGLMultiPoint_Private.h b/platform/darwin/MGLMultiPoint_Private.h
new file mode 100644
index 0000000000..e0d875d88a
--- /dev/null
+++ b/platform/darwin/MGLMultiPoint_Private.h
@@ -0,0 +1,38 @@
+#import "MGLMultiPoint.h"
+
+#import "MGLGeometry.h"
+#import "MGLTypes.h"
+
+#import <mbgl/annotation/shape_annotation.hpp>
+#import <vector>
+
+#import <CoreGraphics/CoreGraphics.h>
+#import <CoreLocation/CoreLocation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class MGLPolygon;
+@class MGLPolyline;
+
+@protocol MGLMultiPointDelegate;
+
+@interface MGLMultiPoint (Private)
+
+- (instancetype)initWithCoordinates:(CLLocationCoordinate2D *)coords count:(NSUInteger)count;
+- (BOOL)intersectsOverlayBounds:(MGLCoordinateBounds)overlayBounds;
+
+- (void)addShapeAnnotationObjectToCollection:(std::vector<mbgl::ShapeAnnotation> &)shapes withDelegate:(id <MGLMultiPointDelegate>)delegate;
+- (mbgl::ShapeAnnotation::Properties)shapeAnnotationPropertiesObjectWithDelegate:(id <MGLMultiPointDelegate>)delegate;
+
+@end
+
+@protocol MGLMultiPointDelegate <NSObject>
+
+- (double)alphaForShapeAnnotation:(MGLShape *)annotation;
+- (mbgl::Color)strokeColorForShapeAnnotation:(MGLShape *)annotation;
+- (mbgl::Color)fillColorForPolygonAnnotation:(MGLPolygon *)annotation;
+- (CGFloat)lineWidthForPolylineAnnotation:(MGLPolyline *)annotation;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/platform/ios/MGLPointAnnotation.m b/platform/darwin/MGLPointAnnotation.m
index 13fbba1083..13fbba1083 100644
--- a/platform/ios/MGLPointAnnotation.m
+++ b/platform/darwin/MGLPointAnnotation.m
diff --git a/platform/darwin/MGLPolygon.mm b/platform/darwin/MGLPolygon.mm
new file mode 100644
index 0000000000..5019385cb2
--- /dev/null
+++ b/platform/darwin/MGLPolygon.mm
@@ -0,0 +1,28 @@
+#import "MGLPolygon.h"
+
+#import "MGLMultiPoint_Private.h"
+
+@implementation MGLPolygon
+
+@dynamic overlayBounds;
+
++ (instancetype)polygonWithCoordinates:(CLLocationCoordinate2D *)coords
+ count:(NSUInteger)count
+{
+ return [[self alloc] initWithCoordinates:coords count:count];
+}
+
+- (mbgl::ShapeAnnotation::Properties)shapeAnnotationPropertiesObjectWithDelegate:(id <MGLMultiPointDelegate>)delegate {
+ mbgl::ShapeAnnotation::Properties shapeProperties = [super shapeAnnotationPropertiesObjectWithDelegate:delegate];
+
+ mbgl::FillAnnotationProperties fillProperties;
+ fillProperties.opacity = [delegate alphaForShapeAnnotation:self];
+ fillProperties.outlineColor = [delegate strokeColorForShapeAnnotation:self];
+ fillProperties.color = [delegate fillColorForPolygonAnnotation:self];
+
+ shapeProperties.set<mbgl::FillAnnotationProperties>(fillProperties);
+
+ return shapeProperties;
+}
+
+@end
diff --git a/platform/darwin/MGLPolyline.mm b/platform/darwin/MGLPolyline.mm
new file mode 100644
index 0000000000..f560a571bc
--- /dev/null
+++ b/platform/darwin/MGLPolyline.mm
@@ -0,0 +1,28 @@
+#import "MGLPolyline.h"
+
+#import "MGLMultiPoint_Private.h"
+
+@implementation MGLPolyline
+
+@dynamic overlayBounds;
+
++ (instancetype)polylineWithCoordinates:(CLLocationCoordinate2D *)coords
+ count:(NSUInteger)count
+{
+ return [[self alloc] initWithCoordinates:coords count:count];
+}
+
+- (mbgl::ShapeAnnotation::Properties)shapeAnnotationPropertiesObjectWithDelegate:(id <MGLMultiPointDelegate>)delegate {
+ mbgl::ShapeAnnotation::Properties shapeProperties = [super shapeAnnotationPropertiesObjectWithDelegate:delegate];
+
+ mbgl::LineAnnotationProperties lineProperties;
+ lineProperties.opacity = [delegate alphaForShapeAnnotation:self];
+ lineProperties.color = [delegate strokeColorForShapeAnnotation:self];
+ lineProperties.width = [delegate lineWidthForPolylineAnnotation:self];
+
+ shapeProperties.set<mbgl::LineAnnotationProperties>(lineProperties);
+
+ return shapeProperties;
+}
+
+@end
diff --git a/platform/ios/MGLShape.m b/platform/darwin/MGLShape.m
index e3d92c38c8..e3d92c38c8 100644
--- a/platform/ios/MGLShape.m
+++ b/platform/darwin/MGLShape.m
diff --git a/platform/ios/MGLStyle.mm b/platform/darwin/MGLStyle.mm
index 15a25db9e3..15a25db9e3 100644
--- a/platform/ios/MGLStyle.mm
+++ b/platform/darwin/MGLStyle.mm
diff --git a/platform/ios/MGLTypes.m b/platform/darwin/MGLTypes.m
index 01e9a1467c..01e9a1467c 100644
--- a/platform/ios/MGLTypes.m
+++ b/platform/darwin/MGLTypes.m
diff --git a/platform/ios/NSException+MGLAdditions.h b/platform/darwin/NSException+MGLAdditions.h
index f75b54c15c..f75b54c15c 100644
--- a/platform/ios/NSException+MGLAdditions.h
+++ b/platform/darwin/NSException+MGLAdditions.h
diff --git a/platform/ios/NSString+MGLAdditions.h b/platform/darwin/NSString+MGLAdditions.h
index 6064f8b40f..6064f8b40f 100644
--- a/platform/ios/NSString+MGLAdditions.h
+++ b/platform/darwin/NSString+MGLAdditions.h
diff --git a/platform/ios/NSString+MGLAdditions.m b/platform/darwin/NSString+MGLAdditions.m
index 284c63f5a9..b94a5f0198 100644
--- a/platform/ios/NSString+MGLAdditions.m
+++ b/platform/darwin/NSString+MGLAdditions.m
@@ -1,8 +1,8 @@
#import "NSString+MGLAdditions.h"
-@implementation NSString (MGLAdditions)
+void mgl_linkStringCategory() {}
-void mgl_linkStringCategory(){}
+@implementation NSString (MGLAdditions)
- (nullable NSString *)mgl_stringOrNilIfEmpty
{
diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm
index 092750cc16..63cca9210e 100644
--- a/platform/ios/MGLMapView.mm
+++ b/platform/ios/MGLMapView.mm
@@ -26,6 +26,8 @@
#include <mbgl/util/default_styles.hpp>
#import "Mapbox.h"
+#import "../darwin/MGLGeometry_Private.h"
+#import "../darwin/MGLMultiPoint_Private.h"
#import "NSBundle+MGLAdditions.h"
#import "NSString+MGLAdditions.h"
@@ -79,34 +81,6 @@ enum { MGLAnnotationTagNotFound = UINT32_MAX };
/// the annotation itself.
typedef std::map<MGLAnnotationTag, MGLAnnotationContext> MGLAnnotationContextMap;
-mbgl::LatLng MGLLatLngFromLocationCoordinate2D(CLLocationCoordinate2D coordinate)
-{
- return mbgl::LatLng(coordinate.latitude, coordinate.longitude);
-}
-
-CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng)
-{
- return CLLocationCoordinate2DMake(latLng.latitude, latLng.longitude);
-}
-
-MGLCoordinateBounds MGLCoordinateBoundsFromLatLngBounds(mbgl::LatLngBounds latLngBounds)
-{
- return MGLCoordinateBoundsMake(MGLLocationCoordinate2DFromLatLng(latLngBounds.sw),
- MGLLocationCoordinate2DFromLatLng(latLngBounds.ne));
-}
-
-mbgl::LatLngBounds MGLLatLngBoundsFromCoordinateBounds(MGLCoordinateBounds coordinateBounds)
-{
- return mbgl::LatLngBounds(MGLLatLngFromLocationCoordinate2D(coordinateBounds.sw),
- MGLLatLngFromLocationCoordinate2D(coordinateBounds.ne));
-}
-
-BOOL MGLCoordinateInCoordinateBounds(CLLocationCoordinate2D coordinate, MGLCoordinateBounds coordinateBounds)
-{
- mbgl::LatLngBounds bounds = MGLLatLngBoundsFromCoordinateBounds(coordinateBounds);
- return bounds.contains(MGLLatLngFromLocationCoordinate2D(coordinate));
-}
-
mbgl::util::UnitBezier MGLUnitBezierForMediaTimingFunction(CAMediaTimingFunction *function)
{
if ( ! function)
@@ -119,6 +93,17 @@ mbgl::util::UnitBezier MGLUnitBezierForMediaTimingFunction(CAMediaTimingFunction
return { p1[0], p1[1], p2[0], p2[1] };
}
+mbgl::Color MGLColorObjectFromUIColor(UIColor *color)
+{
+ if (!color)
+ {
+ return {{ 0, 0, 0, 0 }};
+ }
+ CGFloat r, g, b, a;
+ [color getRed:&r green:&g blue:&b alpha:&a];
+ return {{ (float)r, (float)g, (float)b, (float)a }};
+}
+
/// Lightweight container for metadata about an annotation, including the annotation itself.
class MGLAnnotationContext {
public:
@@ -134,7 +119,8 @@ public:
GLKViewDelegate,
CLLocationManagerDelegate,
UIActionSheetDelegate,
- SMCalloutViewDelegate>
+ SMCalloutViewDelegate,
+ MGLMultiPointDelegate>
@property (nonatomic) EAGLContext *context;
@property (nonatomic) GLKView *glView;
@@ -192,13 +178,18 @@ public:
BOOL _needsDisplayRefresh;
NSUInteger _changeDelimiterSuppressionDepth;
+
+ BOOL _delegateHasAlphasForShapeAnnotations;
+ BOOL _delegateHasStrokeColorsForShapeAnnotations;
+ BOOL _delegateHasFillColorsForShapeAnnotations;
+ BOOL _delegateHasLineWidthsForShapeAnnotations;
}
#pragma mark - Setup & Teardown -
@dynamic debugActive;
-std::chrono::steady_clock::duration durationInSeconds(float duration)
+std::chrono::steady_clock::duration MGLDurationInSeconds(float duration)
{
return std::chrono::duration_cast<std::chrono::steady_clock::duration>(std::chrono::duration<float, std::chrono::seconds::period>(duration));
}
@@ -538,6 +529,16 @@ std::chrono::steady_clock::duration durationInSeconds(float duration)
@"Implement -[%@ mapView:imageForAnnotation:] instead.",
NSStringFromClass([delegate class]), NSStringFromClass([delegate class])];
}
+
+ _delegateHasAlphasForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:alphaForShapeAnnotation:)];
+ _delegateHasStrokeColorsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:strokeColorForShapeAnnotation:)];
+ _delegateHasFillColorsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:fillColorForPolygonAnnotation:)];
+ _delegateHasLineWidthsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:lineWidthForPolylineAnnotation:)];
+}
+
+- (void)didReceiveMemoryWarning
+{
+ _mbglMap->onLowMemory();
}
#pragma mark - Layout -
@@ -949,7 +950,7 @@ std::chrono::steady_clock::duration durationInSeconds(float duration)
if (drift)
{
CGPoint offset = CGPointMake(velocity.x * duration / 4, velocity.y * duration / 4);
- _mbglMap->moveBy({ offset.x, offset.y }, durationInSeconds(duration));
+ _mbglMap->moveBy({ offset.x, offset.y }, MGLDurationInSeconds(duration));
}
[self notifyGestureDidEndWithDrift:drift];
@@ -1031,7 +1032,7 @@ std::chrono::steady_clock::duration durationInSeconds(float duration)
{
CGPoint pinchCenter = [pinch locationInView:pinch.view];
mbgl::PrecisionPoint center(pinchCenter.x, pinchCenter.y);
- _mbglMap->setScale(newScale, center, durationInSeconds(duration));
+ _mbglMap->setScale(newScale, center, MGLDurationInSeconds(duration));
}
[self notifyGestureDidEndWithDrift:velocity];
@@ -1085,7 +1086,7 @@ std::chrono::steady_clock::duration durationInSeconds(float duration)
CGFloat newRadians = radians + velocity * duration * 0.1;
CGFloat newDegrees = MGLDegreesFromRadians(newRadians) * -1;
- _mbglMap->setBearing(newDegrees, durationInSeconds(duration));
+ _mbglMap->setBearing(newDegrees, MGLDurationInSeconds(duration));
[self notifyGestureDidEndWithDrift:YES];
@@ -1178,7 +1179,7 @@ std::chrono::steady_clock::duration durationInSeconds(float duration)
}
mbgl::PrecisionPoint center(zoomInPoint.x, zoomInPoint.y);
- _mbglMap->scaleBy(2, center, durationInSeconds(MGLAnimationDuration));
+ _mbglMap->scaleBy(2, center, MGLDurationInSeconds(MGLAnimationDuration));
__weak MGLMapView *weakSelf = self;
@@ -1217,7 +1218,7 @@ std::chrono::steady_clock::duration durationInSeconds(float duration)
}
mbgl::PrecisionPoint center(zoomOutPoint.x, zoomOutPoint.y);
- _mbglMap->scaleBy(0.5, center, durationInSeconds(MGLAnimationDuration));
+ _mbglMap->scaleBy(0.5, center, MGLDurationInSeconds(MGLAnimationDuration));
__weak MGLMapView *weakSelf = self;
@@ -1538,7 +1539,7 @@ std::chrono::steady_clock::duration durationInSeconds(float duration)
}
if (animated)
{
- options.duration = durationInSeconds(duration);
+ options.duration = MGLDurationInSeconds(duration);
options.easing = MGLUnitBezierForMediaTimingFunction(nil);
}
if (completion)
@@ -1655,7 +1656,7 @@ std::chrono::steady_clock::duration durationInSeconds(float duration)
}
if (duration > 0)
{
- options.duration = durationInSeconds(duration);
+ options.duration = MGLDurationInSeconds(duration);
options.easing = MGLUnitBezierForMediaTimingFunction(function);
}
if (completion)
@@ -1702,7 +1703,7 @@ std::chrono::steady_clock::duration durationInSeconds(float duration)
CGFloat duration = animated ? MGLAnimationDuration : 0;
- _mbglMap->setBearing(direction, durationInSeconds(duration));
+ _mbglMap->setBearing(direction, MGLDurationInSeconds(duration));
}
- (void)setDirection:(CLLocationDirection)direction
@@ -1856,7 +1857,7 @@ std::chrono::steady_clock::duration durationInSeconds(float duration)
}
if (duration > 0)
{
- options.duration = durationInSeconds(duration);
+ options.duration = MGLDurationInSeconds(duration);
options.easing = MGLUnitBezierForMediaTimingFunction(function);
}
if (completion)
@@ -2016,7 +2017,7 @@ std::chrono::steady_clock::duration durationInSeconds(float duration)
newAppliedClasses.insert(newAppliedClasses.end(), [appliedClass UTF8String]);
}
- _mbglMap->setDefaultTransitionDuration(durationInSeconds(transitionDuration));
+ _mbglMap->setDefaultTransitionDuration(MGLDurationInSeconds(transitionDuration));
_mbglMap->setClasses(newAppliedClasses);
}
@@ -2111,10 +2112,6 @@ std::chrono::steady_clock::duration durationInSeconds(float duration)
std::vector<mbgl::ShapeAnnotation> shapes;
BOOL delegateImplementsImageForPoint = [self.delegate respondsToSelector:@selector(mapView:imageForAnnotation:)];
- BOOL delegateImplementsAlphaForShape = [self.delegate respondsToSelector:@selector(mapView:alphaForShapeAnnotation:)];
- BOOL delegateImplementsStrokeColorForShape = [self.delegate respondsToSelector:@selector(mapView:strokeColorForShapeAnnotation:)];
- BOOL delegateImplementsFillColorForPolygon = [self.delegate respondsToSelector:@selector(mapView:fillColorForPolygonAnnotation:)];
- BOOL delegateImplementsLineWidthForPolyline = [self.delegate respondsToSelector:@selector(mapView:lineWidthForPolylineAnnotation:)];
for (id <MGLAnnotation> annotation in annotations)
{
@@ -2122,77 +2119,7 @@ std::chrono::steady_clock::duration durationInSeconds(float duration)
if ([annotation isKindOfClass:[MGLMultiPoint class]])
{
- NSUInteger count = [(MGLMultiPoint *)annotation pointCount];
-
- if (count == 0) break;
-
- CGFloat alpha = (delegateImplementsAlphaForShape ?
- [self.delegate mapView:self alphaForShapeAnnotation:annotation] :
- 1.0);
-
- UIColor *strokeColor = (delegateImplementsStrokeColorForShape ?
- [self.delegate mapView:self strokeColorForShapeAnnotation:annotation] :
- [UIColor blackColor]);
-
- assert(strokeColor);
-
- CGFloat r,g,b,a;
- [strokeColor getRed:&r green:&g blue:&b alpha:&a];
- mbgl::Color strokeNativeColor({{ (float)r, (float)g, (float)b, (float)a }});
-
- mbgl::ShapeAnnotation::Properties shapeProperties;
-
- if ([annotation isKindOfClass:[MGLPolyline class]])
- {
- CGFloat lineWidth = (delegateImplementsLineWidthForPolyline ?
- [self.delegate mapView:self lineWidthForPolylineAnnotation:(MGLPolyline *)annotation] :
- 3.0);
-
- mbgl::LineAnnotationProperties lineProperties;
- lineProperties.opacity = alpha;
- lineProperties.color = strokeNativeColor;
- lineProperties.width = lineWidth;
- shapeProperties.set<mbgl::LineAnnotationProperties>(lineProperties);
-
- }
- else if ([annotation isKindOfClass:[MGLPolygon class]])
- {
- UIColor *fillColor = (delegateImplementsFillColorForPolygon ?
- [self.delegate mapView:self fillColorForPolygonAnnotation:(MGLPolygon *)annotation] :
- [UIColor blueColor]);
-
- assert(fillColor);
-
- [fillColor getRed:&r green:&g blue:&b alpha:&a];
- mbgl::Color fillNativeColor({{ (float)r, (float)g, (float)b, (float)a }});
-
- mbgl::FillAnnotationProperties fillProperties;
- fillProperties.opacity = alpha;
- fillProperties.outlineColor = strokeNativeColor;
- fillProperties.color = fillNativeColor;
- shapeProperties.set<mbgl::FillAnnotationProperties>(fillProperties);
- }
- else
- {
- [[NSException exceptionWithName:@"MGLUnknownShapeClassException"
- reason:[NSString stringWithFormat:@"%@ is an unknown shape class", [annotation class]]
- userInfo:nil] raise];
- }
-
- CLLocationCoordinate2D *coordinates = (CLLocationCoordinate2D *)malloc(count * sizeof(CLLocationCoordinate2D));
- [(MGLMultiPoint *)annotation getCoordinates:coordinates range:NSMakeRange(0, count)];
-
- mbgl::AnnotationSegment segment;
- segment.reserve(count);
-
- for (NSUInteger i = 0; i < count; i++)
- {
- segment.push_back(mbgl::LatLng(coordinates[i].latitude, coordinates[i].longitude));
- }
-
- free(coordinates);
-
- shapes.emplace_back(mbgl::AnnotationSegments {{ segment }}, shapeProperties);
+ [(MGLMultiPoint *)annotation addShapeAnnotationObjectToCollection:shapes withDelegate:self];
}
else
{
@@ -2253,6 +2180,40 @@ std::chrono::steady_clock::duration durationInSeconds(float duration)
[self didChangeValueForKey:@"annotations"];
}
+- (double)alphaForShapeAnnotation:(MGLShape *)annotation
+{
+ if (_delegateHasAlphasForShapeAnnotations)
+ {
+ return [self.delegate mapView:self alphaForShapeAnnotation:annotation];
+ }
+ return 1.0;
+}
+
+- (mbgl::Color)strokeColorForShapeAnnotation:(MGLShape *)annotation
+{
+ UIColor *color = (_delegateHasStrokeColorsForShapeAnnotations
+ ? [self.delegate mapView:self strokeColorForShapeAnnotation:annotation]
+ : [UIColor blackColor]);
+ return MGLColorObjectFromUIColor(color);
+}
+
+- (mbgl::Color)fillColorForPolygonAnnotation:(MGLPolygon *)annotation
+{
+ UIColor *color = (_delegateHasFillColorsForShapeAnnotations
+ ? [self.delegate mapView:self fillColorForPolygonAnnotation:annotation]
+ : [UIColor blueColor]);
+ return MGLColorObjectFromUIColor(color);
+}
+
+- (CGFloat)lineWidthForPolylineAnnotation:(MGLPolyline *)annotation
+{
+ if (_delegateHasLineWidthsForShapeAnnotations)
+ {
+ return [self.delegate mapView:self lineWidthForPolylineAnnotation:(MGLPolyline *)annotation];
+ }
+ return 3.0;
+}
+
- (void)installAnnotationImage:(MGLAnnotationImage *)annotationImage
{
// retrieve pixels
@@ -3612,9 +3573,4 @@ class MBGLView : public mbgl::View
self.pitchEnabled = allowsTilting;
}
-- (void)didReceiveMemoryWarning
-{
- _mbglMap->onLowMemory();
-}
-
@end
diff --git a/platform/ios/MGLMultiPoint_Private.h b/platform/ios/MGLMultiPoint_Private.h
deleted file mode 100644
index fe57064a90..0000000000
--- a/platform/ios/MGLMultiPoint_Private.h
+++ /dev/null
@@ -1,10 +0,0 @@
-#import "MGLMultiPoint.h"
-
-#import <CoreLocation/CoreLocation.h>
-
-@interface MGLMultiPoint (Private)
-
-- (instancetype)initWithCoordinates:(CLLocationCoordinate2D *)coords count:(NSUInteger)count;
-- (BOOL)intersectsOverlayBounds:(MGLCoordinateBounds)overlayBounds;
-
-@end
diff --git a/platform/ios/MGLPolygon.m b/platform/ios/MGLPolygon.m
deleted file mode 100644
index 770a2a4b95..0000000000
--- a/platform/ios/MGLPolygon.m
+++ /dev/null
@@ -1,15 +0,0 @@
-#import "MGLPolygon.h"
-
-#import "MGLMultiPoint_Private.h"
-
-@implementation MGLPolygon
-
-@dynamic overlayBounds;
-
-+ (instancetype)polygonWithCoordinates:(CLLocationCoordinate2D *)coords
- count:(NSUInteger)count
-{
- return [[self alloc] initWithCoordinates:coords count:count];
-}
-
-@end
diff --git a/platform/ios/MGLPolyline.m b/platform/ios/MGLPolyline.m
deleted file mode 100644
index bcd652e0d3..0000000000
--- a/platform/ios/MGLPolyline.m
+++ /dev/null
@@ -1,15 +0,0 @@
-#import "MGLPolyline.h"
-
-#import "MGLMultiPoint_Private.h"
-
-@implementation MGLPolyline
-
-@dynamic overlayBounds;
-
-+ (instancetype)polylineWithCoordinates:(CLLocationCoordinate2D *)coords
- count:(NSUInteger)count
-{
- return [[self alloc] initWithCoordinates:coords count:count];
-}
-
-@end
diff --git a/platform/osx/Info.plist b/platform/osx/Info.plist
deleted file mode 100644
index 514db118ea..0000000000
--- a/platform/osx/Info.plist
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
- <key>CFBundleDevelopmentRegion</key>
- <string>English</string>
- <key>CFBundleExecutable</key>
- <string>${EXECUTABLE_NAME}</string>
- <key>CFBundleGetInfoString</key>
- <string>0.0.1</string>
- <key>CFBundleIconFile</key>
- <string>Icon.icns</string>
- <key>CFBundleIdentifier</key>
- <string>com.mapbox.${PRODUCT_NAME:rfc1034identifier}</string>
- <key>CFBundleInfoDictionaryVersion</key>
- <string>6.0</string>
- <key>CFBundleLongVersionString</key>
- <string>0.0.1</string>
- <key>CFBundleName</key>
- <string>${PRODUCT_NAME}</string>
- <key>CFBundlePackageType</key>
- <string>APPL</string>
- <key>CFBundleShortVersionString</key>
- <string>0.0.1</string>
- <key>CFBundleSignature</key>
- <string>MBGL</string>
- <key>CFBundleVersion</key>
- <string>0.0.1</string>
- <key>CSResourcesFileMapped</key>
- <true/>
- <key>NSHumanReadableCopyright</key>
- <string>0.0.1</string>
- <key>NSHighResolutionCapable</key>
- <true/>
- <key>CFBundleURLTypes</key>
- <array>
- <dict>
- <key>CFBundleURLName</key>
- <string>${PRODUCT_NAME}</string>
- <key>CFBundleURLSchemes</key>
- <array>
- <string>mapboxgl</string>
- </array>
- </dict>
- </array>
-</dict>
-</plist> \ No newline at end of file
diff --git a/platform/osx/app/AppDelegate.h b/platform/osx/app/AppDelegate.h
new file mode 100644
index 0000000000..69b6e0f13a
--- /dev/null
+++ b/platform/osx/app/AppDelegate.h
@@ -0,0 +1,7 @@
+#import <Cocoa/Cocoa.h>
+
+@interface AppDelegate : NSObject <NSApplicationDelegate>
+
+
+@end
+
diff --git a/platform/osx/app/AppDelegate.m b/platform/osx/app/AppDelegate.m
new file mode 100644
index 0000000000..4c8ce92ab7
--- /dev/null
+++ b/platform/osx/app/AppDelegate.m
@@ -0,0 +1,462 @@
+#import "AppDelegate.h"
+
+#import "DroppedPinAnnotation.h"
+#import "LocationCoordinate2DTransformer.h"
+#import "NSValue+Additions.h"
+
+#import <mbgl/osx/Mapbox.h>
+
+static NSString * const MGLMapboxAccessTokenDefaultsKey = @"MGLMapboxAccessToken";
+
+@interface AppDelegate () <NSApplicationDelegate, NSSharingServicePickerDelegate, NSMenuDelegate, MGLMapViewDelegate>
+
+@property (weak) IBOutlet NSWindow *window;
+@property (weak) IBOutlet MGLMapView *mapView;
+@property (weak) IBOutlet NSMenu *mapViewContextMenu;
+
+@property (weak) IBOutlet NSWindow *preferencesWindow;
+
+@end
+
+@implementation AppDelegate {
+ NSPoint _mouseLocationForMapViewContextMenu;
+}
+
+#pragma mark Lifecycle
+
++ (void)load {
+ // Set access token, unless MGLAccountManager already read it in from Info.plist.
+ if (![MGLAccountManager accessToken]) {
+ NSString *accessToken = [NSProcessInfo processInfo].environment[@"MAPBOX_ACCESS_TOKEN"];
+ if (accessToken) {
+ // Store to preferences so that we can launch the app later on without having to specify
+ // token.
+ [[NSUserDefaults standardUserDefaults] setObject:accessToken forKey:MGLMapboxAccessTokenDefaultsKey];
+ } else {
+ // Try to retrieve from preferences, maybe we've stored them there previously and can reuse
+ // the token.
+ accessToken = [[NSUserDefaults standardUserDefaults] stringForKey:MGLMapboxAccessTokenDefaultsKey];
+ }
+ [MGLAccountManager setAccessToken:accessToken];
+ }
+}
+
+- (void)applicationWillFinishLaunching:(NSNotification *)notification {
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(userDefaultsDidChange:)
+ name:NSUserDefaultsDidChangeNotification
+ object:nil];
+
+ [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self
+ andSelector:@selector(handleGetURLEvent:withReplyEvent:)
+ forEventClass:kInternetEventClass
+ andEventID:kAEGetURL];
+}
+
+- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
+ // Set access token, unless MGLAccountManager already read it in from Info.plist.
+ if (![MGLAccountManager accessToken]) {
+ NSAlert *alert = [[NSAlert alloc] init];
+ alert.messageText = @"Access token required";
+ alert.informativeText = @"To load Mapbox-hosted tiles and styles, enter your Mapbox access token in Preferences.";
+ [alert addButtonWithTitle:@"Open Preferences"];
+ [alert runModal];
+ [self showPreferences:nil];
+ }
+
+ NSPressGestureRecognizer *pressGestureRecognizer = [[NSPressGestureRecognizer alloc] initWithTarget:self action:@selector(handlePressGesture:)];
+ [self.mapView addGestureRecognizer:pressGestureRecognizer];
+}
+
+- (void)applicationWillTerminate:(NSNotification *)notification {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
+- (void)userDefaultsDidChange:(NSNotification *)notification {
+ NSUserDefaults *userDefaults = notification.object;
+ NSString *accessToken = [userDefaults stringForKey:MGLMapboxAccessTokenDefaultsKey];
+ if (![accessToken isEqualToString:[MGLAccountManager accessToken]]) {
+ [MGLAccountManager setAccessToken:accessToken];
+ [self reload:self];
+ }
+}
+
+#pragma mark Services
+
+- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent {
+ NSURL *url = [NSURL URLWithString:[event paramDescriptorForKeyword:keyDirectObject].stringValue];
+ NS_MUTABLE_DICTIONARY_OF(NSString *, NSString *) *params = [[NSMutableDictionary alloc] init];
+ for (NSString *param in [url.query componentsSeparatedByString:@"&"]) {
+ NSArray *parts = [param componentsSeparatedByString:@"="];
+ if (parts.count >= 2) {
+ params[parts[0]] = [parts[1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+ }
+ }
+
+ NSString *centerString = params[@"center"];
+ if (centerString) {
+ NSArray *coordinateValues = [centerString componentsSeparatedByString:@","];
+ if (coordinateValues.count == 2) {
+ self.mapView.centerCoordinate = CLLocationCoordinate2DMake([coordinateValues[0] doubleValue],
+ [coordinateValues[1] doubleValue]);
+ }
+ }
+
+ NSString *zoomLevelString = params[@"zoom"];
+ if (zoomLevelString.length) {
+ self.mapView.zoomLevel = zoomLevelString.doubleValue;
+ }
+
+ NSString *directionString = params[@"bearing"];
+ if (directionString.length) {
+ self.mapView.direction = directionString.doubleValue;
+ }
+}
+
+- (IBAction)showShareMenu:(id)sender {
+ NSSharingServicePicker *picker = [[NSSharingServicePicker alloc] initWithItems:@[self.shareURL]];
+ picker.delegate = self;
+ [picker showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMinYEdge];
+}
+
+- (NSURL *)shareURL {
+ NSArray *components = self.mapView.styleURL.pathComponents;
+ CLLocationCoordinate2D centerCoordinate = self.mapView.centerCoordinate;
+ return [NSURL URLWithString:
+ [NSString stringWithFormat:@"https://api.mapbox.com/styles/v1/%@/%@.html?access_token=%@#%.2f/%.5f/%.5f/%.f",
+ components[1], components[2], [MGLAccountManager accessToken],
+ self.mapView.zoomLevel, centerCoordinate.latitude, centerCoordinate.longitude, self.mapView.direction]];
+}
+
+#pragma mark View methods
+
+- (IBAction)setStyle:(id)sender {
+ NSInteger tag;
+ if ([sender isKindOfClass:[NSMenuItem class]]) {
+ tag = [sender tag];
+ } else if ([sender isKindOfClass:[NSPopUpButton class]]) {
+ tag = [sender selectedTag];
+ }
+ NSURL *styleURL;
+ switch (tag) {
+ case 1:
+ styleURL = [MGLStyle streetsStyleURL];
+ break;
+ case 2:
+ styleURL = [MGLStyle emeraldStyleURL];
+ break;
+ case 3:
+ styleURL = [MGLStyle lightStyleURL];
+ break;
+ case 4:
+ styleURL = [MGLStyle darkStyleURL];
+ break;
+ case 5:
+ styleURL = [MGLStyle satelliteStyleURL];
+ break;
+ case 6:
+ styleURL = [MGLStyle hybridStyleURL];
+ break;
+ default:
+ NSAssert(NO, @"Cannot set style from control with tag %li", (long)tag);
+ break;
+ }
+ self.mapView.styleURL = styleURL;
+ [self.window.toolbar validateVisibleItems];
+}
+
+- (IBAction)chooseCustomStyle:(id)sender {
+ NSAlert *alert = [[NSAlert alloc] init];
+ alert.messageText = @"Apply custom style";
+ alert.informativeText = @"Enter the URL to a JSON file that conforms to the Mapbox GL style specification:";
+ NSTextField *textField = [[NSTextField alloc] initWithFrame:NSZeroRect];
+ [textField sizeToFit];
+ NSRect textFieldFrame = textField.frame;
+ textFieldFrame.size.width = 300;
+ textField.frame = textFieldFrame;
+ NSString *savedURLString = [[NSUserDefaults standardUserDefaults] stringForKey:@"MBXCustomStyleURL"];
+ if (savedURLString) {
+ textField.stringValue = savedURLString;
+ }
+ alert.accessoryView = textField;
+ [alert addButtonWithTitle:@"Apply"];
+ [alert addButtonWithTitle:@"Cancel"];
+ if ([alert runModal] == NSAlertFirstButtonReturn) {
+ [[NSUserDefaults standardUserDefaults] setObject:textField.stringValue forKey:@"MBXCustomStyleURL"];
+ self.mapView.styleURL = [NSURL URLWithString:textField.stringValue];
+ [self.window.toolbar validateVisibleItems];
+ }
+}
+
+- (IBAction)zoomIn:(id)sender {
+ [self.mapView setZoomLevel:self.mapView.zoomLevel + 1 animated:YES];
+}
+
+- (IBAction)zoomOut:(id)sender {
+ [self.mapView setZoomLevel:self.mapView.zoomLevel - 1 animated:YES];
+}
+
+- (IBAction)snapToNorth:(id)sender {
+ [self.mapView setDirection:0 animated:YES];
+}
+
+- (IBAction)reload:(id)sender {
+ [self.mapView reloadStyle:sender];
+}
+
+- (IBAction)toggleTileBoundaries:(id)sender {
+ self.mapView.debugMask ^= MGLMapDebugTileBoundariesMask;
+}
+
+- (IBAction)toggleTileInfo:(id)sender {
+ self.mapView.debugMask ^= MGLMapDebugTileInfoMask;
+}
+
+- (IBAction)toggleTileTimestamps:(id)sender {
+ self.mapView.debugMask ^= MGLMapDebugTimestampsMask;
+}
+
+- (IBAction)toggleCollisionBoxes:(id)sender {
+ self.mapView.debugMask ^= MGLMapDebugCollisionBoxesMask;
+}
+
+#pragma mark Help methods
+
+- (IBAction)showShortcuts:(id)sender {
+ NSAlert *alert = [[NSAlert alloc] init];
+ alert.messageText = @"Mapbox GL Help";
+ alert.informativeText = @"\
+• To scroll, swipe with two fingers, drag the cursor, or press the arrow keys.\n\
+• To zoom, pinch with two fingers, or hold down Shift while dragging the cursor up and down, or hold down Option while pressing the up and down arrow keys.\n\
+• To rotate, move two fingers opposite each other in a circle, or hold down Option while dragging the cursor left and right, or hold down Option while pressing the left and right arrow keys.\n\
+• To tilt, hold down Option while dragging the cursor up and down.\
+• To drop a pin, click and hold.\
+";
+ [alert runModal];
+}
+
+- (IBAction)giveFeedback:(id)sender {
+ CLLocationCoordinate2D centerCoordinate = self.mapView.centerCoordinate;
+ NSURL *feedbackURL = [NSURL URLWithString:[NSString stringWithFormat:@"https://www.mapbox.com/map-feedback/#/%.5f/%.5f/%.0f",
+ centerCoordinate.longitude, centerCoordinate.latitude, round(self.mapView.zoomLevel)]];
+ [[NSWorkspace sharedWorkspace] openURL:feedbackURL];
+}
+
+- (IBAction)showPreferences:(id)sender {
+ [self.preferencesWindow makeKeyAndOrderFront:sender];
+}
+
+- (IBAction)openAccessTokenManager:(id)sender {
+ [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://www.mapbox.com/studio/account/tokens/"]];
+}
+
+#pragma mark Mouse events
+
+- (void)handlePressGesture:(NSPressGestureRecognizer *)gestureRecognizer {
+ if (gestureRecognizer.state == NSGestureRecognizerStateBegan) {
+ NSPoint location = [gestureRecognizer locationInView:self.mapView];
+ [self dropPinAtPoint:location];
+ }
+}
+
+- (IBAction)dropPin:(NSMenuItem *)sender {
+ [self dropPinAtPoint:_mouseLocationForMapViewContextMenu];
+}
+
+- (void)dropPinAtPoint:(NSPoint)point {
+ DroppedPinAnnotation *annotation = [[DroppedPinAnnotation alloc] init];
+ annotation.coordinate = [self.mapView convertPoint:point toCoordinateFromView:self.mapView];
+ annotation.title = @"Dropped Pin";
+ [self.mapView addAnnotation:annotation];
+ [self.mapView selectAnnotation:annotation animated:YES];
+}
+
+#pragma mark User interface validation
+
+- (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
+ if (menuItem.action == @selector(setStyle:)) {
+ NSURL *styleURL = self.mapView.styleURL;
+ NSCellStateValue state;
+ switch (menuItem.tag) {
+ case 1:
+ state = [styleURL isEqual:[MGLStyle streetsStyleURL]];
+ break;
+ case 2:
+ state = [styleURL isEqual:[MGLStyle emeraldStyleURL]];
+ break;
+ case 3:
+ state = [styleURL isEqual:[MGLStyle lightStyleURL]];
+ break;
+ case 4:
+ state = [styleURL isEqual:[MGLStyle darkStyleURL]];
+ break;
+ case 5:
+ state = [styleURL isEqual:[MGLStyle satelliteStyleURL]];
+ break;
+ case 6:
+ state = [styleURL isEqual:[MGLStyle hybridStyleURL]];
+ break;
+ default:
+ return NO;
+ }
+ menuItem.state = state;
+ return YES;
+ }
+ if (menuItem.action == @selector(chooseCustomStyle:)) {
+ menuItem.state = self.indexOfStyleInToolbarItem == NSNotFound;
+ return YES;
+ }
+ if (menuItem.action == @selector(zoomIn:)) {
+ return self.mapView.zoomLevel < self.mapView.maximumZoomLevel;
+ }
+ if (menuItem.action == @selector(zoomOut:)) {
+ return self.mapView.zoomLevel > self.mapView.minimumZoomLevel;
+ }
+ if (menuItem.action == @selector(snapToNorth:)) {
+ return self.mapView.direction != 0;
+ }
+ if (menuItem.action == @selector(reload:)) {
+ return YES;
+ }
+ if (menuItem.action == @selector(dropPin:)) {
+ return YES;
+ }
+ if (menuItem.action == @selector(toggleTileBoundaries:)) {
+ BOOL isShown = self.mapView.debugMask & MGLMapDebugTileBoundariesMask;
+ menuItem.title = isShown ? @"Hide Tile Boundaries" : @"Show Tile Boundaries";
+ return YES;
+ }
+ if (menuItem.action == @selector(toggleTileInfo:)) {
+ BOOL isShown = self.mapView.debugMask & MGLMapDebugTileInfoMask;
+ menuItem.title = isShown ? @"Hide Tile Info" : @"Show Tile Info";
+ return YES;
+ }
+ if (menuItem.action == @selector(toggleTileTimestamps:)) {
+ BOOL isShown = self.mapView.debugMask & MGLMapDebugTimestampsMask;
+ menuItem.title = isShown ? @"Hide Tile Timestamps" : @"Show Tile Timestamps";
+ return YES;
+ }
+ if (menuItem.action == @selector(toggleCollisionBoxes:)) {
+ BOOL isShown = self.mapView.debugMask & MGLMapDebugCollisionBoxesMask;
+ menuItem.title = isShown ? @"Hide Collision Boxes" : @"Show Collision Boxes";
+ return YES;
+ }
+ if (menuItem.action == @selector(showShortcuts:)) {
+ return YES;
+ }
+ if (menuItem.action == @selector(giveFeedback:)) {
+ return YES;
+ }
+ if (menuItem.action == @selector(showPreferences:)) {
+ return YES;
+ }
+ return NO;
+}
+
+- (NSUInteger)indexOfStyleInToolbarItem {
+ if (![MGLAccountManager accessToken]) {
+ return NSNotFound;
+ }
+
+ NSArray *styleURLs = @[
+ [MGLStyle streetsStyleURL],
+ [MGLStyle emeraldStyleURL],
+ [MGLStyle lightStyleURL],
+ [MGLStyle darkStyleURL],
+ [MGLStyle satelliteStyleURL],
+ [MGLStyle hybridStyleURL],
+ ];
+ return [styleURLs indexOfObject:self.mapView.styleURL];
+}
+
+- (BOOL)validateToolbarItem:(NSToolbarItem *)toolbarItem {
+ if (!self.mapView) {
+ return NO;
+ }
+
+ if (toolbarItem.action == @selector(showShareMenu:)) {
+ [(NSButton *)toolbarItem.view sendActionOn:NSLeftMouseDownMask];
+ return ([MGLAccountManager accessToken]
+ && [self.mapView.styleURL.scheme isEqualToString:@"mapbox"]
+ && [self.mapView.styleURL.pathComponents.firstObject isEqualToString:@"styles"]);
+ }
+ if (toolbarItem.action == @selector(setStyle:)) {
+ NSPopUpButton *popUpButton = (NSPopUpButton *)toolbarItem.view;
+ NSUInteger index = self.indexOfStyleInToolbarItem;
+ if (index == NSNotFound) {
+ [popUpButton addItemWithTitle:@"Custom"];
+ index = [popUpButton numberOfItems] - 1;
+ }
+ [popUpButton selectItemAtIndex:index];
+ }
+ return NO;
+}
+
+#pragma mark NSApplicationDelegate methods
+
+- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
+ return YES;
+}
+
+#pragma mark NSSharingServicePickerDelegate methods
+
+- (NS_ARRAY_OF(NSSharingService *) *)sharingServicePicker:(NSSharingServicePicker *)sharingServicePicker sharingServicesForItems:(NSArray *)items proposedSharingServices:(NS_ARRAY_OF(NSSharingService *) *)proposedServices {
+ NSURL *shareURL = self.shareURL;
+ NSURL *browserURL = [[NSWorkspace sharedWorkspace] URLForApplicationToOpenURL:shareURL];
+ NSImage *browserIcon = [[NSWorkspace sharedWorkspace] iconForFile:browserURL.path];
+ NSString *browserName = [[NSFileManager defaultManager] displayNameAtPath:browserURL.path];
+ NSString *browserServiceName = [NSString stringWithFormat:@"Open in %@", browserName];
+
+ NSSharingService *browserService = [[NSSharingService alloc] initWithTitle:browserServiceName
+ image:browserIcon
+ alternateImage:nil
+ handler:^{
+ [[NSWorkspace sharedWorkspace] openURL:self.shareURL];
+ }];
+
+ NSMutableArray *sharingServices = [proposedServices mutableCopy];
+ [sharingServices insertObject:browserService atIndex:0];
+ return sharingServices;
+}
+
+#pragma mark NSMenuDelegate methods
+
+- (void)menuWillOpen:(NSMenu *)menu {
+ if (menu == self.mapViewContextMenu) {
+ _mouseLocationForMapViewContextMenu = self.window.mouseLocationOutsideOfEventStream;
+ }
+}
+
+#pragma mark MGLMapViewDelegate methods
+
+- (BOOL)mapView:(MGLMapView *)mapView annotationCanShowCallout:(id <MGLAnnotation>)annotation {
+ return YES;
+}
+
+- (void)mapView:(MGLMapView *)mapView didSelectAnnotation:(id <MGLAnnotation>)annotation {
+ if ([annotation isKindOfClass:[DroppedPinAnnotation class]]) {
+ DroppedPinAnnotation *droppedPin = annotation;
+ [droppedPin resume];
+ }
+}
+
+- (void)mapView:(MGLMapView *)mapView didDeselectAnnotation:(id<MGLAnnotation>)annotation {
+ if ([annotation isKindOfClass:[DroppedPinAnnotation class]]) {
+ DroppedPinAnnotation *droppedPin = annotation;
+ [droppedPin pause];
+ }
+}
+
+@end
+
+@interface ValidatedToolbarItem : NSToolbarItem
+
+@end
+
+@implementation ValidatedToolbarItem
+
+- (void)validate {
+ [(AppDelegate *)self.toolbar.delegate validateToolbarItem:self];
+}
+
+@end
diff --git a/platform/osx/app/Credits.rtf b/platform/osx/app/Credits.rtf
new file mode 100644
index 0000000000..6b17eb34b2
--- /dev/null
+++ b/platform/osx/app/Credits.rtf
@@ -0,0 +1,9 @@
+{\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf130
+{\fonttbl\f0\fnil\fcharset0 SFUIText-Regular;}
+{\colortbl;\red255\green255\blue255;}
+\margl1440\margr1440\vieww10800\viewh8400\viewkind0
+\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\qc\partightenfactor0
+
+\f0\fs20 \cf0 Copyright \'a9 {\field{\*\fldinst{HYPERLINK "https://www.mapbox.com/about/maps/"}}{\fldrslt Mapbox}}.\
+\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\qc\partightenfactor0
+\cf0 Copyright \'a9 {\field{\*\fldinst{HYPERLINK "http://www.openstreetmap.org/about/"}}{\fldrslt OpenStreetMap contributors}}.} \ No newline at end of file
diff --git a/platform/osx/app/DroppedPinAnnotation.h b/platform/osx/app/DroppedPinAnnotation.h
new file mode 100644
index 0000000000..69bb453f28
--- /dev/null
+++ b/platform/osx/app/DroppedPinAnnotation.h
@@ -0,0 +1,10 @@
+#import "MGLPointAnnotation.h"
+
+@interface DroppedPinAnnotation : MGLPointAnnotation
+
+@property (nonatomic, readonly) NSTimeInterval elapsedShownTime;
+
+- (void)resume;
+- (void)pause;
+
+@end
diff --git a/platform/osx/app/DroppedPinAnnotation.m b/platform/osx/app/DroppedPinAnnotation.m
new file mode 100644
index 0000000000..a103ec923c
--- /dev/null
+++ b/platform/osx/app/DroppedPinAnnotation.m
@@ -0,0 +1,63 @@
+#import "DroppedPinAnnotation.h"
+
+#import "LocationCoordinate2DTransformer.h"
+#import "TimeIntervalTransformer.h"
+#import "NSValue+Additions.h"
+
+@implementation DroppedPinAnnotation {
+ NSTimer *_timer;
+ NSTimeInterval _priorShownTimeInterval;
+ NSDate *_dateShown;
+
+ NSValueTransformer *_coordinateTransformer;
+ NSValueTransformer *_timeIntervalTransformer;
+}
+
+- (instancetype)init {
+ if (self = [super init]) {
+ _coordinateTransformer = [NSValueTransformer valueTransformerForName:
+ NSStringFromClass([LocationCoordinate2DTransformer class])];
+ _timeIntervalTransformer = [NSValueTransformer valueTransformerForName:
+ NSStringFromClass([TimeIntervalTransformer class])];
+ [self update:nil];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [self pause];
+}
+
+- (void)setCoordinate:(CLLocationCoordinate2D)coordinate {
+ super.coordinate = coordinate;
+ [self update:nil];
+}
+
+- (NSTimeInterval)elapsedShownTime {
+ return _priorShownTimeInterval - _dateShown.timeIntervalSinceNow;
+}
+
+- (void)resume {
+ _dateShown = [NSDate date];
+ _timer = [NSTimer scheduledTimerWithTimeInterval:1
+ target:self
+ selector:@selector(update:)
+ userInfo:nil
+ repeats:YES];
+}
+
+- (void)pause {
+ [_timer invalidate];
+ _timer = nil;
+ _priorShownTimeInterval -= _dateShown.timeIntervalSinceNow;
+ _dateShown = nil;
+}
+
+- (void)update:(NSTimer *)timer {
+ NSString *coordinate = [_coordinateTransformer transformedValue:
+ [NSValue valueWithCLLocationCoordinate2D:self.coordinate]];
+ NSString *elapsedTime = [_timeIntervalTransformer transformedValue:@(self.elapsedShownTime)];
+ self.subtitle = [NSString stringWithFormat:@"%@\nSelected for %@", coordinate, elapsedTime];
+}
+
+@end
diff --git a/platform/osx/Icon.icns b/platform/osx/app/Icon.icns
index 64a5e57cf8..64a5e57cf8 100644
--- a/platform/osx/Icon.icns
+++ b/platform/osx/app/Icon.icns
Binary files differ
diff --git a/platform/osx/app/Info.plist b/platform/osx/app/Info.plist
new file mode 100644
index 0000000000..0d178d4b9e
--- /dev/null
+++ b/platform/osx/app/Info.plist
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleGetInfoString</key>
+ <string>0.1.0</string>
+ <key>CFBundleIconFile</key>
+ <string>Icon.icns</string>
+ <key>CFBundleIdentifier</key>
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleLongVersionString</key>
+ <string>0.1.0</string>
+ <key>CFBundleName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>0.1.0</string>
+ <key>CFBundleSignature</key>
+ <string>MBGL</string>
+ <key>CFBundleURLTypes</key>
+ <array>
+ <dict>
+ <key>CFBundleURLName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>mapboxgl</string>
+ </array>
+ </dict>
+ </array>
+ <key>CFBundleVersion</key>
+ <string>0.1.0</string>
+ <key>CSResourcesFileMapped</key>
+ <true/>
+ <key>NSHighResolutionCapable</key>
+ <true/>
+ <key>NSMainNibFile</key>
+ <string>MainMenu</string>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+</dict>
+</plist>
diff --git a/platform/osx/app/LocationCoordinate2DTransformer.h b/platform/osx/app/LocationCoordinate2DTransformer.h
new file mode 100644
index 0000000000..162325fbad
--- /dev/null
+++ b/platform/osx/app/LocationCoordinate2DTransformer.h
@@ -0,0 +1,5 @@
+#import <Foundation/Foundation.h>
+
+@interface LocationCoordinate2DTransformer : NSValueTransformer
+
+@end
diff --git a/platform/osx/app/LocationCoordinate2DTransformer.m b/platform/osx/app/LocationCoordinate2DTransformer.m
new file mode 100644
index 0000000000..d9c35b2a1f
--- /dev/null
+++ b/platform/osx/app/LocationCoordinate2DTransformer.m
@@ -0,0 +1,42 @@
+#import "LocationCoordinate2DTransformer.h"
+
+#import "NSValue+Additions.h"
+
+NSString *StringFromDegrees(CLLocationDegrees degrees, char positiveDirection, char negativeDirection) {
+ double minutes = (degrees - floor(degrees)) * 60;
+ double seconds = (minutes - floor(minutes)) * 60;
+
+ NSMutableString *string = [NSMutableString stringWithFormat:@"%.0f°", fabs(degrees)];
+ if (floor(minutes) || floor(seconds)) {
+ [string appendFormat:@"%.0f′", minutes];
+ }
+ if (floor(seconds)) {
+ [string appendFormat:@"%.0f″", seconds];
+ }
+ if (degrees) {
+ [string appendFormat:@"%c", degrees > 0 ? positiveDirection : negativeDirection];
+ }
+ return string;
+}
+
+@implementation LocationCoordinate2DTransformer
+
++ (Class)transformedValueClass {
+ return [NSString class];
+}
+
++ (BOOL)allowsReverseTransformation {
+ return NO;
+}
+
+- (id)transformedValue:(id)value {
+ if (![value isKindOfClass:[NSValue class]]) {
+ return nil;
+ }
+ CLLocationCoordinate2D coordinate = [value CLLocationCoordinate2DValue];
+ return [NSString stringWithFormat:@"%@, %@",
+ StringFromDegrees(coordinate.latitude, 'N', 'S'),
+ StringFromDegrees(coordinate.longitude, 'E', 'W')];
+}
+
+@end
diff --git a/platform/osx/app/MainMenu.xib b/platform/osx/app/MainMenu.xib
new file mode 100644
index 0000000000..c96169b094
--- /dev/null
+++ b/platform/osx/app/MainMenu.xib
@@ -0,0 +1,683 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9531" systemVersion="15B42" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+ <dependencies>
+ <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9531"/>
+ </dependencies>
+ <objects>
+ <customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
+ <connections>
+ <outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
+ </connections>
+ </customObject>
+ <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
+ <customObject id="-3" userLabel="Application">
+ <connections>
+ <outlet property="delegate" destination="Voe-Tx-rLC" id="z9N-Fm-MUP"/>
+ </connections>
+ </customObject>
+ <customObject id="Voe-Tx-rLC" customClass="AppDelegate">
+ <connections>
+ <outlet property="mapView" destination="v1Z-oA-oHU" id="70S-xO-QIP"/>
+ <outlet property="mapViewContextMenu" destination="6rZ-M1-uTn" id="wl2-03-9Z8"/>
+ <outlet property="preferencesWindow" destination="UWc-yQ-qda" id="Ota-aT-Mz2"/>
+ <outlet property="window" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
+ </connections>
+ </customObject>
+ <menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
+ <items>
+ <menuItem title="Mapbox GL" id="1Xt-HY-uBw">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Mapbox GL" systemMenu="apple" id="uQy-DD-JDr">
+ <items>
+ <menuItem title="About Mapbox GL" id="5kV-Vb-QxS">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
+ <menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW">
+ <connections>
+ <action selector="showPreferences:" target="-1" id="Llx-Uy-HTS"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
+ <menuItem title="Services" id="NMo-om-nkz">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
+ <menuItem title="Hide Mapbox GL" keyEquivalent="h" id="Olw-nP-bQN">
+ <connections>
+ <action selector="hide:" target="-1" id="PnN-Uc-m68"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
+ <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+ <connections>
+ <action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Show All" id="Kd2-mp-pUS">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
+ <menuItem title="Quit Mapbox GL" keyEquivalent="q" id="4sb-4s-VLi">
+ <connections>
+ <action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="File" id="dMs-cI-mzQ">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="File" id="bib-Uj-vzu">
+ <items>
+ <menuItem title="New" keyEquivalent="n" id="Was-JA-tGl">
+ <connections>
+ <action selector="newDocument:" target="-1" id="4Si-XN-c54"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Open…" keyEquivalent="o" id="IAo-SY-fd9">
+ <connections>
+ <action selector="openDocument:" target="-1" id="bVn-NM-KNZ"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Open Recent" id="tXI-mr-wws">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Open Recent" systemMenu="recentDocuments" id="oas-Oc-fiZ">
+ <items>
+ <menuItem title="Clear Menu" id="vNY-rz-j42">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="clearRecentDocuments:" target="-1" id="Daa-9d-B3U"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="m54-Is-iLE"/>
+ <menuItem title="Close" keyEquivalent="w" id="DVo-aG-piG">
+ <connections>
+ <action selector="performClose:" target="-1" id="HmO-Ls-i7Q"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Save…" keyEquivalent="s" id="pxx-59-PXV">
+ <connections>
+ <action selector="saveDocument:" target="-1" id="teZ-XB-qJY"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Save As…" keyEquivalent="S" id="Bw7-FT-i3A">
+ <connections>
+ <action selector="saveDocumentAs:" target="-1" id="mDf-zr-I0C"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Revert to Saved" id="KaW-ft-85H">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="revertDocumentToSaved:" target="-1" id="iJ3-Pv-kwq"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="aJh-i4-bef"/>
+ <menuItem title="Page Setup…" keyEquivalent="P" id="qIS-W8-SiK">
+ <modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
+ <connections>
+ <action selector="runPageLayout:" target="-1" id="Din-rz-gC5"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Print…" keyEquivalent="p" id="aTl-1u-JFS">
+ <connections>
+ <action selector="print:" target="-1" id="qaZ-4w-aoO"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Edit" id="5QF-Oa-p0T">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Edit" id="W48-6f-4Dl">
+ <items>
+ <menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
+ <connections>
+ <action selector="undo:" target="-1" id="M6e-cu-g7V"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
+ <connections>
+ <action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
+ <menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
+ <connections>
+ <action selector="cut:" target="-1" id="YJe-68-I9s"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
+ <connections>
+ <action selector="copy:" target="-1" id="G1f-GL-Joy"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
+ <connections>
+ <action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
+ <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+ <connections>
+ <action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Delete" id="pa3-QI-u2k">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
+ <connections>
+ <action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
+ <menuItem title="Find" id="4EN-yA-p0u">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Find" id="1b7-l0-nxx">
+ <items>
+ <menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
+ <connections>
+ <action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
+ <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+ <connections>
+ <action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
+ <connections>
+ <action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
+ <connections>
+ <action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
+ <connections>
+ <action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
+ <connections>
+ <action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
+ <items>
+ <menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
+ <connections>
+ <action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
+ <connections>
+ <action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
+ <menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Substitutions" id="9ic-FL-obx">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
+ <items>
+ <menuItem title="Show Substitutions" id="z6F-FW-3nz">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
+ <menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Smart Quotes" id="hQb-2v-fYv">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Smart Dashes" id="rgM-f4-ycn">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Smart Links" id="cwL-P1-jid">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Data Detectors" id="tRr-pd-1PS">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Text Replacement" id="HFQ-gK-NFA">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Transformations" id="2oI-Rn-ZJC">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Transformations" id="c8a-y6-VQd">
+ <items>
+ <menuItem title="Make Upper Case" id="vmV-6d-7jI">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Make Lower Case" id="d9M-CD-aMd">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Capitalize" id="UEZ-Bs-lqG">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Speech" id="xrE-MZ-jX0">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Speech" id="3rS-ZA-NoH">
+ <items>
+ <menuItem title="Start Speaking" id="Ynk-f8-cLZ">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Stop Speaking" id="Oyz-dy-DGm">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="View" id="H8h-7b-M4v">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="View" id="HyV-fh-RgO">
+ <items>
+ <menuItem title="Streets" state="on" tag="1" keyEquivalent="1" id="17N-yz-NNo">
+ <connections>
+ <action selector="setStyle:" target="-1" id="I4L-Wx-UXA"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Emerald" tag="2" keyEquivalent="2" id="BBa-Qa-SQr">
+ <connections>
+ <action selector="setStyle:" target="-1" id="rM1-yG-t5u"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Light" tag="3" keyEquivalent="3" id="HWe-7u-UVJ">
+ <connections>
+ <action selector="setStyle:" target="-1" id="Q9V-O1-oRz"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Dark" tag="4" keyEquivalent="4" id="6HI-q6-AeV">
+ <connections>
+ <action selector="setStyle:" target="-1" id="YfH-1I-G50"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Satellite" tag="5" keyEquivalent="5" id="h0J-5X-kgF">
+ <connections>
+ <action selector="setStyle:" target="-1" id="GXt-oK-Hy1"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Hybrid" tag="6" keyEquivalent="6" id="9BL-00-HFt">
+ <connections>
+ <action selector="setStyle:" target="-1" id="oL4-AC-waq"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Custom Style…" id="L0h-86-2cU">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="chooseCustomStyle:" target="-1" id="QJF-fM-Ty3"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="BMF-ml-0Bd"/>
+ <menuItem title="Zoom In" keyEquivalent="+" id="W82-WO-xvB">
+ <connections>
+ <action selector="zoomIn:" target="-1" id="g33-vK-zUu"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Zoom Out" keyEquivalent="-" id="j7h-PY-edM">
+ <connections>
+ <action selector="zoomOut:" target="-1" id="0pP-tO-9ex"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Snap to North" keyEquivalent="" id="Zss-3w-wkz">
+ <connections>
+ <action selector="snapToNorth:" target="-1" id="Ayq-GE-Lb5"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="mkP-YN-G0w"/>
+ <menuItem title="Reload" keyEquivalent="r" id="JvI-nv-KaE">
+ <connections>
+ <action selector="reload:" target="-1" id="xkh-9F-mOe"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="CyM-Wv-Bnc"/>
+ <menuItem title="Show Toolbar" keyEquivalent="t" id="snW-S8-Cw5">
+ <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+ <connections>
+ <action selector="toggleToolbarShown:" target="-1" id="BXY-wc-z0C"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Debug" id="ZNC-5r-eBw">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Debug" id="McE-ka-r79">
+ <items>
+ <menuItem title="Show Tile Boundaries" id="rDE-dG-rTR">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleTileBoundaries:" target="-1" id="LAO-88-F7h"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Show Tile Info" id="LoH-qD-kb0">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleTileInfo:" target="-1" id="KCn-0G-V87"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Show Tile Timestamps" id="bY0-2E-LZ7">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleTileTimestamps:" target="-1" id="tBs-2N-KEG"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Show Collision Boxes" id="Y0b-3K-mJE">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="toggleCollisionBoxes:" target="-1" id="EYa-7n-iWZ"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Window" id="aUF-d1-5bR">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
+ <items>
+ <menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
+ <connections>
+ <action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Zoom" keyEquivalent="z" id="R4o-n2-Eq4">
+ <modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
+ <connections>
+ <action selector="performZoom:" target="-1" id="DIl-cC-cCs"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
+ <menuItem title="Bring All to Front" id="LE2-aR-0XJ">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Help" id="wpr-3q-Mcd">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
+ <items>
+ <menuItem title="Mapbox GL Help" keyEquivalent="?" id="FKE-Sm-Kum">
+ <connections>
+ <action selector="showShortcuts:" target="-1" id="hNZ-sm-X2q"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="EpY-wQ-SjH"/>
+ <menuItem title="Improve This Map" id="xu5-WN-qYK">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="giveFeedback:" target="-1" id="cil-i9-r39"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ </items>
+ </menu>
+ <window title="Mapbox GL" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" frameAutosaveName="Mapbox" animationBehavior="default" id="QvC-M9-y7g">
+ <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES" fullSizeContentView="YES"/>
+ <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
+ <rect key="contentRect" x="388" y="211" width="512" height="360"/>
+ <rect key="screenRect" x="0.0" y="0.0" width="1280" height="777"/>
+ <view key="contentView" id="EiT-Mj-1SZ">
+ <rect key="frame" x="0.0" y="0.0" width="512" height="360"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <subviews>
+ <customView translatesAutoresizingMaskIntoConstraints="NO" id="v1Z-oA-oHU" customClass="MGLMapView">
+ <rect key="frame" x="0.0" y="0.0" width="512" height="360"/>
+ <connections>
+ <outlet property="delegate" destination="Voe-Tx-rLC" id="qJ7-fL-iw1"/>
+ <outlet property="menu" destination="6rZ-M1-uTn" id="4sB-UN-zuc"/>
+ </connections>
+ </customView>
+ </subviews>
+ <constraints>
+ <constraint firstItem="v1Z-oA-oHU" firstAttribute="leading" secondItem="EiT-Mj-1SZ" secondAttribute="leading" id="Z3O-xb-fah"/>
+ <constraint firstAttribute="trailing" secondItem="v1Z-oA-oHU" secondAttribute="trailing" id="oYw-bk-P2l"/>
+ <constraint firstItem="v1Z-oA-oHU" firstAttribute="top" secondItem="EiT-Mj-1SZ" secondAttribute="top" id="r4Z-4m-bgA"/>
+ <constraint firstAttribute="bottom" secondItem="v1Z-oA-oHU" secondAttribute="bottom" id="w3h-H1-baQ"/>
+ </constraints>
+ </view>
+ <toolbar key="toolbar" implicitIdentifier="A3AC6577-4712-4628-813D-113498171A84" allowsUserCustomization="NO" displayMode="iconOnly" sizeMode="regular" id="8nY-8B-Om5">
+ <allowedToolbarItems>
+ <toolbarItem implicitItemIdentifier="NSToolbarSpaceItem" id="zzK-Oz-JV8"/>
+ <toolbarItem implicitItemIdentifier="NSToolbarFlexibleSpaceItem" id="SMs-8a-x2h"/>
+ <toolbarItem implicitItemIdentifier="2CB58C0A-7B95-4233-8DD3-F94BFE7D3061" label="Share" paletteLabel="Share" image="NSShareTemplate" id="HtT-hs-jWY" customClass="ValidatedToolbarItem">
+ <nil key="toolTip"/>
+ <size key="minSize" width="40" height="32"/>
+ <size key="maxSize" width="40" height="32"/>
+ <button key="view" verticalHuggingPriority="750" id="l0U-ql-5hn">
+ <rect key="frame" x="0.0" y="14" width="48" height="32"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+ <buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="NSShareTemplate" imagePosition="only" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="vxZ-hj-hV2">
+ <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+ <font key="font" metaFont="system"/>
+ </buttonCell>
+ </button>
+ <connections>
+ <action selector="showShareMenu:" target="-1" id="vDU-9Y-DkS"/>
+ </connections>
+ </toolbarItem>
+ <toolbarItem implicitItemIdentifier="BA3542AF-D63A-4893-9CC7-8F67EF2E82B0" label="Style" paletteLabel="Style" id="ge5-0H-wyr" customClass="ValidatedToolbarItem">
+ <nil key="toolTip"/>
+ <size key="minSize" width="100" height="26"/>
+ <size key="maxSize" width="100" height="26"/>
+ <popUpButton key="view" verticalHuggingPriority="750" id="5Cd-Lw-cWm">
+ <rect key="frame" x="0.0" y="14" width="100" height="26"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+ <popUpButtonCell key="cell" type="roundTextured" title="Streets" bezelStyle="texturedRounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="border" tag="1" imageScaling="proportionallyDown" inset="2" selectedItem="ZnV-nK-Bri" id="esr-2z-V4Y">
+ <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+ <font key="font" metaFont="menu"/>
+ <menu key="menu" id="uxq-U6-EQW">
+ <items>
+ <menuItem title="Streets" state="on" tag="1" id="ZnV-nK-Bri"/>
+ <menuItem title="Emerald" tag="2" id="LLa-jX-Dox"/>
+ <menuItem title="Light" tag="3" id="jDf-NE-hMn"/>
+ <menuItem title="Dark" tag="4" id="etJ-y7-M5R">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem title="Satellite" tag="5" id="xYa-J8-bLe">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem title="Hybrid" tag="6" id="1Uq-Nl-FSZ">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ </items>
+ </menu>
+ </popUpButtonCell>
+ </popUpButton>
+ <connections>
+ <action selector="setStyle:" target="-1" id="qb2-E8-7pE"/>
+ </connections>
+ </toolbarItem>
+ </allowedToolbarItems>
+ <defaultToolbarItems>
+ <toolbarItem reference="HtT-hs-jWY"/>
+ <toolbarItem reference="SMs-8a-x2h"/>
+ <toolbarItem reference="ge5-0H-wyr"/>
+ </defaultToolbarItems>
+ <connections>
+ <outlet property="delegate" destination="Voe-Tx-rLC" id="ciT-Nf-Dtm"/>
+ </connections>
+ </toolbar>
+ <connections>
+ <binding destination="Voe-Tx-rLC" name="title" keyPath="mapView.centerCoordinate" id="XFo-sp-PtR">
+ <dictionary key="options">
+ <string key="NSValueTransformerName">LocationCoordinate2DTransformer</string>
+ </dictionary>
+ </binding>
+ </connections>
+ </window>
+ <window title="Preferences" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="Preferences" animationBehavior="default" id="UWc-yQ-qda">
+ <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
+ <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
+ <rect key="contentRect" x="109" y="131" width="350" height="62"/>
+ <rect key="screenRect" x="0.0" y="0.0" width="1280" height="777"/>
+ <view key="contentView" id="eA4-n3-qPe">
+ <rect key="frame" x="0.0" y="0.0" width="350" height="62"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <subviews>
+ <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0IK-AW-Gg3">
+ <rect key="frame" x="18" y="23" width="89" height="17"/>
+ <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Access token:" id="Ptd-FI-M5A">
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ <connections>
+ <accessibilityConnection property="link" destination="7sb-sf-oJU" id="U0t-jC-oQ7"/>
+ </connections>
+ </textField>
+ <textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="7sb-sf-oJU">
+ <rect key="frame" x="113" y="20" width="197" height="22"/>
+ <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" drawsBackground="YES" id="jlV-TC-NUv">
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ <connections>
+ <binding destination="45S-yT-WUN" name="value" keyPath="values.MGLMapboxAccessToken" id="iJE-S2-ALY"/>
+ </connections>
+ </textField>
+ <button translatesAutoresizingMaskIntoConstraints="NO" id="c3S-LC-PoX">
+ <rect key="frame" x="318" y="25" width="12" height="12"/>
+ <constraints>
+ <constraint firstAttribute="width" constant="12" id="M3J-pU-gKn"/>
+ </constraints>
+ <buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" image="NSFollowLinkFreestandingTemplate" imagePosition="only" alignment="center" controlSize="small" imageScaling="proportionallyUpOrDown" inset="2" id="38x-37-Ay0">
+ <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
+ <font key="font" metaFont="smallSystem"/>
+ </buttonCell>
+ <connections>
+ <action selector="openAccessTokenManager:" target="-1" id="1LX-4G-roC"/>
+ </connections>
+ </button>
+ </subviews>
+ <constraints>
+ <constraint firstAttribute="trailing" secondItem="c3S-LC-PoX" secondAttribute="trailing" constant="20" symbolic="YES" id="7QU-Jd-Rg6"/>
+ <constraint firstItem="c3S-LC-PoX" firstAttribute="top" secondItem="eA4-n3-qPe" secondAttribute="top" constant="25" id="JOS-HU-27c"/>
+ <constraint firstItem="7sb-sf-oJU" firstAttribute="leading" secondItem="0IK-AW-Gg3" secondAttribute="trailing" constant="8" symbolic="YES" id="SS6-VQ-sLK"/>
+ <constraint firstItem="0IK-AW-Gg3" firstAttribute="leading" secondItem="eA4-n3-qPe" secondAttribute="leading" constant="20" symbolic="YES" id="TYG-io-qfV"/>
+ <constraint firstItem="7sb-sf-oJU" firstAttribute="top" secondItem="eA4-n3-qPe" secondAttribute="top" constant="20" symbolic="YES" id="Vzb-q8-ecP"/>
+ <constraint firstItem="c3S-LC-PoX" firstAttribute="leading" secondItem="7sb-sf-oJU" secondAttribute="trailing" constant="8" symbolic="YES" id="pjl-9u-IgM"/>
+ <constraint firstItem="7sb-sf-oJU" firstAttribute="baseline" secondItem="0IK-AW-Gg3" secondAttribute="baseline" id="qIY-Jr-9Ws"/>
+ <constraint firstItem="7sb-sf-oJU" firstAttribute="centerY" secondItem="c3S-LC-PoX" secondAttribute="centerY" id="zej-gw-fC0"/>
+ </constraints>
+ </view>
+ <connections>
+ <outlet property="initialFirstResponder" destination="7sb-sf-oJU" id="UZe-di-dnA"/>
+ </connections>
+ <point key="canvasLocation" x="754" y="210"/>
+ </window>
+ <userDefaultsController representsSharedInstance="YES" id="45S-yT-WUN"/>
+ <menu title="Map View" id="6rZ-M1-uTn">
+ <items>
+ <menuItem title="Drop Pin" id="fxQ-3P-E7l">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="dropPin:" target="Voe-Tx-rLC" id="eDR-iw-JUG"/>
+ </connections>
+ </menuItem>
+ </items>
+ <connections>
+ <outlet property="delegate" destination="Voe-Tx-rLC" id="MsV-YU-f4X"/>
+ </connections>
+ <point key="canvasLocation" x="820" y="254.5"/>
+ </menu>
+ </objects>
+ <resources>
+ <image name="NSFollowLinkFreestandingTemplate" width="14" height="14"/>
+ <image name="NSShareTemplate" width="11" height="16"/>
+ </resources>
+</document>
diff --git a/platform/osx/app/NSValue+Additions.h b/platform/osx/app/NSValue+Additions.h
new file mode 100644
index 0000000000..050dbc8132
--- /dev/null
+++ b/platform/osx/app/NSValue+Additions.h
@@ -0,0 +1,10 @@
+#import <Foundation/Foundation.h>
+#import <CoreLocation/CoreLocation.h>
+
+@interface NSValue (Additions)
+
++ (instancetype)valueWithCLLocationCoordinate2D:(CLLocationCoordinate2D)coordinate;
+
+@property (readonly) CLLocationCoordinate2D CLLocationCoordinate2DValue;
+
+@end
diff --git a/platform/osx/app/NSValue+Additions.m b/platform/osx/app/NSValue+Additions.m
new file mode 100644
index 0000000000..c060602f43
--- /dev/null
+++ b/platform/osx/app/NSValue+Additions.m
@@ -0,0 +1,15 @@
+#import "NSValue+Additions.h"
+
+@implementation NSValue (Additions)
+
++ (instancetype)valueWithCLLocationCoordinate2D:(CLLocationCoordinate2D)coordinate {
+ return [self valueWithBytes:&coordinate objCType:@encode(CLLocationCoordinate2D)];
+}
+
+- (CLLocationCoordinate2D)CLLocationCoordinate2DValue {
+ CLLocationCoordinate2D coordinate;
+ [self getValue:&coordinate];
+ return coordinate;
+}
+
+@end
diff --git a/platform/osx/app/TimeIntervalTransformer.h b/platform/osx/app/TimeIntervalTransformer.h
new file mode 100644
index 0000000000..ca88ad2cd1
--- /dev/null
+++ b/platform/osx/app/TimeIntervalTransformer.h
@@ -0,0 +1,5 @@
+#import <Foundation/Foundation.h>
+
+@interface TimeIntervalTransformer : NSValueTransformer
+
+@end
diff --git a/platform/osx/app/TimeIntervalTransformer.m b/platform/osx/app/TimeIntervalTransformer.m
new file mode 100644
index 0000000000..e6d7333d25
--- /dev/null
+++ b/platform/osx/app/TimeIntervalTransformer.m
@@ -0,0 +1,55 @@
+#import "TimeIntervalTransformer.h"
+
+#import "NSValue+Additions.h"
+
+@implementation TimeIntervalTransformer
+
++ (Class)transformedValueClass {
+ return [NSString class];
+}
+
++ (BOOL)allowsReverseTransformation {
+ return NO;
+}
+
+NSString *NumberAndUnitString(NSInteger quantity, NSString *singular, NSString *plural) {
+ return [NSString stringWithFormat:@"%ld %@", quantity, quantity == 1 ? singular : plural];
+}
+
+- (id)transformedValue:(id)value {
+ if (![value isKindOfClass:[NSValue class]]) {
+ return nil;
+ }
+
+ NSTimeInterval timeInterval = [value doubleValue];
+ NSInteger seconds = floor(timeInterval);
+ NSInteger minutes = floor(seconds / 60);
+ seconds -= minutes * 60;
+ NSInteger hours = floor(minutes / 60);
+ minutes -= hours * 60;
+ NSInteger days = floor(hours / 24);
+ hours -= days * 24;
+ NSInteger weeks = floor(days) / 7;
+ days -= weeks * 7;
+
+ NSMutableArray *components = [NSMutableArray array];
+ if (seconds || timeInterval < 60) {
+ [components addObject:NumberAndUnitString(seconds, @"second", @"seconds")];
+ }
+ if (minutes) {
+ [components insertObject:NumberAndUnitString(minutes, @"minute", @"minutes") atIndex:0];
+ }
+ if (hours) {
+ [components insertObject:NumberAndUnitString(hours, @"hour", @"hours") atIndex:0];
+ }
+ if (days) {
+ [components insertObject:NumberAndUnitString(days, @"day", @"days") atIndex:0];
+ }
+ if (weeks) {
+ [components insertObject:NumberAndUnitString(weeks, @"week", @"weeks") atIndex:0];
+ }
+
+ return [components componentsJoinedByString:@", "];
+}
+
+@end
diff --git a/platform/osx/app/main.m b/platform/osx/app/main.m
new file mode 100644
index 0000000000..8a6799b414
--- /dev/null
+++ b/platform/osx/app/main.m
@@ -0,0 +1,5 @@
+#import <Cocoa/Cocoa.h>
+
+int main(int argc, const char * argv[]) {
+ return NSApplicationMain(argc, argv);
+}
diff --git a/platform/osx/app/mapboxgl-app.gypi b/platform/osx/app/mapboxgl-app.gypi
new file mode 100644
index 0000000000..8ab0320d31
--- /dev/null
+++ b/platform/osx/app/mapboxgl-app.gypi
@@ -0,0 +1,48 @@
+{
+ 'includes': [
+ '../../../gyp/common.gypi',
+ ],
+ 'targets': [
+ { 'target_name': 'osxapp',
+ 'product_name': 'Mapbox GL',
+ 'type': 'executable',
+ 'product_extension': 'app',
+ 'mac_bundle': 1,
+ 'mac_bundle_resources': [
+ 'Credits.rtf',
+ 'Icon.icns',
+ 'MainMenu.xib',
+ ],
+
+ 'dependencies': [
+ 'mbgl.gyp:core',
+ 'mbgl.gyp:platform-<(platform_lib)',
+ 'mbgl.gyp:http-<(http_lib)',
+ 'mbgl.gyp:asset-<(asset_lib)',
+ 'mbgl.gyp:cache-<(cache_lib)',
+ ],
+
+ 'sources': [
+ './AppDelegate.h',
+ './AppDelegate.m',
+ './DroppedPinAnnotation.h',
+ './DroppedPinAnnotation.m',
+ './LocationCoordinate2DTransformer.h',
+ './LocationCoordinate2DTransformer.m',
+ './TimeIntervalTransformer.h',
+ './TimeIntervalTransformer.m',
+ './NSValue+Additions.h',
+ './NSValue+Additions.m',
+ './main.m',
+ ],
+
+ 'xcode_settings': {
+ 'SDKROOT': 'macosx',
+ 'SUPPORTED_PLATFORMS':'macosx',
+ 'OTHER_LDFLAGS': [ '-stdlib=libc++', '-lstdc++' ],
+ 'INFOPLIST_FILE': '../platform/osx/app/Info.plist',
+ 'CLANG_ENABLE_OBJC_ARC': 'YES',
+ },
+ }
+ ]
+}
diff --git a/platform/osx/main.mm b/platform/osx/main.mm
deleted file mode 100644
index 6b8aef3814..0000000000
--- a/platform/osx/main.mm
+++ /dev/null
@@ -1,212 +0,0 @@
-#include <mbgl/platform/log.hpp>
-#include <mbgl/platform/platform.hpp>
-#include <mbgl/platform/darwin/settings_nsuserdefaults.hpp>
-#include <mbgl/platform/darwin/reachability.h>
-#include <mbgl/platform/default/glfw_view.hpp>
-#include <mbgl/storage/default_file_source.hpp>
-#include <mbgl/storage/sqlite_cache.hpp>
-#include <mbgl/storage/network_status.hpp>
-
-#include <mbgl/util/geo.hpp>
-#include <mbgl/util/default_styles.hpp>
-
-#import <Foundation/Foundation.h>
-
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wunknown-pragmas"
-#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
-#pragma GCC diagnostic ignored "-Wshadow"
-#include <boost/program_options.hpp>
-#pragma GCC diagnostic pop
-
-#include <iostream>
-
-namespace po = boost::program_options;
-
-
-@interface URLHandler : NSObject
-@property (nonatomic) mbgl::Map *map;
-
-- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event
- withReplyEvent:(NSAppleEventDescriptor *)replyEvent;
-@end
-
-@implementation URLHandler
-
-- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event
- withReplyEvent:(NSAppleEventDescriptor *)replyEvent {
- (void)replyEvent;
-
- NSString* urlString = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
- NSURL *url = [NSURL URLWithString:urlString];
- NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
- for (NSString *param in [[url query] componentsSeparatedByString:@"&"]) {
- NSArray *parts = [param componentsSeparatedByString:@"="];
- if([parts count] < 2) continue;
- [params setObject:[parts objectAtIndex:1] forKey:[parts objectAtIndex:0]];
- }
-
- mbgl::LatLng latLng = mbgl::LatLng(0, 0);
- double zoom = 0, bearing = 0;
- bool hasCenter = false, hasZoom = false, hasBearing = false;
-
- NSString *centerString = [params objectForKey:@"center"];
- if (centerString) {
- NSArray *latLngValues = [centerString componentsSeparatedByString:@","];
- if ([latLngValues count] == 2) {
- latLng.latitude = [latLngValues[0] doubleValue];
- latLng.longitude = [latLngValues[1] doubleValue];
- hasCenter = true;
- }
- }
-
- NSString *zoomString = [params objectForKey:@"zoom"];
- if (zoomString) {
- zoom = [zoomString doubleValue];
- hasZoom = true;
- }
-
- NSString *bearingString = [params objectForKey:@"bearing"];
- if (bearingString) {
- bearing = [bearingString doubleValue];
- hasBearing = true;
- }
-
- if ([self map]) {
- if (hasCenter && hasZoom) {
- [self map]->setLatLngZoom(latLng, zoom);
- } else if (hasCenter) {
- [self map]->setLatLng(latLng);
- } else if (hasZoom) {
- [self map]->setZoom(zoom);
- }
-
- if (hasBearing) {
- [self map]->setBearing(bearing);
- }
- }
-}
-@end
-
-// Returns the path to the default cache database on this system.
-const std::string &defaultCacheDatabase() {
- static const std::string path = []() -> std::string {
- NSArray *paths = NSSearchPathForDirectoriesInDomains(
- NSApplicationSupportDirectory, NSUserDomainMask, YES);
- if ([paths count] == 0) {
- // Disable the cache if we don't have a location to write.
- return "";
- }
-
- NSString *p = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"Mapbox GL"];
-
- if (![[NSFileManager defaultManager] createDirectoryAtPath:p
- withIntermediateDirectories:YES
- attributes:nil
- error:nil]) {
- // Disable the cache if we couldn't create the directory.
- return "";
- }
-
- return [[p stringByAppendingPathComponent:@"cache.db"] UTF8String];
- }();
- return path;
-}
-
-int main(int argc, char* argv[]) {
- bool fullscreen = false;
- bool benchmark = false;
- std::string style;
-
- po::options_description desc("Allowed options");
- desc.add_options()
- ("fullscreen,f", po::bool_switch(&fullscreen)->default_value(fullscreen), "Fullscreen mode")
- ("style,s", po::value(&style)->value_name("json"), "Map stylesheet")
- ("benchmark,b", po::bool_switch(&benchmark)->default_value(benchmark), "Benchmark mode")
- ;
-
- try {
- auto parsed = po::command_line_parser(argc, argv).options(desc).allow_unregistered().run();
- po::variables_map vm;
- po::store(parsed, vm);
- po::notify(vm);
- } catch(std::exception& e) {
- std::cout << "Error: " << e.what() << std::endl << desc;
- exit(1);
- }
-
- if (benchmark) {
- mbgl::Log::Info(mbgl::Event::General, "BENCHMARK MODE: Some optimizations are disabled.");
- }
-
- GLFWView view(fullscreen, benchmark);
-
- mbgl::SQLiteCache cache(defaultCacheDatabase());
- mbgl::DefaultFileSource fileSource(&cache);
-
- // Set access token if present
- NSString *accessToken = [[NSProcessInfo processInfo] environment][@"MAPBOX_ACCESS_TOKEN"];
- if (!accessToken) mbgl::Log::Warning(mbgl::Event::Setup, "No access token set. Mapbox vector tiles won't work.");
- if (accessToken) fileSource.setAccessToken([accessToken cStringUsingEncoding:[NSString defaultCStringEncoding]]);
-
- mbgl::Map map(view, fileSource);
-
- URLHandler *handler = [[URLHandler alloc] init];
- [handler setMap:&map];
- NSAppleEventManager *appleEventManager = [NSAppleEventManager sharedAppleEventManager];
- [appleEventManager setEventHandler:handler andSelector:@selector(handleGetURLEvent:withReplyEvent:) forEventClass:kInternetEventClass andEventID:kAEGetURL];
-
- // Notify map object when network reachability status changes.
- MGLReachability* reachability = [MGLReachability reachabilityForInternetConnection];
- reachability.reachableBlock = ^(MGLReachability *) {
- mbgl::NetworkStatus::Reachable();
- };
- [reachability startNotifier];
-
- // Load settings
- mbgl::Settings_NSUserDefaults settings;
- map.setLatLngZoom(mbgl::LatLng(settings.latitude, settings.longitude), settings.zoom);
- map.setBearing(settings.bearing);
- map.setPitch(settings.pitch);
- map.setDebug(mbgl::MapDebugOptions(settings.debug));
-
- view.setChangeStyleCallback([&map, &view] () {
- static uint8_t currentStyleIndex;
-
- if (++currentStyleIndex == mbgl::util::default_styles::numOrderedStyles) {
- currentStyleIndex = 0;
- }
-
- mbgl::util::default_styles::DefaultStyle newStyle = mbgl::util::default_styles::orderedStyles[currentStyleIndex];
- map.setStyleURL(newStyle.url);
- view.setWindowTitle(newStyle.name);
-
- mbgl::Log::Info(mbgl::Event::Setup, "Changed style to: %s", newStyle.name);
- });
-
-
- // Load style
- if (style.empty()) {
- mbgl::util::default_styles::DefaultStyle newStyle = mbgl::util::default_styles::orderedStyles[0];
- style = newStyle.url;
- view.setWindowTitle(newStyle.name);
- }
-
- map.setStyleURL(style);
-
- view.run();
-
- [reachability stopNotifier];
-
- // Save settings
- mbgl::LatLng latLng = map.getLatLng();
- settings.latitude = latLng.latitude;
- settings.longitude = latLng.longitude;
- settings.zoom = map.getZoom();
- settings.bearing = map.getBearing();
- settings.pitch = map.getPitch();
- settings.debug = uint32_t(map.getDebug());
- settings.save();
-
- return 0;
-}
diff --git a/platform/osx/mapboxgl-app.gypi b/platform/osx/mapboxgl-app.gypi
deleted file mode 100644
index 2bb893af30..0000000000
--- a/platform/osx/mapboxgl-app.gypi
+++ /dev/null
@@ -1,63 +0,0 @@
-{
- 'includes': [
- '../../gyp/common.gypi',
- ],
- 'targets': [
- { 'target_name': 'osxapp',
- 'product_name': 'Mapbox GL',
- 'type': 'executable',
- 'product_extension': 'app',
- 'mac_bundle': 1,
- 'mac_bundle_resources': [
- 'Icon.icns'
- ],
-
- 'dependencies': [
- 'mbgl.gyp:core',
- 'mbgl.gyp:platform-<(platform_lib)',
- 'mbgl.gyp:http-<(http_lib)',
- 'mbgl.gyp:asset-<(asset_lib)',
- 'mbgl.gyp:cache-<(cache_lib)',
- ],
-
- 'sources': [
- './main.mm',
- '../darwin/settings_nsuserdefaults.hpp',
- '../darwin/settings_nsuserdefaults.mm',
- '../darwin/reachability.m',
- '../default/glfw_view.hpp',
- '../default/glfw_view.cpp',
- ],
-
- 'variables' : {
- 'cflags_cc': [
- '<@(boost_cflags)',
- '<@(glfw_cflags)',
- '<@(variant_cflags)',
- ],
- 'ldflags': [
- '-framework SystemConfiguration', # For NSUserDefaults and Reachability
- '<@(glfw_ldflags)',
- ],
- 'libraries': [
- '<@(glfw_static_libs)',
- '<@(boost_libprogram_options_static_libs)'
- ],
- },
-
- 'libraries': [
- '<@(libraries)',
- ],
-
- 'xcode_settings': {
- 'SDKROOT': 'macosx',
- 'SUPPORTED_PLATFORMS':'macosx',
- 'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ],
- 'OTHER_LDFLAGS': [ '<@(ldflags)' ],
- 'SDKROOT': 'macosx',
- 'INFOPLIST_FILE': '../platform/osx/Info.plist',
- 'CLANG_ENABLE_OBJC_ARC': 'YES'
- },
- }
- ]
-}
diff --git a/platform/osx/sdk/MGLAccountManager.m b/platform/osx/sdk/MGLAccountManager.m
new file mode 100644
index 0000000000..d6bc82defe
--- /dev/null
+++ b/platform/osx/sdk/MGLAccountManager.m
@@ -0,0 +1,66 @@
+#import "MGLAccountManager_Private.h"
+
+#import <mbgl/osx/MGLMapView.h>
+
+#import "NSBundle+MGLAdditions.h"
+#import "NSProcessInfo+MGLAdditions.h"
+#import "../../darwin/NSString+MGLAdditions.h"
+
+@interface MGLAccountManager ()
+
+@property (atomic) NSString *accessToken;
+
+@end
+
+@implementation MGLAccountManager
+
+#pragma mark - Internal
+
++ (void)load {
+ mgl_linkBundleCategory();
+ mgl_linkStringCategory();
+ mgl_linkProcessInfoCategory();
+
+ [MGLMapView restorableStateKeyPaths];
+
+ // Read the initial configuration from Info.plist.
+ NSString *accessToken = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"MGLMapboxAccessToken"];
+ if (accessToken.length) {
+ self.accessToken = accessToken;
+ }
+}
+
+// Can be called from any thread.
+//
++ (instancetype)sharedManager {
+ static dispatch_once_t onceToken;
+ static MGLAccountManager *_sharedManager;
+ void (^setupBlock)() = ^{
+ dispatch_once(&onceToken, ^{
+ _sharedManager = [[self alloc] init];
+ });
+ };
+ if (![[NSThread currentThread] isMainThread]) {
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ setupBlock();
+ });
+ }
+ else {
+ setupBlock();
+ }
+ return _sharedManager;
+}
+
++ (void)setAccessToken:(NSString *)accessToken {
+ accessToken = [accessToken stringByTrimmingCharactersInSet:
+ [NSCharacterSet whitespaceAndNewlineCharacterSet]];
+ if (![accessToken length]) return;
+
+ [MGLAccountManager sharedManager].accessToken = accessToken;
+}
+
++ (NSString *)accessToken {
+ return [MGLAccountManager sharedManager].accessToken;
+}
+
+@end
diff --git a/platform/osx/sdk/MGLAccountManager_Private.h b/platform/osx/sdk/MGLAccountManager_Private.h
new file mode 100644
index 0000000000..c0b8d2666a
--- /dev/null
+++ b/platform/osx/sdk/MGLAccountManager_Private.h
@@ -0,0 +1,10 @@
+#import <mbgl/osx/MGLAccountManager.h>
+
+@interface MGLAccountManager (Private)
+
+/** Returns the shared instance of the `MGLAccountManager` class. */
++ (instancetype)sharedManager;
+
+@property (atomic) NSString *accessToken;
+
+@end
diff --git a/platform/osx/sdk/MGLAnnotationImage.m b/platform/osx/sdk/MGLAnnotationImage.m
new file mode 100644
index 0000000000..855105fded
--- /dev/null
+++ b/platform/osx/sdk/MGLAnnotationImage.m
@@ -0,0 +1,25 @@
+#import <mbgl/osx/MGLAnnotationImage.h>
+
+@interface MGLAnnotationImage ()
+
+@property (nonatomic) NSImage *image;
+@property (nonatomic) NSString *reuseIdentifier;
+
+@end
+
+@implementation MGLAnnotationImage
+
++ (instancetype)annotationImageWithImage:(NSImage *)image reuseIdentifier:(NSString *)reuseIdentifier {
+ return [[self alloc] initWithImage:image reuseIdentifier:reuseIdentifier];
+}
+
+- (instancetype)initWithImage:(NSImage *)image reuseIdentifier:(NSString *)reuseIdentifier {
+ if (self = [super init]) {
+ _image = image;
+ _reuseIdentifier = [reuseIdentifier copy];
+ _selectable = YES;
+ }
+ return self;
+}
+
+@end
diff --git a/platform/osx/sdk/MGLAttributionButton.h b/platform/osx/sdk/MGLAttributionButton.h
new file mode 100644
index 0000000000..7bc0fa5357
--- /dev/null
+++ b/platform/osx/sdk/MGLAttributionButton.h
@@ -0,0 +1,11 @@
+#import <Cocoa/Cocoa.h>
+
+@interface MGLAttributionButton : NSButton
+
+- (instancetype)initWithTitle:(NSString *)title URL:(NSURL *)url;
+
+@property (nonatomic) NSURL *URL;
+
+- (IBAction)openURL:(id)sender;
+
+@end
diff --git a/platform/osx/sdk/MGLAttributionButton.m b/platform/osx/sdk/MGLAttributionButton.m
new file mode 100644
index 0000000000..642f71f608
--- /dev/null
+++ b/platform/osx/sdk/MGLAttributionButton.m
@@ -0,0 +1,46 @@
+#import "MGLAttributionButton.h"
+
+@implementation MGLAttributionButton {
+ NSTrackingRectTag _trackingAreaTag;
+}
+
+- (instancetype)initWithTitle:(NSString *)title URL:(NSURL *)url {
+ if (self = [super initWithFrame:NSZeroRect]) {
+ self.bordered = NO;
+ self.bezelStyle = NSRegularSquareBezelStyle;
+
+ NSMutableAttributedString *attributedTitle = [[NSMutableAttributedString alloc] initWithString:@"© "
+ attributes:@{
+ NSFontAttributeName: [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSMiniControlSize]],
+ }];
+ [attributedTitle appendAttributedString:
+ [[NSAttributedString alloc] initWithString:title
+ attributes:@{
+ NSFontAttributeName: [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSMiniControlSize]],
+ NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle),
+ }]];
+ self.attributedTitle = attributedTitle;
+ [self sizeToFit];
+
+ _URL = url;
+ self.toolTip = _URL.absoluteString;
+
+ self.target = self;
+ self.action = @selector(openURL:);
+ }
+ return self;
+}
+
+- (BOOL)wantsLayer {
+ return YES;
+}
+
+- (void)resetCursorRects {
+ [self addCursorRect:self.bounds cursor:[NSCursor pointingHandCursor]];
+}
+
+- (IBAction)openURL:(__unused id)sender {
+ [[NSWorkspace sharedWorkspace] openURL:self.URL];
+}
+
+@end
diff --git a/platform/osx/sdk/MGLCompassCell.h b/platform/osx/sdk/MGLCompassCell.h
new file mode 100644
index 0000000000..8c89b43e18
--- /dev/null
+++ b/platform/osx/sdk/MGLCompassCell.h
@@ -0,0 +1,5 @@
+#import <Cocoa/Cocoa.h>
+
+@interface MGLCompassCell : NSSliderCell
+
+@end
diff --git a/platform/osx/sdk/MGLCompassCell.m b/platform/osx/sdk/MGLCompassCell.m
new file mode 100644
index 0000000000..88911de2ff
--- /dev/null
+++ b/platform/osx/sdk/MGLCompassCell.m
@@ -0,0 +1,31 @@
+#import "MGLCompassCell.h"
+
+@implementation MGLCompassCell
+
+- (instancetype)init {
+ if (self = [super init]) {
+ self.sliderType = NSCircularSlider;
+ self.numberOfTickMarks = 4;
+ self.minValue = -360;
+ self.maxValue = 0;
+ }
+ return self;
+}
+
+- (void)drawKnob:(NSRect)knobRect {
+ NSBezierPath *trianglePath = [NSBezierPath bezierPath];
+ [trianglePath moveToPoint:NSMakePoint(NSMinX(knobRect), NSMaxY(knobRect))];
+ [trianglePath lineToPoint:NSMakePoint(NSMaxX(knobRect), NSMaxY(knobRect))];
+ [trianglePath lineToPoint:NSMakePoint(NSMidX(knobRect), NSMinY(knobRect))];
+ [trianglePath closePath];
+ NSAffineTransform *transform = [NSAffineTransform transform];
+ [transform translateXBy:NSMidX(knobRect) yBy:NSMidY(knobRect)];
+ [transform scaleBy:0.8];
+ [transform rotateByDegrees:self.doubleValue];
+ [transform translateXBy:-NSMidX(knobRect) yBy:-NSMidY(knobRect)];
+ [trianglePath transformUsingAffineTransform:transform];
+ [[NSColor redColor] setFill];
+ [trianglePath fill];
+}
+
+@end
diff --git a/platform/osx/sdk/MGLMapView+IBAdditions.m b/platform/osx/sdk/MGLMapView+IBAdditions.m
new file mode 100644
index 0000000000..504bc789de
--- /dev/null
+++ b/platform/osx/sdk/MGLMapView+IBAdditions.m
@@ -0,0 +1,114 @@
+#import <mbgl/osx/MGLMapView+IBAdditions.h>
+
+#import "MGLMapView_Private.h"
+
+@implementation MGLMapView (IBAdditions)
+
++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingStyleURL__ {
+ return [NSSet setWithObject:@"styleURL"];
+}
+
+- (nullable NSString *)styleURL__ {
+ return self.styleURL.absoluteString;
+}
+
+- (void)setStyleURL__:(nullable NSString *)URLString {
+ URLString = [URLString stringByTrimmingCharactersInSet:
+ [NSCharacterSet whitespaceAndNewlineCharacterSet]];
+ NSURL *url = URLString.length ? [NSURL URLWithString:URLString] : nil;
+ if (URLString.length && !url) {
+ [NSException raise:@"Invalid style URL"
+ format:@"“%@” is not a valid style URL.", URLString];
+ }
+ self.styleURL = url;
+}
+
++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingLatitude {
+ return [NSSet setWithObjects:@"centerCoordinate", nil];
+}
+
+- (double)latitude {
+ return self.centerCoordinate.latitude;
+}
+
+- (void)setLatitude:(double)latitude {
+ if (!isnan(self.pendingLongitude)) {
+ self.centerCoordinate = CLLocationCoordinate2DMake(latitude, self.pendingLongitude);
+ self.pendingLatitude = NAN;
+ self.pendingLongitude = NAN;
+ } else {
+ // Not enough info to make a valid center coordinate yet. Stash this
+ // latitude away until the longitude is set too.
+ self.pendingLatitude = latitude;
+ }
+}
+
++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingLongitude {
+ return [NSSet setWithObjects:@"centerCoordinate", nil];
+}
+
+- (double)longitude {
+ return self.centerCoordinate.longitude;
+}
+
+- (void)setLongitude:(double)longitude {
+ if (!isnan(self.pendingLatitude)) {
+ self.centerCoordinate = CLLocationCoordinate2DMake(self.pendingLatitude, longitude);
+ self.pendingLatitude = NAN;
+ self.pendingLongitude = NAN;
+ } else {
+ // Not enough info to make a valid center coordinate yet. Stash this
+ // longitude away until the latitude is set too.
+ self.pendingLongitude = longitude;
+ }
+}
+
++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingAllowsZooming {
+ return [NSSet setWithObject:@"zoomEnabled"];
+}
+
+- (BOOL)allowsZooming {
+ return self.zoomEnabled;
+}
+
+- (void)setAllowsZooming:(BOOL)allowsZooming {
+ self.zoomEnabled = allowsZooming;
+}
+
++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingAllowsScrolling {
+ return [NSSet setWithObject:@"scrollEnabled"];
+}
+
+- (BOOL)allowsScrolling {
+ return self.scrollEnabled;
+}
+
+- (void)setAllowsScrolling:(BOOL)allowsScrolling {
+ self.scrollEnabled = allowsScrolling;
+}
+
++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingAllowsRotating {
+ return [NSSet setWithObject:@"rotateEnabled"];
+}
+
+- (BOOL)allowsRotating {
+ return self.rotateEnabled;
+}
+
+- (void)setAllowsRotating:(BOOL)allowsRotating {
+ self.rotateEnabled = allowsRotating;
+}
+
++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingAllowsTilting {
+ return [NSSet setWithObject:@"pitchEnabled"];
+}
+
+- (BOOL)allowsTilting {
+ return self.pitchEnabled;
+}
+
+- (void)setAllowsTilting:(BOOL)allowsTilting {
+ self.pitchEnabled = allowsTilting;
+}
+
+@end
diff --git a/platform/osx/sdk/MGLMapView.mm b/platform/osx/sdk/MGLMapView.mm
new file mode 100644
index 0000000000..c6be45c5f7
--- /dev/null
+++ b/platform/osx/sdk/MGLMapView.mm
@@ -0,0 +1,1636 @@
+#import "MGLMapView_Private.h"
+#import "MGLAccountManager_Private.h"
+#import "MGLAttributionButton.h"
+#import "MGLCompassCell.h"
+#import "MGLOpenGLLayer.h"
+#import "MGLStyle.h"
+
+#import "../../darwin/MGLGeometry_Private.h"
+#import "../../darwin/MGLMultiPoint_Private.h"
+
+#import <mbgl/darwin/MGLPolygon.h>
+#import <mbgl/darwin/MGLPolyline.h>
+#import <mbgl/osx/MGLAnnotationImage.h>
+#import <mbgl/osx/MGLMapViewDelegate.h>
+
+#import <mbgl/mbgl.hpp>
+#import <mbgl/annotation/point_annotation.hpp>
+#import <mbgl/map/camera.hpp>
+#import <mbgl/platform/darwin/reachability.h>
+#import <mbgl/platform/gl.hpp>
+#import <mbgl/sprite/sprite_image.hpp>
+#import <mbgl/storage/default_file_source.hpp>
+#import <mbgl/storage/network_status.hpp>
+#import <mbgl/storage/sqlite_cache.hpp>
+#import <mbgl/util/constants.hpp>
+#import <mbgl/util/math.hpp>
+#import <mbgl/util/std.hpp>
+
+#import <map>
+#import <unordered_set>
+
+#import "NSBundle+MGLAdditions.h"
+#import "NSProcessInfo+MGLAdditions.h"
+#import "../../darwin/NSException+MGLAdditions.h"
+#import "../../darwin/NSString+MGLAdditions.h"
+
+#import <QuartzCore/QuartzCore.h>
+
+class MGLMapViewImpl;
+class MGLAnnotationContext;
+
+const CGFloat MGLOrnamentPadding = 12;
+
+const NSTimeInterval MGLAnimationDuration = 0.3;
+const CGFloat MGLKeyPanningIncrement = 150;
+const CLLocationDegrees MGLKeyRotationIncrement = 25;
+
+static NSString * const MGLVendorDirectoryName = @"com.mapbox.MapboxGL";
+
+static NSString * const MGLDefaultStyleMarkerSymbolName = @"default_marker";
+static NSString * const MGLAnnotationSpritePrefix = @"com.mapbox.sprites.";
+const CGFloat MGLAnnotationImagePaddingForHitTest = 4;
+const CGFloat MGLAnnotationImagePaddingForCallout = 4;
+
+struct MGLAttribution {
+ NSString *title;
+ NSString *urlString;
+} MGLAttributions[] = {
+ { @"Mapbox", @"https://www.mapbox.com/about/maps/" },
+ { @"OpenStreetMap", @"http://www.openstreetmap.org/about/" },
+};
+
+typedef uint32_t MGLAnnotationID;
+enum { MGLAnnotationNotFound = UINT32_MAX };
+typedef std::map<MGLAnnotationID, MGLAnnotationContext> MGLAnnotationContextMap;
+
+NSImage *MGLDefaultMarkerImage() {
+ NSString *path = [[NSBundle mgl_resourceBundle] pathForResource:MGLDefaultStyleMarkerSymbolName
+ ofType:@"pdf"];
+ return [[NSImage alloc] initWithContentsOfFile:path];
+}
+
+std::chrono::steady_clock::duration MGLDurationInSeconds(float duration) {
+ return std::chrono::duration_cast<std::chrono::steady_clock::duration>(std::chrono::duration<float, std::chrono::seconds::period>(duration));
+}
+
+mbgl::Color MGLColorObjectFromNSColor(NSColor *color) {
+ if (!color) {
+ return {{ 0, 0, 0, 0 }};
+ }
+ CGFloat r, g, b, a;
+ [[color colorUsingColorSpaceName:NSCalibratedRGBColorSpace] getRed:&r green:&g blue:&b alpha:&a];
+ return {{ (float)r, (float)g, (float)b, (float)a }};
+}
+
+class MGLAnnotationContext {
+public:
+ id <MGLAnnotation> annotation;
+ NSString *symbolIdentifier;
+};
+
+@interface MGLMapView () <NSPopoverDelegate, MGLMultiPointDelegate>
+
+@property (nonatomic, readwrite) NSSegmentedControl *zoomControls;
+@property (nonatomic, readwrite) NSSlider *compass;
+@property (nonatomic, readwrite) NSImageView *logoView;
+@property (nonatomic, readwrite) NSView *attributionView;
+
+@property (nonatomic) NS_MUTABLE_DICTIONARY_OF(NSString *, MGLAnnotationImage *) *annotationImagesByIdentifier;
+@property (nonatomic) NSPopover *calloutForSelectedAnnotation;
+
+@property (nonatomic, readwrite, getter=isDormant) BOOL dormant;
+
+@end
+
+@implementation MGLMapView {
+ mbgl::Map *_mbglMap;
+ MGLMapViewImpl *_mbglView;
+ std::shared_ptr<mbgl::SQLiteCache> _mbglFileCache;
+ mbgl::DefaultFileSource *_mbglFileSource;
+
+ NSMagnificationGestureRecognizer *_magnificationGestureRecognizer;
+ NSRotationGestureRecognizer *_rotationGestureRecognizer;
+ double _scaleAtBeginningOfGesture;
+ CLLocationDirection _directionAtBeginningOfGesture;
+ CGFloat _pitchAtBeginningOfGesture;
+
+ MGLAnnotationContextMap _annotationContextsByAnnotationID;
+ MGLAnnotationID _selectedAnnotationID;
+ NSRect _unionedAnnotationImageAlignmentRect;
+ std::vector<MGLAnnotationID> _annotationsNearbyLastClick;
+ BOOL _delegateHasAlphasForShapeAnnotations;
+ BOOL _delegateHasStrokeColorsForShapeAnnotations;
+ BOOL _delegateHasFillColorsForShapeAnnotations;
+ BOOL _delegateHasLineWidthsForShapeAnnotations;
+
+ BOOL _isTargetingInterfaceBuilder;
+ CLLocationDegrees _pendingLatitude;
+ CLLocationDegrees _pendingLongitude;
+}
+
+#pragma mark Lifecycle
+
+- (instancetype)initWithFrame:(NSRect)frameRect {
+ if (self = [super initWithFrame:frameRect]) {
+ [self commonInit];
+ self.styleURL = nil;
+ }
+ return self;
+}
+
+- (instancetype)initWithFrame:(CGRect)frame styleURL:(nullable NSURL *)styleURL {
+ if (self = [super initWithFrame:frame]) {
+ [self commonInit];
+ self.styleURL = styleURL;
+ }
+ return self;
+}
+
+- (instancetype)initWithCoder:(nonnull NSCoder *)decoder {
+ if (self = [super initWithCoder:decoder]) {
+ [self commonInit];
+ }
+ return self;
+}
+
+- (void)awakeFromNib {
+ [super awakeFromNib];
+
+ self.styleURL = nil;
+}
+
++ (NSArray *)restorableStateKeyPaths {
+ return @[@"zoomLevel", @"direction"];
+}
+
+- (void)commonInit {
+ _isTargetingInterfaceBuilder = NSProcessInfo.processInfo.mgl_isInterfaceBuilderDesignablesAgent;
+
+ _mbglView = new MGLMapViewImpl(self, [NSScreen mainScreen].backingScaleFactor);
+
+ // Place the cache in a location that can be shared among all the
+ // applications that embed the Mapbox OS X SDK.
+ NSURL *cacheDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory
+ inDomain:NSUserDomainMask
+ appropriateForURL:nil
+ create:YES
+ error:nil];
+ cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:MGLVendorDirectoryName];
+ [[NSFileManager defaultManager] createDirectoryAtURL:cacheDirectoryURL
+ withIntermediateDirectories:YES
+ attributes:nil
+ error:nil];
+ NSURL *cacheURL = [cacheDirectoryURL URLByAppendingPathComponent:@"cache.db"];
+ NSString *cachePath = cacheURL ? cacheURL.path : @"";
+ _mbglFileCache = mbgl::SharedSQLiteCache::get(cachePath.UTF8String);
+ _mbglFileSource = new mbgl::DefaultFileSource(_mbglFileCache.get());
+
+ _mbglMap = new mbgl::Map(*_mbglView, *_mbglFileSource, mbgl::MapMode::Continuous);
+
+ self.layer = _isTargetingInterfaceBuilder ? [CALayer layer] : [MGLOpenGLLayer layer];
+
+ // Observe for changes to the global access token (and find out the current one).
+ [[MGLAccountManager sharedManager] addObserver:self
+ forKeyPath:@"accessToken"
+ options:(NSKeyValueObservingOptionInitial |
+ NSKeyValueObservingOptionNew)
+ context:NULL];
+
+ // Notify map object when network reachability status changes.
+ MGLReachability *reachability = [MGLReachability reachabilityForInternetConnection];
+ reachability.reachableBlock = ^(MGLReachability *) {
+ mbgl::NetworkStatus::Reachable();
+ };
+ [reachability startNotifier];
+
+ [self installZoomControls];
+ [self installCompass];
+ [self installLogoView];
+ [self installAttributionView];
+ [self installGestureRecognizers];
+
+ _annotationImagesByIdentifier = [NSMutableDictionary dictionary];
+ _annotationContextsByAnnotationID = {};
+ _selectedAnnotationID = MGLAnnotationNotFound;
+ _unionedAnnotationImageAlignmentRect = NSZeroRect;
+
+ mbgl::CameraOptions options;
+ options.center = mbgl::LatLng(0, 0);
+ options.zoom = _mbglMap->getMinZoom();
+ _mbglMap->jumpTo(options);
+}
+
+- (void)installZoomControls {
+ _zoomControls = [[NSSegmentedControl alloc] initWithFrame:NSZeroRect];
+ _zoomControls.wantsLayer = YES;
+ _zoomControls.layer.opacity = 0.9;
+ [(NSSegmentedCell *)_zoomControls.cell setTrackingMode:NSSegmentSwitchTrackingMomentary];
+ _zoomControls.continuous = YES;
+ _zoomControls.segmentCount = 2;
+ [_zoomControls setLabel:@"−" forSegment:0];
+ [(NSSegmentedCell *)_zoomControls.cell setTag:0 forSegment:0];
+ [(NSSegmentedCell *)_zoomControls.cell setToolTip:@"Zoom Out" forSegment:0];
+ [_zoomControls setLabel:@"+" forSegment:1];
+ [(NSSegmentedCell *)_zoomControls.cell setTag:1 forSegment:1];
+ [(NSSegmentedCell *)_zoomControls.cell setToolTip:@"Zoom In" forSegment:1];
+ _zoomControls.target = self;
+ _zoomControls.action = @selector(zoomInOrOut:);
+ _zoomControls.controlSize = NSRegularControlSize;
+ [_zoomControls sizeToFit];
+ _zoomControls.translatesAutoresizingMaskIntoConstraints = NO;
+ [self addSubview:_zoomControls];
+}
+
+- (void)installCompass {
+ _compass = [[NSSlider alloc] initWithFrame:NSZeroRect];
+ _compass.wantsLayer = YES;
+ _compass.layer.opacity = 0.9;
+ _compass.cell = [[MGLCompassCell alloc] init];
+ _compass.continuous = YES;
+ _compass.target = self;
+ _compass.action = @selector(rotate:);
+ [_compass sizeToFit];
+ _compass.translatesAutoresizingMaskIntoConstraints = NO;
+ [self addSubview:_compass];
+}
+
+- (void)installLogoView {
+ _logoView = [[NSImageView alloc] initWithFrame:NSZeroRect];
+ _logoView.wantsLayer = YES;
+ NSImage *logoImage = [[NSImage alloc] initWithContentsOfFile:
+ [[NSBundle mgl_resourceBundle] pathForResource:@"mapbox" ofType:@"pdf"]];
+ logoImage.alignmentRect = NSInsetRect(logoImage.alignmentRect, 3, 3);
+ _logoView.image = logoImage;
+ _logoView.translatesAutoresizingMaskIntoConstraints = NO;
+ _logoView.accessibilityTitle = @"Mapbox";
+ [self addSubview:_logoView];
+}
+
+- (void)installAttributionView {
+ _attributionView = [[NSView alloc] initWithFrame:NSZeroRect];
+ _attributionView.wantsLayer = YES;
+ _attributionView.layer.opacity = 0.6;
+ CIFilter *attributionBlurFilter = [CIFilter filterWithName:@"CIGaussianBlur"];
+ [attributionBlurFilter setDefaults];
+ CIFilter *attributionColorFilter = [CIFilter filterWithName:@"CIColorControls"];
+ [attributionColorFilter setDefaults];
+ [attributionColorFilter setValue:@(0.1) forKey:kCIInputBrightnessKey];
+ _attributionView.backgroundFilters = @[attributionColorFilter, attributionBlurFilter];
+ _attributionView.layer.cornerRadius = 4;
+ _attributionView.translatesAutoresizingMaskIntoConstraints = NO;
+ [self addSubview:_attributionView];
+ [self updateAttributionView];
+}
+
+- (void)installGestureRecognizers {
+ self.acceptsTouchEvents = YES;
+ _scrollEnabled = YES;
+ _zoomEnabled = YES;
+ _rotateEnabled = YES;
+ _pitchEnabled = YES;
+
+ NSPanGestureRecognizer *panGestureRecognizer = [[NSPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)];
+ panGestureRecognizer.delaysKeyEvents = YES;
+ [self addGestureRecognizer:panGestureRecognizer];
+
+ NSClickGestureRecognizer *clickGestureRecognizer = [[NSClickGestureRecognizer alloc] initWithTarget:self action:@selector(handleClickGesture:)];
+ clickGestureRecognizer.delaysPrimaryMouseButtonEvents = NO;
+ [self addGestureRecognizer:clickGestureRecognizer];
+
+ NSClickGestureRecognizer *secondaryClickGestureRecognizer = [[NSClickGestureRecognizer alloc] initWithTarget:self action:@selector(handleSecondaryClickGesture:)];
+ secondaryClickGestureRecognizer.buttonMask = 0x2;
+ [self addGestureRecognizer:secondaryClickGestureRecognizer];
+
+ NSClickGestureRecognizer *doubleClickGestureRecognizer = [[NSClickGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleClickGesture:)];
+ doubleClickGestureRecognizer.numberOfClicksRequired = 2;
+ doubleClickGestureRecognizer.delaysPrimaryMouseButtonEvents = NO;
+ [self addGestureRecognizer:doubleClickGestureRecognizer];
+
+ _magnificationGestureRecognizer = [[NSMagnificationGestureRecognizer alloc] initWithTarget:self action:@selector(handleMagnificationGesture:)];
+ [self addGestureRecognizer:_magnificationGestureRecognizer];
+
+ _rotationGestureRecognizer = [[NSRotationGestureRecognizer alloc] initWithTarget:self action:@selector(handleRotationGesture:)];
+ [self addGestureRecognizer:_rotationGestureRecognizer];
+}
+
+- (void)updateAttributionView {
+ self.attributionView.subviews = @[];
+
+ for (NSUInteger i = 0; i < sizeof(MGLAttributions) / sizeof(MGLAttributions[0]); i++) {
+ NSURL *url = [NSURL URLWithString:MGLAttributions[i].urlString];
+ NSButton *button = [[MGLAttributionButton alloc] initWithTitle:MGLAttributions[i].title URL:url];
+ button.controlSize = NSMiniControlSize;
+ button.translatesAutoresizingMaskIntoConstraints = NO;
+
+ NSView *previousView = self.attributionView.subviews.lastObject;
+ [self.attributionView addSubview:button];
+
+ [_attributionView addConstraint:
+ [NSLayoutConstraint constraintWithItem:button
+ attribute:NSLayoutAttributeBottom
+ relatedBy:NSLayoutRelationEqual
+ toItem:_attributionView
+ attribute:NSLayoutAttributeBottom
+ multiplier:1
+ constant:0]];
+ [_attributionView addConstraint:
+ [NSLayoutConstraint constraintWithItem:button
+ attribute:NSLayoutAttributeLeading
+ relatedBy:NSLayoutRelationEqual
+ toItem:previousView ? previousView : _attributionView
+ attribute:previousView ? NSLayoutAttributeTrailing : NSLayoutAttributeLeading
+ multiplier:1
+ constant:8]];
+ }
+}
+
+- (void)dealloc {
+ [[MGLAccountManager sharedManager] removeObserver:self forKeyPath:@"accessToken"];
+
+ [self.calloutForSelectedAnnotation close];
+ self.calloutForSelectedAnnotation = nil;
+
+ if (_mbglMap) {
+ delete _mbglMap;
+ _mbglMap = nullptr;
+ }
+ if (_mbglFileSource) {
+ delete _mbglFileSource;
+ _mbglFileSource = nullptr;
+ }
+ if (_mbglView) {
+ delete _mbglView;
+ _mbglView = nullptr;
+ }
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(__unused void *)context {
+ // Synchronize mbgl::Map’s access token with the global one in MGLAccountManager.
+ if ([keyPath isEqualToString:@"accessToken"] && object == [MGLAccountManager sharedManager]) {
+ NSString *accessToken = change[NSKeyValueChangeNewKey];
+ if (![accessToken isKindOfClass:[NSNull class]]) {
+ _mbglFileSource->setAccessToken((std::string)[accessToken UTF8String]);
+ }
+ }
+}
+
+- (void)setDelegate:(id<MGLMapViewDelegate>)delegate {
+ _delegate = delegate;
+
+ _delegateHasAlphasForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:alphaForShapeAnnotation:)];
+ _delegateHasStrokeColorsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:strokeColorForShapeAnnotation:)];
+ _delegateHasFillColorsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:fillColorForPolygonAnnotation:)];
+ _delegateHasLineWidthsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:lineWidthForPolylineAnnotation:)];
+}
+
+#pragma mark Style
+
+- (nonnull NSURL *)styleURL {
+ NSString *styleURLString = @(_mbglMap->getStyleURL().c_str()).mgl_stringOrNilIfEmpty;
+ return styleURLString ? [NSURL URLWithString:styleURLString] : [MGLStyle streetsStyleURL];
+}
+
+- (void)setStyleURL:(nullable NSURL *)styleURL {
+ if (_isTargetingInterfaceBuilder) {
+ return;
+ }
+
+ if (!styleURL) {
+ if (![MGLAccountManager accessToken]) {
+ return;
+ }
+ styleURL = [MGLStyle streetsStyleURL];
+ }
+
+ if (![styleURL scheme]) {
+ // Assume a relative path into the application’s resource folder.
+ styleURL = [NSURL URLWithString:[@"asset://" stringByAppendingString:[styleURL absoluteString]]];
+ }
+
+ _mbglMap->setStyleURL([[styleURL absoluteString] UTF8String]);
+}
+
+- (IBAction)reloadStyle:(__unused id)sender {
+ NSURL *styleURL = self.styleURL;
+ _mbglMap->setStyleURL("");
+ self.styleURL = styleURL;
+}
+
+#pragma mark View hierarchy and drawing
+
+- (void)viewWillMoveToWindow:(NSWindow *)newWindow {
+ [self deselectAnnotation:self.selectedAnnotation animated:NO];
+ if (!self.dormant && !newWindow) {
+ self.dormant = YES;
+ _mbglMap->pause();
+ }
+}
+
+- (void)viewDidMoveToWindow {
+ if (self.dormant && self.window) {
+ _mbglMap->resume();
+ self.dormant = NO;
+ }
+}
+
+- (BOOL)wantsLayer {
+ return YES;
+}
+
+- (BOOL)wantsBestResolutionOpenGLSurface {
+ return !_isTargetingInterfaceBuilder;
+}
+
+- (void)setFrame:(NSRect)frame {
+ super.frame = frame;
+ if (!_isTargetingInterfaceBuilder) {
+ _mbglMap->update(mbgl::Update::Dimensions);
+ }
+}
+
+- (void)updateConstraints {
+ [self addConstraint:
+ [NSLayoutConstraint constraintWithItem:self
+ attribute:NSLayoutAttributeBottom
+ relatedBy:NSLayoutRelationEqual
+ toItem:_zoomControls
+ attribute:NSLayoutAttributeBottom
+ multiplier:1
+ constant:MGLOrnamentPadding]];
+ [self addConstraint:
+ [NSLayoutConstraint constraintWithItem:self
+ attribute:NSLayoutAttributeTrailing
+ relatedBy:NSLayoutRelationEqual
+ toItem:_zoomControls
+ attribute:NSLayoutAttributeTrailing
+ multiplier:1
+ constant:MGLOrnamentPadding]];
+
+ [self addConstraint:
+ [NSLayoutConstraint constraintWithItem:_compass
+ attribute:NSLayoutAttributeCenterX
+ relatedBy:NSLayoutRelationEqual
+ toItem:_zoomControls
+ attribute:NSLayoutAttributeCenterX
+ multiplier:1
+ constant:0]];
+ [self addConstraint:
+ [NSLayoutConstraint constraintWithItem:_zoomControls
+ attribute:NSLayoutAttributeTop
+ relatedBy:NSLayoutRelationEqual
+ toItem:_compass
+ attribute:NSLayoutAttributeBottom
+ multiplier:1
+ constant:8]];
+
+ [self addConstraint:
+ [NSLayoutConstraint constraintWithItem:self
+ attribute:NSLayoutAttributeBottom
+ relatedBy:NSLayoutRelationEqual
+ toItem:_logoView
+ attribute:NSLayoutAttributeBottom
+ multiplier:1
+ constant:MGLOrnamentPadding - _logoView.image.alignmentRect.origin.y]];
+ [self addConstraint:
+ [NSLayoutConstraint constraintWithItem:_logoView
+ attribute:NSLayoutAttributeLeading
+ relatedBy:NSLayoutRelationEqual
+ toItem:self
+ attribute:NSLayoutAttributeLeading
+ multiplier:1
+ constant:MGLOrnamentPadding - _logoView.image.alignmentRect.origin.x]];
+
+ [self addConstraint:[NSLayoutConstraint constraintWithItem:_logoView
+ attribute:NSLayoutAttributeBaseline
+ relatedBy:NSLayoutRelationEqual
+ toItem:_attributionView
+ attribute:NSLayoutAttributeBaseline
+ multiplier:1
+ constant:_logoView.image.alignmentRect.origin.y]];
+ [self addConstraint:[NSLayoutConstraint constraintWithItem:_attributionView
+ attribute:NSLayoutAttributeLeading
+ relatedBy:NSLayoutRelationEqual
+ toItem:_logoView
+ attribute:NSLayoutAttributeTrailing
+ multiplier:1
+ constant:8]];
+ [self addConstraint:[NSLayoutConstraint constraintWithItem:_attributionView.subviews.firstObject
+ attribute:NSLayoutAttributeTop
+ relatedBy:NSLayoutRelationEqual
+ toItem:_attributionView
+ attribute:NSLayoutAttributeTop
+ multiplier:1
+ constant:0]];
+ [self addConstraint:[NSLayoutConstraint constraintWithItem:_attributionView
+ attribute:NSLayoutAttributeTrailing
+ relatedBy:NSLayoutRelationEqual
+ toItem:_attributionView.subviews.lastObject
+ attribute:NSLayoutAttributeTrailing
+ multiplier:1
+ constant:8]];
+
+ [super updateConstraints];
+}
+
+- (void)renderSync {
+ if (!self.dormant) {
+ CGFloat zoomFactor = _mbglMap->getMaxZoom() - _mbglMap->getMinZoom() + 1;
+ CGFloat cpuFactor = (CGFloat)[NSProcessInfo processInfo].processorCount;
+ CGFloat memoryFactor = (CGFloat)[NSProcessInfo processInfo].physicalMemory / 1000 / 1000 / 1000;
+ CGFloat sizeFactor = ((CGFloat)_mbglMap->getWidth() / mbgl::util::tileSize) * ((CGFloat)_mbglMap->getHeight() / mbgl::util::tileSize);
+
+ NSUInteger cacheSize = zoomFactor * cpuFactor * memoryFactor * sizeFactor * 0.5;
+
+ _mbglMap->setSourceTileCacheSize(cacheSize);
+ _mbglMap->renderSync();
+
+// [self updateUserLocationAnnotationView];
+ }
+}
+
+- (void)invalidate {
+ MGLAssertIsMainThread();
+
+ [self.layer setNeedsDisplay];
+}
+
+- (void)notifyMapChange:(mbgl::MapChange)change {
+ // Ignore map updates when the Map object isn't set.
+ if (!_mbglMap) {
+ return;
+ }
+
+ switch (change) {
+ case mbgl::MapChangeRegionWillChange:
+ case mbgl::MapChangeRegionWillChangeAnimated:
+ {
+ if ([self.delegate respondsToSelector:@selector(mapView:regionWillChangeAnimated:)]) {
+ BOOL animated = change == mbgl::MapChangeRegionWillChangeAnimated;
+ [self.delegate mapView:self regionWillChangeAnimated:animated];
+ }
+ break;
+ }
+ case mbgl::MapChangeRegionIsChanging:
+ {
+ [self updateCompass];
+ [self updateAnnotationCallouts];
+
+ if ([self.delegate respondsToSelector:@selector(mapViewRegionIsChanging:)]) {
+ [self.delegate mapViewRegionIsChanging:self];
+ }
+ break;
+ }
+ case mbgl::MapChangeRegionDidChange:
+ case mbgl::MapChangeRegionDidChangeAnimated:
+ {
+ [self updateZoomControls];
+ [self updateCompass];
+ [self updateAnnotationCallouts];
+
+ if ([self.delegate respondsToSelector:@selector(mapView:regionDidChangeAnimated:)]) {
+ BOOL animated = change == mbgl::MapChangeRegionDidChangeAnimated;
+ [self.delegate mapView:self regionDidChangeAnimated:animated];
+ }
+ break;
+ }
+ case mbgl::MapChangeWillStartLoadingMap:
+ {
+ if ([self.delegate respondsToSelector:@selector(mapViewWillStartLoadingMap:)]) {
+ [self.delegate mapViewWillStartLoadingMap:self];
+ }
+ break;
+ }
+ case mbgl::MapChangeDidFinishLoadingMap:
+ {
+ if ([self.delegate respondsToSelector:@selector(mapViewDidFinishLoadingMap:)]) {
+ [self.delegate mapViewDidFinishLoadingMap:self];
+ }
+ break;
+ }
+ case mbgl::MapChangeDidFailLoadingMap:
+ {
+ // Not yet implemented.
+ break;
+ }
+ case mbgl::MapChangeWillStartRenderingMap:
+ {
+ if ([self.delegate respondsToSelector:@selector(mapViewWillStartRenderingMap:)]) {
+ [self.delegate mapViewWillStartRenderingMap:self];
+ }
+ break;
+ }
+ case mbgl::MapChangeDidFinishRenderingMap:
+ case mbgl::MapChangeDidFinishRenderingMapFullyRendered:
+ {
+ if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingMap:fullyRendered:)]) {
+ BOOL fullyRendered = change == mbgl::MapChangeDidFinishRenderingMapFullyRendered;
+ [self.delegate mapViewDidFinishRenderingMap:self fullyRendered:fullyRendered];
+ }
+ break;
+ }
+ case mbgl::MapChangeWillStartRenderingFrame:
+ {
+ if ([self.delegate respondsToSelector:@selector(mapViewWillStartRenderingFrame:)]) {
+ [self.delegate mapViewWillStartRenderingFrame:self];
+ }
+ break;
+ }
+ case mbgl::MapChangeDidFinishRenderingFrame:
+ case mbgl::MapChangeDidFinishRenderingFrameFullyRendered:
+ {
+ if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingFrame:fullyRendered:)]) {
+ BOOL fullyRendered = change == mbgl::MapChangeDidFinishRenderingFrameFullyRendered;
+ [self.delegate mapViewDidFinishRenderingFrame:self fullyRendered:fullyRendered];
+ }
+ break;
+ }
+ }
+}
+
+#pragma mark Viewport
+
+- (CLLocationCoordinate2D)centerCoordinate {
+ return MGLLocationCoordinate2DFromLatLng(_mbglMap->getLatLng());
+}
+
+- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate {
+ [self setCenterCoordinate:centerCoordinate animated:NO];
+}
+
+- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate animated:(BOOL)animated {
+ [self willChangeValueForKey:@"centerCoordinate"];
+ _mbglMap->setLatLng(MGLLatLngFromLocationCoordinate2D(centerCoordinate),
+ MGLDurationInSeconds(animated ? MGLAnimationDuration : 0));
+ [self didChangeValueForKey:@"centerCoordinate"];
+}
+
+- (void)offsetCenterCoordinateBy:(NSPoint)delta animated:(BOOL)animated {
+ [self willChangeValueForKey:@"centerCoordinate"];
+ _mbglMap->cancelTransitions();
+ _mbglMap->moveBy({ delta.x, delta.y },
+ MGLDurationInSeconds(animated ? MGLAnimationDuration : 0));
+ [self didChangeValueForKey:@"centerCoordinate"];
+}
+
+- (double)zoomLevel {
+ return _mbglMap->getZoom();
+}
+
+- (void)setZoomLevel:(double)zoomLevel {
+ [self setZoomLevel:zoomLevel animated:NO];
+}
+
+- (void)setZoomLevel:(double)zoomLevel animated:(BOOL)animated {
+ _mbglMap->setZoom(zoomLevel, MGLDurationInSeconds(animated ? MGLAnimationDuration : 0));
+}
+
+- (void)scaleBy:(double)scaleFactor atPoint:(NSPoint)point animated:(BOOL)animated {
+ [self willChangeValueForKey:@"zoomLevel"];
+ mbgl::PrecisionPoint center(point.x, point.y);
+ _mbglMap->scaleBy(scaleFactor, center, MGLDurationInSeconds(animated ? MGLAnimationDuration : 0));
+ [self didChangeValueForKey:@"zoomLevel"];
+}
+
+- (double)maximumZoomLevel {
+ return _mbglMap->getMaxZoom();
+}
+
+- (double)minimumZoomLevel {
+ return _mbglMap->getMinZoom();
+}
+
+- (IBAction)zoomInOrOut:(NSSegmentedControl *)sender {
+ switch (sender.selectedSegment) {
+ case 0:
+ [self moveToEndOfParagraph:sender];
+ break;
+ case 1:
+ [self moveToBeginningOfParagraph:sender];
+ break;
+ default:
+ break;
+ }
+}
+
+- (CLLocationDirection)direction {
+ return mbgl::util::wrap(_mbglMap->getBearing(), 0., 360.);
+}
+
+- (void)setDirection:(CLLocationDirection)direction {
+ [self setDirection:direction animated:NO];
+}
+
+- (void)setDirection:(CLLocationDirection)direction animated:(BOOL)animated {
+ [self willChangeValueForKey:@"direction"];
+ _mbglMap->setBearing(direction, MGLDurationInSeconds(animated ? MGLAnimationDuration : 0));
+ [self didChangeValueForKey:@"direction"];
+}
+
+- (void)offsetDirectionBy:(CLLocationDegrees)delta animated:(BOOL)animated {
+ [self willChangeValueForKey:@"direction"];
+ _mbglMap->cancelTransitions();
+ _mbglMap->setBearing(_mbglMap->getBearing() + delta, MGLDurationInSeconds(animated ? MGLAnimationDuration : 0));
+ [self didChangeValueForKey:@"direction"];
+}
+
++ (NSSet *)keyPathsForValuesAffectingVisibleCoordinateBounds {
+ return [NSSet setWithObjects:@"centerCoordinate", @"zoomLevel", @"direction", @"bounds", nil];
+}
+
+- (MGLCoordinateBounds)visibleCoordinateBounds {
+ return [self convertRectToCoordinateBounds:self.bounds];
+}
+
+- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds {
+ [self setVisibleCoordinateBounds:bounds animated:NO];
+}
+
+- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds animated:(BOOL)animated {
+ [self setVisibleCoordinateBounds:bounds edgePadding:NSEdgeInsetsZero animated:animated];
+}
+
+- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(NSEdgeInsets)insets animated:(BOOL)animated {
+ _mbglMap->cancelTransitions();
+
+ mbgl::EdgeInsets mbglInsets = {insets.top, insets.left, insets.bottom, insets.right};
+ mbgl::CameraOptions options = _mbglMap->cameraForLatLngBounds(MGLLatLngBoundsFromCoordinateBounds(bounds), mbglInsets);
+ if (animated) {
+ options.duration = MGLDurationInSeconds(MGLAnimationDuration);
+ }
+
+ [self willChangeValueForKey:@"visibleCoordinateBounds"];
+ options.transitionFinishFn = ^() {
+ [self didChangeValueForKey:@"visibleCoordinateBounds"];
+ };
+ _mbglMap->easeTo(options);
+}
+
+#pragma mark Mouse events and gestures
+
+- (BOOL)acceptsFirstResponder {
+ return YES;
+}
+
+- (void)handlePanGesture:(NSPanGestureRecognizer *)gestureRecognizer {
+ NSPoint delta = [gestureRecognizer translationInView:self];
+ NSPoint endPoint = [gestureRecognizer locationInView:self];
+ NSPoint startPoint = NSMakePoint(endPoint.x - delta.x, self.bounds.size.height - (endPoint.y - delta.y));
+
+ NSEventModifierFlags flags = [NSApp currentEvent].modifierFlags;
+ if (flags & NSShiftKeyMask) {
+ if (!self.zoomEnabled) {
+ return;
+ }
+
+ _mbglMap->cancelTransitions();
+
+ if (gestureRecognizer.state == NSGestureRecognizerStateBegan) {
+ _mbglMap->setGestureInProgress(true);
+ _scaleAtBeginningOfGesture = _mbglMap->getScale();
+ } else if (gestureRecognizer.state == NSGestureRecognizerStateChanged) {
+ CGFloat newZoomLevel = log2f(_scaleAtBeginningOfGesture) - delta.y / 75;
+ [self scaleBy:powf(2, newZoomLevel) / _mbglMap->getScale() atPoint:startPoint animated:NO];
+ } else if (gestureRecognizer.state == NSGestureRecognizerStateEnded
+ || gestureRecognizer.state == NSGestureRecognizerStateCancelled) {
+ _mbglMap->setGestureInProgress(false);
+ // Maps.app locks the cursor to the start point, but that would
+ // interfere with the pan gesture recognizer. Just move the cursor
+ // back at the end of the gesture.
+ CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, startPoint);
+ }
+ } else if (flags & NSAlternateKeyMask) {
+ _mbglMap->cancelTransitions();
+
+ if (gestureRecognizer.state == NSGestureRecognizerStateBegan) {
+ _mbglMap->setGestureInProgress(true);
+ _directionAtBeginningOfGesture = self.direction;
+ _pitchAtBeginningOfGesture = _mbglMap->getPitch();
+ } else if (gestureRecognizer.state == NSGestureRecognizerStateChanged) {
+ mbgl::PrecisionPoint center(startPoint.x, startPoint.y);
+ if (self.rotateEnabled) {
+ CLLocationDirection newDirection = _directionAtBeginningOfGesture - delta.x / 10;
+ [self willChangeValueForKey:@"direction"];
+ _mbglMap->setBearing(newDirection, center);
+ [self didChangeValueForKey:@"direction"];
+ }
+ if (self.pitchEnabled) {
+ _mbglMap->setPitch(_pitchAtBeginningOfGesture + delta.y / 5);
+ }
+ } else if (gestureRecognizer.state == NSGestureRecognizerStateEnded
+ || gestureRecognizer.state == NSGestureRecognizerStateCancelled) {
+ _mbglMap->setGestureInProgress(false);
+ }
+ } else if (self.scrollEnabled) {
+ _mbglMap->cancelTransitions();
+
+ if (gestureRecognizer.state == NSGestureRecognizerStateBegan) {
+ [[NSCursor closedHandCursor] push];
+ _mbglMap->setGestureInProgress(true);
+ } else if (gestureRecognizer.state == NSGestureRecognizerStateChanged) {
+ delta.y *= -1;
+ [self offsetCenterCoordinateBy:delta animated:NO];
+ [gestureRecognizer setTranslation:NSZeroPoint inView:self];
+ } else if (gestureRecognizer.state == NSGestureRecognizerStateEnded
+ || gestureRecognizer.state == NSGestureRecognizerStateCancelled) {
+ _mbglMap->setGestureInProgress(false);
+ [[NSCursor arrowCursor] pop];
+ }
+ }
+}
+
+- (void)handleMagnificationGesture:(NSMagnificationGestureRecognizer *)gestureRecognizer {
+ if (!self.zoomEnabled) {
+ return;
+ }
+
+ _mbglMap->cancelTransitions();
+
+ if (gestureRecognizer.state == NSGestureRecognizerStateBegan) {
+ _mbglMap->setGestureInProgress(true);
+ _scaleAtBeginningOfGesture = _mbglMap->getScale();
+ } else if (gestureRecognizer.state == NSGestureRecognizerStateChanged) {
+ NSPoint zoomInPoint = [gestureRecognizer locationInView:self];
+ mbgl::PrecisionPoint center(zoomInPoint.x, self.bounds.size.height - zoomInPoint.y);
+ if (gestureRecognizer.magnification > -1) {
+ [self willChangeValueForKey:@"zoomLevel"];
+ [self willChangeValueForKey:@"centerCoordinate"];
+ _mbglMap->setScale(_scaleAtBeginningOfGesture * (1 + gestureRecognizer.magnification), center);
+ [self didChangeValueForKey:@"centerCoordinate"];
+ [self didChangeValueForKey:@"zoomLevel"];
+ }
+ } else if (gestureRecognizer.state == NSGestureRecognizerStateEnded
+ || gestureRecognizer.state == NSGestureRecognizerStateCancelled) {
+ _mbglMap->setGestureInProgress(false);
+ }
+}
+
+- (void)handleClickGesture:(NSClickGestureRecognizer *)gestureRecognizer {
+ NSPoint gesturePoint = [gestureRecognizer locationInView:self];
+ NSRect hitRect = NSOffsetRect(_unionedAnnotationImageAlignmentRect,
+ gesturePoint.x, gesturePoint.y);
+ hitRect = NSInsetRect(hitRect, -MGLAnnotationImagePaddingForHitTest,
+ -MGLAnnotationImagePaddingForHitTest);
+ mbgl::LatLngBounds hitBounds = [self convertRectToLatLngBounds:hitRect];
+ std::vector<MGLAnnotationID> nearbyAnnotations = _mbglMap->getPointAnnotationsInBounds(hitBounds);
+
+ if (nearbyAnnotations.size()) {
+ mbgl::util::erase_if(nearbyAnnotations, [&](const MGLAnnotationID annotationID) {
+ NSAssert(_annotationContextsByAnnotationID.count(annotationID) != 0, @"Unknown annotation found nearby click");
+ NSString *customSymbol = _annotationContextsByAnnotationID[annotationID].symbolIdentifier;
+ NSString *symbolName = customSymbol.length ? customSymbol : MGLDefaultStyleMarkerSymbolName;
+ MGLAnnotationImage *annotationImage = [self dequeueReusableAnnotationImageWithIdentifier:symbolName];
+ return !annotationImage.selectable;
+ });
+ }
+
+ MGLAnnotationID hitAnnotationID = MGLAnnotationNotFound;
+ if (nearbyAnnotations.size()) {
+ std::sort(nearbyAnnotations.begin(), nearbyAnnotations.end());
+
+ if (nearbyAnnotations == _annotationsNearbyLastClick) {
+ if (_selectedAnnotationID == _annotationsNearbyLastClick.back()
+ || _selectedAnnotationID == MGLAnnotationNotFound) {
+ hitAnnotationID = _annotationsNearbyLastClick.front();
+ } else {
+ auto result = std::find(_annotationsNearbyLastClick.begin(),
+ _annotationsNearbyLastClick.end(),
+ _selectedAnnotationID);
+ auto distance = std::distance(_annotationsNearbyLastClick.begin(), result);
+ hitAnnotationID = _annotationsNearbyLastClick[distance + 1];
+ }
+ } else {
+ _annotationsNearbyLastClick = nearbyAnnotations;
+ hitAnnotationID = _annotationsNearbyLastClick.front();
+ }
+ }
+
+ if (hitAnnotationID != MGLAnnotationNotFound) {
+ if (hitAnnotationID != _selectedAnnotationID) {
+ id <MGLAnnotation> annotation = [self annotationWithID:hitAnnotationID];
+ NSAssert(annotation, @"Cannot select nonexistent annotation with ID %i", hitAnnotationID);
+ [self selectAnnotation:annotation animated:YES];
+ }
+ } else {
+ [self deselectAnnotation:self.selectedAnnotation animated:YES];
+ }
+}
+
+- (void)handleSecondaryClickGesture:(NSClickGestureRecognizer *)gestureRecognizer {
+ if (!self.zoomEnabled) {
+ return;
+ }
+
+ _mbglMap->cancelTransitions();
+
+ NSPoint gesturePoint = [gestureRecognizer locationInView:self];
+ [self scaleBy:0.5 atPoint:NSMakePoint(gesturePoint.x, self.bounds.size.height - gesturePoint.y) animated:YES];
+}
+
+- (void)handleDoubleClickGesture:(NSClickGestureRecognizer *)gestureRecognizer {
+ if (!self.zoomEnabled) {
+ return;
+ }
+
+ _mbglMap->cancelTransitions();
+
+ NSPoint gesturePoint = [gestureRecognizer locationInView:self];
+ [self scaleBy:2 atPoint:NSMakePoint(gesturePoint.x, self.bounds.size.height - gesturePoint.y) animated:YES];
+}
+
+- (void)handleRotationGesture:(NSRotationGestureRecognizer *)gestureRecognizer {
+ if (!self.rotateEnabled) {
+ return;
+ }
+
+ _mbglMap->cancelTransitions();
+
+ if (gestureRecognizer.state == NSGestureRecognizerStateBegan) {
+ _mbglMap->setGestureInProgress(true);
+ _directionAtBeginningOfGesture = self.direction;
+ } else if (gestureRecognizer.state == NSGestureRecognizerStateChanged) {
+ NSPoint rotationPoint = [gestureRecognizer locationInView:self];
+ mbgl::PrecisionPoint center(rotationPoint.x, rotationPoint.y);
+ _mbglMap->setBearing(_directionAtBeginningOfGesture + gestureRecognizer.rotationInDegrees, center);
+ } else if (gestureRecognizer.state == NSGestureRecognizerStateEnded
+ || gestureRecognizer.state == NSGestureRecognizerStateCancelled) {
+ _mbglMap->setGestureInProgress(false);
+ }
+}
+
+- (BOOL)wantsScrollEventsForSwipeTrackingOnAxis:(__unused NSEventGestureAxis)axis {
+ return YES;
+}
+
+- (void)scrollWheel:(NSEvent *)event {
+ // https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKitOlderNotes/#10_7Dragging
+ if (event.phase == NSEventPhaseNone && event.momentumPhase == NSEventPhaseNone) {
+ // A traditional, vertical scroll wheel zooms instead of panning.
+ if (self.zoomEnabled && std::abs(event.scrollingDeltaX) < std::abs(event.scrollingDeltaY)) {
+ _mbglMap->cancelTransitions();
+
+ [self willChangeValueForKey:@"zoomLevel"];
+ [self willChangeValueForKey:@"centerCoordinate"];
+ NSPoint gesturePoint = [self convertPoint:event.locationInWindow fromView:nil];
+ mbgl::PrecisionPoint center(gesturePoint.x, self.bounds.size.height - gesturePoint.y);
+ _mbglMap->scaleBy(exp2(event.scrollingDeltaY / 20), center);
+ [self didChangeValueForKey:@"centerCoordinate"];
+ [self didChangeValueForKey:@"zoomLevel"];
+ }
+ } else if (self.scrollEnabled
+ && _magnificationGestureRecognizer.state == NSGestureRecognizerStatePossible
+ && _rotationGestureRecognizer.state == NSGestureRecognizerStatePossible) {
+ _mbglMap->cancelTransitions();
+
+ CGFloat x = event.scrollingDeltaX;
+ CGFloat y = event.scrollingDeltaY;
+ if (x || y) {
+ [self offsetCenterCoordinateBy:NSMakePoint(x, y) animated:NO];
+ }
+
+ if (event.momentumPhase != NSEventPhaseNone) {
+ [self offsetCenterCoordinateBy:NSMakePoint(x, y) animated:NO];
+ }
+ }
+}
+
+#pragma mark Keyboard events
+
+- (void)keyDown:(NSEvent *)event {
+ if (event.modifierFlags & NSNumericPadKeyMask) {
+ [self interpretKeyEvents:@[event]];
+ } else {
+ [super keyDown:event];
+ }
+}
+
+- (IBAction)moveUp:(__unused id)sender {
+ [self offsetCenterCoordinateBy:NSMakePoint(0, MGLKeyPanningIncrement) animated:YES];
+}
+
+- (IBAction)moveDown:(__unused id)sender {
+ [self offsetCenterCoordinateBy:NSMakePoint(0, -MGLKeyPanningIncrement) animated:YES];
+}
+
+- (IBAction)moveLeft:(__unused id)sender {
+ [self offsetCenterCoordinateBy:NSMakePoint(MGLKeyPanningIncrement, 0) animated:YES];
+}
+
+- (IBAction)moveRight:(__unused id)sender {
+ [self offsetCenterCoordinateBy:NSMakePoint(-MGLKeyPanningIncrement, 0) animated:YES];
+}
+
+- (IBAction)moveToBeginningOfParagraph:(__unused id)sender {
+ if (self.zoomEnabled) {
+ [self scaleBy:2 atPoint:NSZeroPoint animated:YES];
+ }
+}
+
+- (IBAction)moveToEndOfParagraph:(__unused id)sender {
+ if (self.zoomEnabled) {
+ [self scaleBy:0.5 atPoint:NSZeroPoint animated:YES];
+ }
+}
+
+- (IBAction)moveWordLeft:(__unused id)sender {
+ if (self.rotateEnabled) {
+ [self offsetDirectionBy:MGLKeyRotationIncrement animated:YES];
+ }
+}
+
+- (IBAction)moveWordRight:(__unused id)sender {
+ if (self.rotateEnabled) {
+ [self offsetDirectionBy:-MGLKeyRotationIncrement animated:YES];
+ }
+}
+
+- (void)setZoomEnabled:(BOOL)zoomEnabled {
+ _zoomEnabled = zoomEnabled;
+ _zoomControls.enabled = zoomEnabled;
+ _zoomControls.hidden = !zoomEnabled;
+}
+
+- (void)setRotateEnabled:(BOOL)rotateEnabled {
+ _rotateEnabled = rotateEnabled;
+ _compass.enabled = rotateEnabled;
+ _compass.hidden = !rotateEnabled;
+}
+
+#pragma mark Ornaments
+
+- (void)updateZoomControls {
+ [_zoomControls setEnabled:self.zoomLevel > self.minimumZoomLevel forSegment:0];
+ [_zoomControls setEnabled:self.zoomLevel < self.maximumZoomLevel forSegment:1];
+}
+
+- (void)updateCompass {
+ _compass.doubleValue = -self.direction;
+}
+
+- (IBAction)rotate:(NSSlider *)sender {
+ [self setDirection:-sender.doubleValue animated:YES];
+}
+
+#pragma mark Annotations
+
+- (nullable NS_ARRAY_OF(id <MGLAnnotation>) *)annotations {
+ if (_annotationContextsByAnnotationID.empty()) {
+ return nil;
+ }
+
+ std::vector<id <MGLAnnotation> > annotations;
+ std::transform(_annotationContextsByAnnotationID.begin(),
+ _annotationContextsByAnnotationID.end(),
+ std::back_inserter(annotations),
+ ^ id <MGLAnnotation> (const std::pair<MGLAnnotationID, MGLAnnotationContext> &pair) {
+ return pair.second.annotation;
+ });
+ return [NSArray arrayWithObjects:&annotations[0] count:annotations.size()];
+}
+
+- (id <MGLAnnotation>)annotationWithID:(MGLAnnotationID)annotationID {
+ if (!_annotationContextsByAnnotationID.count(annotationID)) {
+ return nil;
+ }
+
+ MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationID[annotationID];
+ return annotationContext.annotation;
+}
+
+- (MGLAnnotationID)annotationIDForAnnotation:(id <MGLAnnotation>)annotation {
+ if (!annotation) {
+ return MGLAnnotationNotFound;
+ }
+
+ for (auto &pair : _annotationContextsByAnnotationID) {
+ if (pair.second.annotation == annotation) {
+ return pair.first;
+ }
+ }
+ return MGLAnnotationNotFound;
+}
+
+- (void)addAnnotation:(id <MGLAnnotation>)annotation {
+ if (annotation) {
+ [self addAnnotations:@[annotation]];
+ }
+}
+
+- (void)addAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)annotations {
+ if (!annotations) {
+ return;
+ }
+
+ BOOL delegateHasImagesForAnnotations = [self.delegate respondsToSelector:@selector(mapView:imageForAnnotation:)];
+
+ std::vector<mbgl::PointAnnotation> points;
+ std::vector<mbgl::ShapeAnnotation> shapes;
+
+ for (id <MGLAnnotation> annotation in annotations) {
+ NSAssert([annotation conformsToProtocol:@protocol(MGLAnnotation)], @"Annotation does not conform to MGLAnnotation");
+
+ if ([annotation isKindOfClass:[MGLMultiPoint class]]) {
+ [(MGLMultiPoint *)annotation addShapeAnnotationObjectToCollection:shapes withDelegate:self];
+ } else {
+ MGLAnnotationImage *annotationImage = nil;
+ if (delegateHasImagesForAnnotations) {
+ annotationImage = [self.delegate mapView:self imageForAnnotation:annotation];
+ }
+ if (!annotationImage) {
+ annotationImage = [self dequeueReusableAnnotationImageWithIdentifier:MGLDefaultStyleMarkerSymbolName];
+ }
+ if (!annotationImage) {
+ NSImage *image = MGLDefaultMarkerImage();
+ NSRect alignmentRect = image.alignmentRect;
+ alignmentRect.size.height /= 2;
+ image.alignmentRect = alignmentRect;
+ annotationImage = [MGLAnnotationImage annotationImageWithImage:image
+ reuseIdentifier:MGLDefaultStyleMarkerSymbolName];
+ }
+
+ if (!self.annotationImagesByIdentifier[annotationImage.reuseIdentifier]) {
+ self.annotationImagesByIdentifier[annotationImage.reuseIdentifier] = annotationImage;
+ [self installAnnotationImage:annotationImage];
+ }
+
+ NSString *symbolName = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier];
+ points.emplace_back(MGLLatLngFromLocationCoordinate2D(annotation.coordinate), symbolName ? [symbolName UTF8String] : "");
+ }
+ }
+
+ if (points.size()) {
+ std::vector<MGLAnnotationID> pointAnnotationIDs = _mbglMap->addPointAnnotations(points);
+
+ for (size_t i = 0; i < pointAnnotationIDs.size(); ++i) {
+ MGLAnnotationContext context;
+ context.annotation = annotations[i];
+ context.symbolIdentifier = @(points[i].icon.c_str());
+ _annotationContextsByAnnotationID[pointAnnotationIDs[i]] = context;
+ }
+ }
+
+ if (shapes.size()) {
+ std::vector<MGLAnnotationID> shapeAnnotationIDs = _mbglMap->addShapeAnnotations(shapes);
+
+ for (size_t i = 0; i < shapeAnnotationIDs.size(); ++i) {
+ MGLAnnotationContext context;
+ context.annotation = annotations[i];
+ _annotationContextsByAnnotationID[shapeAnnotationIDs[i]] = context;
+ }
+ }
+}
+
+- (void)installAnnotationImage:(MGLAnnotationImage *)annotationImage {
+ NSImage *image = annotationImage.image;
+ NSSize size = image.size;
+ if (size.width < 1 || size.height < 1 || !image.valid) {
+ return;
+ }
+
+ // http://www.cocoabuilder.com/archive/cocoa/82430-nsimage-getting-raw-bitmap-data.html#82431
+ [image lockFocus];
+ NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:{ NSZeroPoint, size }];
+ [image unlockFocus];
+
+ std::string pixelString((const char *)rep.bitmapData, rep.pixelsWide * rep.pixelsHigh * 4 /* RGBA */);
+ auto cSpriteImage = std::make_shared<mbgl::SpriteImage>((uint16_t)rep.size.width,
+ (uint16_t)rep.size.height,
+ (float)(rep.pixelsWide / size.width),
+ std::move(pixelString));
+ NSString *symbolName = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier];
+ _mbglMap->addAnnotationIcon(symbolName.UTF8String, cSpriteImage);
+
+ // Center the alignment rect around what will be the center of the
+ // annotation, then union it with all existing alignment rects.
+ NSRect alignmentRect = image.alignmentRect;
+ alignmentRect = NSOffsetRect(alignmentRect, -size.width / 2, -size.height / 2);
+ _unionedAnnotationImageAlignmentRect = NSUnionRect(_unionedAnnotationImageAlignmentRect,
+ alignmentRect);
+}
+
+- (void)removeAnnotation:(id <MGLAnnotation>)annotation {
+ if (annotation) {
+ [self removeAnnotations:@[annotation]];
+ }
+}
+
+- (void)removeAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)annotations {
+ if (!annotations) {
+ return;
+ }
+
+ std::vector<MGLAnnotationID> annotationIDsToRemove;
+ annotationIDsToRemove.reserve(annotations.count);
+
+ for (id <MGLAnnotation> annotation in annotations) {
+ NSAssert([annotation conformsToProtocol:@protocol(MGLAnnotation)], @"Annotation does not conform to MGLAnnotation");
+
+ MGLAnnotationID annotationID = [self annotationIDForAnnotation:annotation];
+ NSAssert(annotationID != MGLAnnotationNotFound, @"No ID for annotation %@", annotation);
+ annotationIDsToRemove.push_back(annotationID);
+
+ _annotationContextsByAnnotationID.erase(annotationID);
+
+ if (annotationID == _selectedAnnotationID) {
+ [self deselectAnnotation:annotation animated:NO];
+ }
+ }
+
+ _mbglMap->removeAnnotations(annotationIDsToRemove);
+}
+
+- (id <MGLAnnotation>)selectedAnnotation {
+ if (!_annotationContextsByAnnotationID.count(_selectedAnnotationID)) {
+ return nil;
+ }
+ MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationID.at(_selectedAnnotationID);
+ return annotationContext.annotation;
+}
+
+- (nullable MGLAnnotationImage *)dequeueReusableAnnotationImageWithIdentifier:(NSString *)identifier {
+ if ([identifier hasPrefix:MGLAnnotationSpritePrefix]) {
+ identifier = [identifier substringFromIndex:MGLAnnotationSpritePrefix.length];
+ }
+ return self.annotationImagesByIdentifier[identifier];
+}
+
+- (NS_ARRAY_OF(id <MGLAnnotation>) *)selectedAnnotations {
+ id <MGLAnnotation> selectedAnnotation = self.selectedAnnotation;
+ return selectedAnnotation ? @[selectedAnnotation] : @[];
+}
+
+- (void)setSelectedAnnotation:(id <MGLAnnotation>)selectedAnnotation {
+ _selectedAnnotationID = [self annotationIDForAnnotation:selectedAnnotation];
+}
+
+- (void)setSelectedAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)selectedAnnotations {
+ if (!selectedAnnotations.count) {
+ return;
+ }
+
+ id <MGLAnnotation> firstAnnotation = selectedAnnotations[0];
+ NSAssert([firstAnnotation conformsToProtocol:@protocol(MGLAnnotation)], @"Annotation does not conform to MGLAnnotation");
+ if ([firstAnnotation isKindOfClass:[MGLMultiPoint class]]) {
+ return;
+ }
+
+ if (MGLCoordinateInCoordinateBounds(firstAnnotation.coordinate, self.visibleCoordinateBounds)) {
+ [self selectAnnotation:firstAnnotation animated:NO];
+ }
+}
+
+- (void)selectAnnotation:(id <MGLAnnotation>)annotation animated:(BOOL)animated
+{
+ if (!annotation
+ || [annotation isKindOfClass:[MGLMultiPoint class]]
+ || !MGLCoordinateInCoordinateBounds(annotation.coordinate, self.visibleCoordinateBounds)) {
+ return;
+ }
+
+ id <MGLAnnotation> selectedAnnotation = self.selectedAnnotation;
+ if (annotation == selectedAnnotation) {
+ return;
+ }
+
+ [self deselectAnnotation:selectedAnnotation animated:NO];
+
+ MGLAnnotationID annotationID = [self annotationIDForAnnotation:annotation];
+ if (annotationID == MGLAnnotationNotFound) {
+ [self addAnnotation:annotation];
+ }
+ _selectedAnnotationID = annotationID;
+
+ if ([annotation respondsToSelector:@selector(title)]
+ && annotation.title
+ && !self.calloutForSelectedAnnotation.shown
+ && [self.delegate respondsToSelector:@selector(mapView:annotationCanShowCallout:)]
+ && [self.delegate mapView:self annotationCanShowCallout:annotation]) {
+ NSPopover *callout = [self calloutForAnnotation:annotation];
+ callout.animates = animated;
+
+ callout.delegate = self;
+ self.calloutForSelectedAnnotation = callout;
+ NSRect positioningRect = [self positioningRectForCalloutForAnnotationWithID:annotationID];
+ NSRectEdge edge = (self.userInterfaceLayoutDirection == NSUserInterfaceLayoutDirectionRightToLeft
+ ? NSMinXEdge
+ : NSMaxXEdge);
+ [callout showRelativeToRect:positioningRect ofView:self preferredEdge:edge];
+ }
+}
+
+- (NSPopover *)calloutForAnnotation:(id <MGLAnnotation>)annotation {
+ NSPopover *callout = [[NSPopover alloc] init];
+ callout.behavior = NSPopoverBehaviorTransient;
+
+ NSViewController *viewController;
+ if ([self.delegate respondsToSelector:@selector(mapView:calloutViewControllerForAnnotation:)]) {
+ viewController = [self.delegate mapView:self calloutViewControllerForAnnotation:annotation];
+ }
+ if (!viewController) {
+ viewController = [[NSViewController alloc] initWithNibName:@"MGLAnnotationCallout"
+ bundle:[NSBundle mgl_resourceBundle]];
+ }
+ NSAssert(viewController, @"Unable to load MGLAnnotationCallout view controller");
+ viewController.representedObject = annotation;
+ callout.contentViewController = viewController;
+
+ return callout;
+}
+
+- (NSRect)positioningRectForCalloutForAnnotationWithID:(MGLAnnotationID)annotationID {
+ id <MGLAnnotation> annotation = [self annotationWithID:annotationID];
+ if (!annotation) {
+ return NSZeroRect;
+ }
+
+ NSString *customSymbol = _annotationContextsByAnnotationID.at(annotationID).symbolIdentifier;
+ NSString *symbolName = customSymbol.length ? customSymbol : MGLDefaultStyleMarkerSymbolName;
+
+ NSPoint calloutAnchorPoint = [self convertCoordinate:annotation.coordinate toPointToView:self];
+
+ MGLAnnotationImage *annotationImage = [self dequeueReusableAnnotationImageWithIdentifier:symbolName];
+ NSSize annotationSize = annotationImage.image.size;
+ NSRect annotationRect = NSMakeRect(calloutAnchorPoint.x - annotationSize.width / 2, calloutAnchorPoint.y,
+ annotationSize.width, annotationSize.height / 2);
+ annotationRect = NSOffsetRect(annotationImage.image.alignmentRect,
+ annotationRect.origin.x, annotationRect.origin.y);
+ return NSInsetRect(annotationRect, -MGLAnnotationImagePaddingForCallout, 0);
+}
+
+- (void)deselectAnnotation:(id <MGLAnnotation>)annotation animated:(BOOL)animated {
+ if (!annotation || self.selectedAnnotation != annotation) {
+ return;
+ }
+
+ NSPopover *callout = self.calloutForSelectedAnnotation;
+ callout.animates = animated;
+ [callout performClose:self];
+
+ self.selectedAnnotation = nil;
+}
+
+- (void)updateAnnotationCallouts {
+ NSPopover *callout = self.calloutForSelectedAnnotation;
+ if (callout) {
+ callout.positioningRect = [self positioningRectForCalloutForAnnotationWithID:_selectedAnnotationID];
+ }
+}
+
+#pragma mark MGLMultiPointDelegate methods
+
+- (double)alphaForShapeAnnotation:(MGLShape *)annotation {
+ if (_delegateHasAlphasForShapeAnnotations) {
+ return [self.delegate mapView:self alphaForShapeAnnotation:annotation];
+ }
+ return 1.0;
+}
+
+- (mbgl::Color)strokeColorForShapeAnnotation:(MGLShape *)annotation {
+ NSColor *color = (_delegateHasStrokeColorsForShapeAnnotations
+ ? [self.delegate mapView:self strokeColorForShapeAnnotation:annotation]
+ : [NSColor blackColor]);
+ return MGLColorObjectFromNSColor(color);
+}
+
+- (mbgl::Color)fillColorForPolygonAnnotation:(MGLPolygon *)annotation {
+ NSColor *color = (_delegateHasFillColorsForShapeAnnotations
+ ? [self.delegate mapView:self fillColorForPolygonAnnotation:annotation]
+ : [NSColor blueColor]);
+ return MGLColorObjectFromNSColor(color);
+}
+
+- (CGFloat)lineWidthForPolylineAnnotation:(MGLPolyline *)annotation {
+ if (_delegateHasLineWidthsForShapeAnnotations) {
+ return [self.delegate mapView:self lineWidthForPolylineAnnotation:(MGLPolyline *)annotation];
+ }
+ return 3.0;
+}
+
+#pragma mark MGLPopoverDelegate methods
+
+- (void)popoverDidShow:(__unused NSNotification *)notification {
+ id <MGLAnnotation> annotation = self.selectedAnnotation;
+ if ([self.delegate respondsToSelector:@selector(mapView:didSelectAnnotation:)]) {
+ [self.delegate mapView:self didSelectAnnotation:annotation];
+ }
+}
+
+- (void)popoverDidClose:(__unused NSNotification *)notification {
+ id <MGLAnnotation> annotation = self.calloutForSelectedAnnotation.contentViewController.representedObject;
+ self.calloutForSelectedAnnotation = nil;
+ self.selectedAnnotation = nil;
+
+ if ([self.delegate respondsToSelector:@selector(mapView:didDeselectAnnotation:)]) {
+ [self.delegate mapView:self didDeselectAnnotation:annotation];
+ }
+}
+
+#pragma mark Overlays
+
+- (void)addOverlay:(id <MGLOverlay>)overlay {
+ [self addOverlays:@[overlay]];
+}
+
+- (void)addOverlays:(NS_ARRAY_OF(id <MGLOverlay>) *)overlays
+{
+ for (id <MGLOverlay> overlay in overlays) {
+ NSAssert([overlay conformsToProtocol:@protocol(MGLOverlay)], @"Overlay does not conform to MGLOverlay");
+ }
+ [self addAnnotations:overlays];
+}
+
+- (void)removeOverlay:(id <MGLOverlay>)overlay {
+ [self removeOverlays:@[overlay]];
+}
+
+- (void)removeOverlays:(NS_ARRAY_OF(id <MGLOverlay>) *)overlays {
+ for (id <MGLOverlay> overlay in overlays) {
+ NSAssert([overlay conformsToProtocol:@protocol(MGLOverlay)], @"Overlay does not conform to MGLOverlay");
+ }
+ [self removeAnnotations:overlays];
+}
+
+#pragma mark Interface Builder methods
+
+- (void)prepareForInterfaceBuilder {
+ [super prepareForInterfaceBuilder];
+
+ self.layer.borderColor = [NSColor colorWithRed:59/255.
+ green:178/255.
+ blue:208/255.
+ alpha:0.8].CGColor;
+ self.layer.borderWidth = 2;
+ self.layer.backgroundColor = [NSColor colorWithRed:59/255.
+ green:178/255.
+ blue:208/255.
+ alpha:0.6].CGColor;
+
+ self.layer.contents = MGLDefaultMarkerImage();
+ self.layer.contentsGravity = kCAGravityCenter;
+ self.layer.contentsScale = [NSScreen mainScreen].backingScaleFactor;
+}
+
+#pragma mark Geometric methods
+
+- (CLLocationCoordinate2D)convertPoint:(NSPoint)point toCoordinateFromView:(nullable NSView *)view {
+ return MGLLocationCoordinate2DFromLatLng([self convertPoint:point toLatLngFromView:view]);
+}
+
+- (mbgl::LatLng)convertPoint:(NSPoint)point toLatLngFromView:(nullable NSView *)view {
+ NSPoint convertedPoint = [self convertPoint:point fromView:view];
+ return _mbglMap->latLngForPixel(mbgl::PrecisionPoint(convertedPoint.x, convertedPoint.y));
+}
+
+- (NSPoint)convertCoordinate:(CLLocationCoordinate2D)coordinate toPointToView:(nullable NSView *)view {
+ return [self convertLatLng:MGLLatLngFromLocationCoordinate2D(coordinate) toPointToView:view];
+}
+
+- (NSPoint)convertLatLng:(mbgl::LatLng)latLng toPointToView:(nullable NSView *)view {
+ mbgl::vec2<double> pixel = _mbglMap->pixelForLatLng(latLng);
+ return [self convertPoint:NSMakePoint(pixel.x, pixel.y) toView:view];
+}
+
+- (MGLCoordinateBounds)convertRectToCoordinateBounds:(NSRect)rect {
+ return MGLCoordinateBoundsFromLatLngBounds([self convertRectToLatLngBounds:rect]);
+}
+
+- (mbgl::LatLngBounds)convertRectToLatLngBounds:(NSRect)rect {
+ mbgl::LatLngBounds bounds = mbgl::LatLngBounds::getExtendable();
+ bounds.extend([self convertPoint:{ NSMinX(rect), NSMinY(rect) } toLatLngFromView:self]);
+ bounds.extend([self convertPoint:{ NSMaxX(rect), NSMinY(rect) } toLatLngFromView:self]);
+ bounds.extend([self convertPoint:{ NSMaxX(rect), NSMaxY(rect) } toLatLngFromView:self]);
+ bounds.extend([self convertPoint:{ NSMinX(rect), NSMaxY(rect) } toLatLngFromView:self]);
+
+ // If the world is wrapping, extend the bounds to cover all longitudes.
+ mbgl::LatLng outsideLatLng;
+ if (bounds.sw.longitude > -180) {
+ outsideLatLng = {
+ (bounds.sw.latitude + bounds.ne.latitude) / 2,
+ bounds.sw.longitude - 1,
+ };
+ } else if (bounds.ne.longitude < 180) {
+ outsideLatLng = {
+ (bounds.sw.latitude + bounds.ne.latitude) / 2,
+ bounds.ne.longitude + 1,
+ };
+ }
+ if (NSPointInRect([self convertLatLng:outsideLatLng toPointToView:self], rect)) {
+ bounds.sw.longitude = -180;
+ bounds.ne.longitude = 180;
+ }
+
+ return bounds;
+}
+
+- (CLLocationDistance)metersPerPixelAtLatitude:(CLLocationDegrees)latitude {
+ return _mbglMap->getMetersPerPixelAtLatitude(latitude, self.zoomLevel);
+}
+
+#pragma mark Debugging
+
+- (MGLMapDebugMaskOptions)debugMask {
+ mbgl::MapDebugOptions options = _mbglMap->getDebug();
+ MGLMapDebugMaskOptions mask = 0;
+ if (options & mbgl::MapDebugOptions::TileBorders) {
+ mask |= MGLMapDebugTileBoundariesMask;
+ }
+ if (options & mbgl::MapDebugOptions::ParseStatus) {
+ mask |= MGLMapDebugTileInfoMask;
+ }
+ if (options & mbgl::MapDebugOptions::Timestamps) {
+ mask |= MGLMapDebugTimestampsMask;
+ }
+ if (options & mbgl::MapDebugOptions::Collision) {
+ mask |= MGLMapDebugCollisionBoxesMask;
+ }
+ return mask;
+}
+
+- (void)setDebugMask:(MGLMapDebugMaskOptions)debugMask {
+ mbgl::MapDebugOptions options = mbgl::MapDebugOptions::NoDebug;
+ if (debugMask & MGLMapDebugTileBoundariesMask) {
+ options |= mbgl::MapDebugOptions::TileBorders;
+ }
+ if (debugMask & MGLMapDebugTileInfoMask) {
+ options |= mbgl::MapDebugOptions::ParseStatus;
+ }
+ if (debugMask & MGLMapDebugTimestampsMask) {
+ options |= mbgl::MapDebugOptions::Timestamps;
+ }
+ if (debugMask & MGLMapDebugCollisionBoxesMask) {
+ options |= mbgl::MapDebugOptions::Collision;
+ }
+ _mbglMap->setDebug(options);
+}
+
+class MGLMapViewImpl : public mbgl::View {
+public:
+ MGLMapViewImpl(MGLMapView *nativeView_, const float scaleFactor_)
+ : nativeView(nativeView_), scaleFactor(scaleFactor_) {}
+ virtual ~MGLMapViewImpl() {}
+
+
+ float getPixelRatio() const override {
+ return scaleFactor;
+ }
+
+ std::array<uint16_t, 2> getSize() const override {
+ return {{ static_cast<uint16_t>(nativeView.bounds.size.width),
+ static_cast<uint16_t>(nativeView.bounds.size.height) }};
+ }
+
+ std::array<uint16_t, 2> getFramebufferSize() const override {
+ NSRect bounds = [nativeView convertRectToBacking:nativeView.bounds];
+ return {{ static_cast<uint16_t>(bounds.size.width),
+ static_cast<uint16_t>(bounds.size.height) }};
+ }
+
+ void notify() override {}
+
+ void notifyMapChange(mbgl::MapChange change) override {
+ assert([[NSThread currentThread] isMainThread]);
+ [nativeView notifyMapChange:change];
+ }
+
+ void activate() override {
+ MGLOpenGLLayer *layer = (MGLOpenGLLayer *)nativeView.layer;
+ if ([NSOpenGLContext currentContext] != layer.openGLContext) {
+ [layer.openGLContext makeCurrentContext];
+
+ mbgl::gl::InitializeExtensions([](const char *name) {
+ static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl"));
+ if (!framework) {
+ throw std::runtime_error("Failed to load OpenGL framework.");
+ }
+
+ CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault, name, kCFStringEncodingASCII);
+ void *symbol = CFBundleGetFunctionPointerForName(framework, str);
+ CFRelease(str);
+
+ return reinterpret_cast<mbgl::gl::glProc>(symbol);
+ });
+ }
+ }
+
+ void deactivate() override {
+ [NSOpenGLContext clearCurrentContext];
+ }
+
+ void invalidate() override {
+ [nativeView performSelectorOnMainThread:@selector(invalidate)
+ withObject:nil
+ waitUntilDone:NO];
+ }
+
+ void beforeRender() override {
+ activate();
+ }
+
+ void afterRender() override {}
+
+private:
+ __weak MGLMapView *nativeView = nullptr;
+ const float scaleFactor;
+};
+
+@end
diff --git a/platform/osx/sdk/MGLMapView_Private.h b/platform/osx/sdk/MGLMapView_Private.h
new file mode 100644
index 0000000000..3b37ce24b4
--- /dev/null
+++ b/platform/osx/sdk/MGLMapView_Private.h
@@ -0,0 +1,12 @@
+#import <mbgl/osx/MGLMapView.h>
+
+@interface MGLMapView (Private)
+
+@property (nonatomic, readonly, getter=isDormant) BOOL dormant;
+
+@property (nonatomic) CLLocationDegrees pendingLatitude;
+@property (nonatomic) CLLocationDegrees pendingLongitude;
+
+- (void)renderSync;
+
+@end
diff --git a/platform/osx/sdk/MGLOpenGLLayer.h b/platform/osx/sdk/MGLOpenGLLayer.h
new file mode 100644
index 0000000000..9429b01bb5
--- /dev/null
+++ b/platform/osx/sdk/MGLOpenGLLayer.h
@@ -0,0 +1,10 @@
+#import <Cocoa/Cocoa.h>
+
+#import "MGLTypes.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface MGLOpenGLLayer : NSOpenGLLayer
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/platform/osx/sdk/MGLOpenGLLayer.mm b/platform/osx/sdk/MGLOpenGLLayer.mm
new file mode 100644
index 0000000000..06a7be961c
--- /dev/null
+++ b/platform/osx/sdk/MGLOpenGLLayer.mm
@@ -0,0 +1,49 @@
+#import "MGLOpenGLLayer.h"
+
+#import "MGLMapView_Private.h"
+
+#import <mbgl/platform/gl.hpp>
+
+@implementation MGLOpenGLLayer
+
+- (MGLMapView *)mapView {
+ return (MGLMapView *)super.view;
+}
+
+//- (BOOL)isAsynchronous {
+// return YES;
+//}
+
+- (BOOL)needsDisplayOnBoundsChange {
+ return YES;
+}
+
+- (CGRect)frame {
+ return self.view.bounds;
+}
+
+- (NSOpenGLPixelFormat *)openGLPixelFormatForDisplayMask:(uint32_t)mask {
+ NSOpenGLPixelFormatAttribute pfas[] = {
+ NSOpenGLPFAAccelerated,
+ NSOpenGLPFAClosestPolicy,
+ NSOpenGLPFAAccumSize, 32,
+ NSOpenGLPFAColorSize, 24,
+ NSOpenGLPFAAlphaSize, 8,
+ NSOpenGLPFADepthSize, 16,
+ NSOpenGLPFAStencilSize, 8,
+ NSOpenGLPFAScreenMask, mask,
+ 0
+ };
+ return [[NSOpenGLPixelFormat alloc] initWithAttributes:pfas];
+}
+
+- (BOOL)canDrawInOpenGLContext:(__unused NSOpenGLContext *)context pixelFormat:(__unused NSOpenGLPixelFormat *)pixelFormat forLayerTime:(__unused CFTimeInterval)t displayTime:(__unused const CVTimeStamp *)ts {
+ return !self.mapView.dormant;
+}
+
+- (void)drawInOpenGLContext:(NSOpenGLContext *)context pixelFormat:(NSOpenGLPixelFormat *)pixelFormat forLayerTime:(CFTimeInterval)t displayTime:(const CVTimeStamp *)ts {
+ [self.mapView renderSync];
+ [super drawInOpenGLContext:context pixelFormat:pixelFormat forLayerTime:t displayTime:ts];
+}
+
+@end
diff --git a/platform/osx/sdk/NSBundle+MGLAdditions.h b/platform/osx/sdk/NSBundle+MGLAdditions.h
new file mode 100644
index 0000000000..c09ba38a5d
--- /dev/null
+++ b/platform/osx/sdk/NSBundle+MGLAdditions.h
@@ -0,0 +1,16 @@
+#import <Foundation/Foundation.h>
+
+#import "MGLTypes.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+void mgl_linkBundleCategory();
+
+@interface NSBundle (MGLAdditions)
+
++ (instancetype)mgl_resourceBundle;
++ (NSString *)mgl_resourceBundlePath;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/platform/osx/sdk/NSBundle+MGLAdditions.m b/platform/osx/sdk/NSBundle+MGLAdditions.m
new file mode 100644
index 0000000000..a53802c343
--- /dev/null
+++ b/platform/osx/sdk/NSBundle+MGLAdditions.m
@@ -0,0 +1,22 @@
+#import "NSBundle+MGLAdditions.h"
+
+#import "MGLAccountManager.h"
+
+void mgl_linkBundleCategory() {}
+
+@implementation NSBundle (MGLAdditions)
+
++ (instancetype)mgl_resourceBundle {
+ return [self bundleWithPath:[self mgl_resourceBundlePath]];
+}
+
++ (NSString *)mgl_resourceBundlePath {
+ NSString *resourceBundlePath = [[self bundleForClass:[MGLAccountManager class]]
+ pathForResource:@"Mapbox" ofType:@"bundle"];
+ if (!resourceBundlePath) {
+ resourceBundlePath = [[self mainBundle] bundlePath];
+ }
+ return resourceBundlePath;
+}
+
+@end
diff --git a/platform/osx/sdk/NSProcessInfo+MGLAdditions.h b/platform/osx/sdk/NSProcessInfo+MGLAdditions.h
new file mode 100644
index 0000000000..6b34f54756
--- /dev/null
+++ b/platform/osx/sdk/NSProcessInfo+MGLAdditions.h
@@ -0,0 +1,9 @@
+#import <Foundation/Foundation.h>
+
+void mgl_linkProcessInfoCategory();
+
+@interface NSProcessInfo (MGLAdditions)
+
+- (BOOL)mgl_isInterfaceBuilderDesignablesAgent;
+
+@end
diff --git a/platform/osx/sdk/NSProcessInfo+MGLAdditions.m b/platform/osx/sdk/NSProcessInfo+MGLAdditions.m
new file mode 100644
index 0000000000..16f869703c
--- /dev/null
+++ b/platform/osx/sdk/NSProcessInfo+MGLAdditions.m
@@ -0,0 +1,11 @@
+#import "NSProcessInfo+MGLAdditions.h"
+
+void mgl_linkProcessInfoCategory() {}
+
+@implementation NSProcessInfo (MGLAdditions)
+
+- (BOOL)mgl_isInterfaceBuilderDesignablesAgent {
+ return [self.processName isEqualToString:@"IBDesignablesAgent"];
+}
+
+@end
diff --git a/platform/osx/sdk/resources/MGLAnnotationCallout.xib b/platform/osx/sdk/resources/MGLAnnotationCallout.xib
new file mode 100644
index 0000000000..edf84a26a7
--- /dev/null
+++ b/platform/osx/sdk/resources/MGLAnnotationCallout.xib
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9531" systemVersion="15B42" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+ <dependencies>
+ <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9531"/>
+ </dependencies>
+ <objects>
+ <customObject id="-2" userLabel="File's Owner" customClass="NSViewController">
+ <connections>
+ <outlet property="view" destination="c22-O7-iKe" id="QAM-0O-WIj"/>
+ </connections>
+ </customObject>
+ <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
+ <customObject id="-3" userLabel="Application" customClass="NSObject"/>
+ <customView id="c22-O7-iKe">
+ <rect key="frame" x="0.0" y="0.0" width="270" height="50"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+ <subviews>
+ <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="k5x-ao-Pz3">
+ <rect key="frame" x="18" y="25" width="234" height="17"/>
+ <textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" title="Title" id="nVE-Zi-KcG">
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ <connections>
+ <binding destination="-2" name="value" keyPath="representedObject.title" id="3nD-YS-gzq"/>
+ </connections>
+ </textField>
+ <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="e9C-Ve-ccw">
+ <rect key="frame" x="18" y="8" width="234" height="14"/>
+ <textFieldCell key="cell" controlSize="small" selectable="YES" sendsActionOnEndEditing="YES" title="Subtitle" id="eKw-tQ-dw8">
+ <font key="font" metaFont="smallSystem"/>
+ <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ <connections>
+ <binding destination="-2" name="value" keyPath="representedObject.subtitle" id="RQf-48-DyH"/>
+ </connections>
+ </textField>
+ </subviews>
+ <constraints>
+ <constraint firstItem="e9C-Ve-ccw" firstAttribute="leading" secondItem="k5x-ao-Pz3" secondAttribute="leading" id="ApT-ew-CYb"/>
+ <constraint firstAttribute="bottom" secondItem="e9C-Ve-ccw" secondAttribute="bottom" constant="8" id="CWV-Dd-8oi"/>
+ <constraint firstItem="k5x-ao-Pz3" firstAttribute="leading" secondItem="c22-O7-iKe" secondAttribute="leading" constant="20" id="UUL-GB-Jtv"/>
+ <constraint firstItem="e9C-Ve-ccw" firstAttribute="top" secondItem="k5x-ao-Pz3" secondAttribute="bottom" constant="3" id="Urc-wn-m8X"/>
+ <constraint firstItem="e9C-Ve-ccw" firstAttribute="trailing" secondItem="k5x-ao-Pz3" secondAttribute="trailing" id="gss-6G-9GF"/>
+ <constraint firstAttribute="trailing" secondItem="k5x-ao-Pz3" secondAttribute="trailing" constant="20" id="xCZ-s9-HaP"/>
+ <constraint firstItem="k5x-ao-Pz3" firstAttribute="top" secondItem="c22-O7-iKe" secondAttribute="top" constant="8" id="xcm-oY-jjy"/>
+ </constraints>
+ <point key="canvasLocation" x="257" y="355"/>
+ </customView>
+ </objects>
+</document>
diff --git a/platform/osx/sdk/resources/default_marker.pdf b/platform/osx/sdk/resources/default_marker.pdf
new file mode 100644
index 0000000000..4e2e332301
--- /dev/null
+++ b/platform/osx/sdk/resources/default_marker.pdf
Binary files differ
diff --git a/platform/osx/sdk/resources/mapbox.pdf b/platform/osx/sdk/resources/mapbox.pdf
new file mode 100644
index 0000000000..c08a0e3135
--- /dev/null
+++ b/platform/osx/sdk/resources/mapbox.pdf
Binary files differ
diff --git a/scripts/ios/package.sh b/scripts/ios/package.sh
index 7f0fe447d1..049aa7434d 100755
--- a/scripts/ios/package.sh
+++ b/scripts/ios/package.sh
@@ -99,6 +99,9 @@ echo "Created ${OUTPUT}/static/lib${NAME}.a"
step "Copying Headers..."
mkdir -p "${OUTPUT}/static/Headers"
+for i in `ls -R include/mbgl/darwin | grep -vi private`; do
+ cp -pv include/mbgl/darwin/$i "${OUTPUT}/static/Headers"
+done
for i in `ls -R include/mbgl/ios | grep -vi private`; do
cp -pv include/mbgl/ios/$i "${OUTPUT}/static/Headers"
done
diff --git a/scripts/osx/package.sh b/scripts/osx/package.sh
new file mode 100755
index 0000000000..eef9fcd919
--- /dev/null
+++ b/scripts/osx/package.sh
@@ -0,0 +1,78 @@
+#!/usr/bin/env bash
+
+set -e
+set -o pipefail
+set -u
+
+NAME=Mapbox
+OUTPUT=build/osx/pkg
+OSX_SDK_VERSION=`xcrun --sdk macosx --show-sdk-version`
+LIBUV_VERSION=1.7.5
+
+if [[ ${#} -eq 0 ]]; then # e.g. "make ipackage"
+ BUILDTYPE="Release"
+ GCC_GENERATE_DEBUGGING_SYMBOLS="YES"
+else # e.g. "make ipackage-strip"
+ BUILDTYPE="Release"
+ GCC_GENERATE_DEBUGGING_SYMBOLS="NO"
+fi
+
+function step { >&2 echo -e "\033[1m\033[36m* $@\033[0m"; }
+function finish { >&2 echo -en "\033[0m"; }
+trap finish EXIT
+
+
+rm -rf ${OUTPUT}
+mkdir -p "${OUTPUT}"/static
+
+
+step "Recording library version..."
+VERSION="${OUTPUT}"/static/version.txt
+echo -n "https://github.com/mapbox/mapbox-gl-native/commit/" > ${VERSION}
+HASH=`git log | head -1 | awk '{ print $2 }' | cut -c 1-10` && true
+echo -n "mapbox-gl-native "
+echo ${HASH}
+echo ${HASH} >> ${VERSION}
+
+
+step "Creating build files..."
+export MASON_PLATFORM=osx
+export BUILDTYPE=${BUILDTYPE:-Release}
+export HOST=osx
+make Xcode/osx
+
+step "Building OS X targets..."
+xcodebuild -sdk macosx${OSX_SDK_VERSION} \
+ ARCHS="x86_64" \
+ ONLY_ACTIVE_ARCH=NO \
+ GCC_GENERATE_DEBUGGING_SYMBOLS=${GCC_GENERATE_DEBUGGING_SYMBOLS} \
+ -project ./build/osx-x86_64/gyp/mbgl.xcodeproj \
+ -configuration ${BUILDTYPE} \
+ -target everything \
+ -jobs ${JOBS}
+
+
+step "Building static library..."
+LIBS=(core.a platform-osx.a asset-fs.a cache-sqlite.a http-nsurl.a)
+libtool -static -no_warning_for_no_symbols \
+ `find mason_packages/osx-${OSX_SDK_VERSION} -type f -name libuv.a` \
+ `find mason_packages/osx-${OSX_SDK_VERSION} -type f -name libgeojsonvt.a` \
+ -o ${OUTPUT}/static/lib${NAME}.a \
+ ${LIBS[@]/#/gyp/build/${BUILDTYPE}/libmbgl-}
+echo "Created ${OUTPUT}/static/lib${NAME}.a"
+
+
+step "Copying Headers..."
+mkdir -p "${OUTPUT}/static/Headers"
+for i in `ls -R include/mbgl/darwin | grep -vi private`; do
+ cp -pv include/mbgl/darwin/$i "${OUTPUT}/static/Headers"
+done
+for i in `ls -R include/mbgl/osx | grep -vi private`; do
+ cp -pv include/mbgl/osx/$i "${OUTPUT}/static/Headers"
+done
+
+step "Copying Resources..."
+cp -pv LICENSE.md "${OUTPUT}/static"
+mkdir -p "${OUTPUT}/static/${NAME}.bundle"
+cp -pv platform/osx/resources/* "${OUTPUT}/static/${NAME}.bundle"
+
diff --git a/scripts/osx/run.sh b/scripts/osx/run.sh
index 28c1adba1b..0f7841b4a3 100755
--- a/scripts/osx/run.sh
+++ b/scripts/osx/run.sh
@@ -11,6 +11,12 @@ BUILDTYPE=${BUILDTYPE:-Release}
# Build
################################################################################
+mapbox_time "package_osx_symbols" \
+make xpackage
+
+mapbox_time "package_osx_stripped" \
+make xpackage-strip
+
mapbox_time "compile_program" \
make xosx -j${JOBS} BUILDTYPE=${BUILDTYPE}