diff --git a/.gitignore b/.gitignore index b253752e5..123bf8cba 100644 --- a/.gitignore +++ b/.gitignore @@ -12,11 +12,14 @@ /mason_packages /config/*.gypi /build +/lib /macosx/build /linux/build /ios/build /ios/app/build /test/build +/gyp/build +/gyp/java /test/node_modules /test/fixtures/*/*_actual.* /test/fixtures/api/1.png @@ -31,3 +34,6 @@ /ios/benchmark/assets/tiles/mapbox.mapbox-streets-v6/ /ios/benchmark/assets/tiles/mapbox.mapbox-terrain-v2/ /ios/benchmark/assets/glyphs/DIN Offc Pro*/ + +/platform/node/test/actual +/platform/node/test/diff diff --git a/.gitmodules b/.gitmodules index d84cfecbc..2aa6d52be 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,7 +1,3 @@ -[submodule "test/suite"] - path = test/suite - url = https://github.com/mapbox/mapbox-gl-test-suite.git - [submodule ".mason"] path = .mason url = https://github.com/mapbox/mason.git diff --git a/.travis.yml b/.travis.yml index e5b94a813..f3a830849 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,46 +25,69 @@ matrix: sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.5' ] packages: [ 'gdb', 'clang-3.5', 'libstdc++-4.9-dev', 'libstdc++6', 'libllvm3.4', 'xutils-dev', 'libxxf86vm-dev', 'x11proto-xf86vidmode-dev', 'mesa-utils' ] - os: osx - osx_image: beta-xcode6.3 + osx_image: xcode6.4 env: FLAVOR=osx BUILDTYPE=Debug - os: osx - osx_image: beta-xcode6.3 + osx_image: xcode6.4 env: FLAVOR=ios BUILDTYPE=Release - os: linux env: FLAVOR=android ANDROID_ABI=arm-v7 BUILDTYPE=Release android: - components: [ 'build-tools-22.0.1', 'android-22', 'extra' ] + components: [ 'build-tools-23.0.1', 'android-23', 'extra-android-m2repository', 'extra-android-support', 'extra-google-m2repository' ] addons: apt: packages: [ 'lib32stdc++6' ] - os: linux env: FLAVOR=android ANDROID_ABI=arm-v7 BUILDTYPE=Debug android: - components: [ 'build-tools-22.0.1', 'android-22', 'extra' ] + components: [ 'build-tools-23.0.1', 'android-23', 'extra-android-m2repository', 'extra-android-support', 'extra-google-m2repository' ] addons: apt: packages: [ 'lib32stdc++6' ] - os: linux env: FLAVOR=android ANDROID_ABI=arm-v8 BUILDTYPE=Release android: - components: [ 'build-tools-22.0.1', 'android-22', 'extra' ] + components: [ 'build-tools-23.0.1', 'android-23', 'extra-android-m2repository', 'extra-android-support', 'extra-google-m2repository' ] addons: apt: packages: [ 'lib32stdc++6' ] - os: linux env: FLAVOR=android ANDROID_ABI=x86 BUILDTYPE=Release android: - components: [ 'build-tools-22.0.1', 'android-22', 'extra' ] + components: [ 'build-tools-23.0.1', 'android-23', 'extra-android-m2repository', 'extra-android-support', 'extra-google-m2repository' ] addons: apt: packages: [ 'lib32stdc++6' ] - os: linux - env: FLAVOR=android ANDROID_ABI=mips BUILDTYPE=Release - android: - components: [ 'build-tools-22.0.1', 'android-22', 'extra' ] + env: FLAVOR=node CXX=clang++-3.5 BUILDTYPE=Release NODE_VERSION=iojs-v3 addons: apt: - packages: [ 'lib32stdc++6' ] + sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.5' ] + packages: [ 'gdb', 'clang-3.5', 'libstdc++-4.9-dev', 'libstdc++6', 'libllvm3.4', 'xutils-dev', 'libxxf86vm-dev', 'x11proto-xf86vidmode-dev', 'mesa-utils' ] + - os: linux + env: FLAVOR=node CXX=clang++-3.5 BUILDTYPE=Release NODE_VERSION=0.12 + addons: + apt: + sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.5' ] + packages: [ 'gdb', 'clang-3.5', 'libstdc++-4.9-dev', 'libstdc++6', 'libllvm3.4', 'xutils-dev', 'libxxf86vm-dev', 'x11proto-xf86vidmode-dev', 'mesa-utils' ] + - os: linux + env: FLAVOR=node CXX=clang++-3.5 BUILDTYPE=Release NODE_VERSION=0.10 + addons: + apt: + sources: [ 'ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.5' ] + packages: [ 'gdb', 'clang-3.5', 'libstdc++-4.9-dev', 'libstdc++6', 'libllvm3.4', 'xutils-dev', 'libxxf86vm-dev', 'x11proto-xf86vidmode-dev', 'mesa-utils' ] + - os: osx + osx_image: xcode6.4 + compiler: clang + env: FLAVOR=node NODE_VERSION=iojs-v3 + - os: osx + osx_image: xcode6.4 + compiler: clang + env: FLAVOR=node NODE_VERSION=0.12 + - os: osx + osx_image: xcode6.4 + compiler: clang + env: FLAVOR=node NODE_VERSION=0.10 env: global: diff --git a/.ycm_extra_conf.py b/.ycm_extra_conf.py index 2f6dc2459..be5e3a5c1 100644 --- a/.ycm_extra_conf.py +++ b/.ycm_extra_conf.py @@ -80,14 +80,18 @@ def MakeRelativePathsInFlagsAbsolute(flags, working_directory): return new_flags +def FlagsForFileDefault(): + return { + 'flags': default_flags, + 'do_cache': False, + } + + def FlagsForFileLinux(filename): working_directory = os.path.join('build', 'linux') if not os.path.exists(os.path.join(DirectoryOfThisScript(), working_directory)): - return { - 'flags': default_flags, - 'do_cache': False, - } + return FlagsForFileDefault() relative_source_path = filename.replace(DirectoryOfThisScript() + '/', '') relative_obj_path = os.path.splitext(relative_source_path)[0] + '.o' @@ -98,10 +102,7 @@ def FlagsForFileLinux(filename): stdout, stderr = p.communicate() if p.returncode: - return { - 'flags': default_flags, - 'do_cache': False, - } + return FlagsForFileDefault() flags = stdout.split(' ') flags_relative = MakeRelativePathsInFlagsAbsolute(flags, os.path.join(DirectoryOfThisScript(), working_directory)) @@ -113,11 +114,49 @@ def FlagsForFileLinux(filename): } +def FlagsForFileDarwin(filename): + working_directory = os.path.join('build', 'osx') + + if not os.path.exists(os.path.join(DirectoryOfThisScript(), working_directory)): + return FlagsForFileDefault() + + relative_source_path = filename.replace(DirectoryOfThisScript() + '/', '') + relative_obj_path = os.path.splitext(relative_source_path)[0] + '.o' + + make_cmd = 'make -pn |grep "%s :=" |grep "NDEBUG" |tail -n 1 | cut -d\' \' -f4-' % relative_obj_path + + p = subprocess.Popen([make_cmd], shell=True, stdout=subprocess.PIPE) + stdout, stderr = p.communicate() + + if p.returncode: + return FlagsForFileDefault() + + flags = stdout.split(' ') + flags_relative = MakeRelativePathsInFlagsAbsolute(flags, os.path.join(DirectoryOfThisScript(), working_directory)) + flags_final = [flag for flag in flags_relative if flag.startswith("-")] + + # Append OSX sysroot SDK path + xcrun_cmd = 'xcrun --show-sdk-path' + + p = subprocess.Popen([xcrun_cmd], shell=True, stdout=subprocess.PIPE) + stdout, stderr = p.communicate() + + if p.returncode: + return FlagsForFileDefault() + + flags_final.append('-isysroot ' + stdout) + + return { + 'flags': flags_final, + 'do_cache': True, + } + + def FlagsForFile(filename, **kwargs): if sys.platform.startswith('linux'): return FlagsForFileLinux(filename) - return { - 'flags': default_flags, - 'do_cache': False, - } + if sys.platform.startswith('darwin'): + return FlagsForFileDarwin(filename) + + return FlagsForFileDefault() diff --git a/CHANGELOG.md b/CHANGELOG.md index 07f6672aa..c1b3e3003 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,48 @@ # Changelog +## iOS 2.0.0 + +Repackaging 2.0.0-pre.1 as it contained no issues. + +## iOS 2.0.0-pre.1 + +Repackaging 0.5.1 as the Mapbox iOS SDK 2.0.0 series. + +## 0.5.1 + +### iOS + +- Added support for CocoaPods 0.38.0. ([#1876](https://github.com/mapbox/mapbox-gl-native/pull/1876)) + +## 0.5.0 + +### Core + +- Support for runtime marker imagery. ([#941](https://github.com/mapbox/mapbox-gl-native/pull/941)) +- Added `Map::fitBounds()` for region-based viewport setting. ([#1092](https://github.com/mapbox/mapbox-gl-native/issues/1092)) +- Added a raster satellite bundled style and improved raster rendering. ([#963](https://github.com/mapbox/mapbox-gl-native/issues/963)) +- Improved round line joins for semi-transparent lines. ([#1839](https://github.com/mapbox/mapbox-gl-native/pull/1839)) +- Improved map render lifecycle notifications. ([#1026](https://github.com/mapbox/mapbox-gl-native/issues/1026)) +- Fixed a bug that caused annotations not to show at zoom level zero. ([#1279](https://github.com/mapbox/mapbox-gl-native/issues/1279)) +- Fixed a bug with the ordering of shape layers. ([#1866](https://github.com/mapbox/mapbox-gl-native/pull/1866)) +- Other bug fixes and performance improvements. + +### iOS + +- **Breaking:** Headers now make use of lightweight generics, eliminating many unnecessary casts when working with annotations in Swift 2.0 in Xcode 7. ([#1711](https://github.com/mapbox/mapbox-gl-native/pull/1711)) +- **Breaking:** `-mapView:symbolNameForAnnotation:` has been removed from the `MGLMapViewDelegate` protocol. Implement `-mapView:imageForAnnotation:` instead, which accepts images at runtime. ([#941](https://github.com/mapbox/mapbox-gl-native/pull/941)) +- **Breaking:** `MGLMapView.direction` is now expressed in terms of degrees clockwise from true north, as indicated in the documentation, rather than counterclockwise. ([#1789](https://github.com/mapbox/mapbox-gl-native/pull/1789)) +- A Satellite style showing Mapbox Satellite imagery is now bundled with Mapbox GL. ([#1845](https://github.com/mapbox/mapbox-gl-native/pull/1845)) +- Improved `UIView` tracking to the map. ([#1813](https://github.com/mapbox/mapbox-gl-native/pull/1813)) +- Delegate method `-[MGLMapViewDelegate mapView:didFailToLocateUserWithError:]` now works. ([#1608](https://github.com/mapbox/mapbox-gl-native/pull/1608)) +- It is now possible to fit the map’s viewport to a coordinate bounding box via `-[MGLMapView setVisibleCoordinateBounds:animated:]` or to a specific set of coordinates via `-[MGLMapView setVisibleCoordinates:count:edgePadding:animated:]`. ([#1783](https://github.com/mapbox/mapbox-gl-native/pull/1783), [#1795](https://github.com/mapbox/mapbox-gl-native/pull/1795)) +- The logo and ℹ️ no longer disappear or get distorted after embedding MGLMapView in a different view, and you can now access these subviews directly via properties on MGLMapView. ([#1779](https://github.com/mapbox/mapbox-gl-native/pull/1779), [#1815](https://github.com/mapbox/mapbox-gl-native/pull/1815)) +- Raster tiles now look sharper midway between two zoom levels. ([#1843](https://github.com/mapbox/mapbox-gl-native/pull/1843)) +- Resetting the map rotation to north no longer also resets the user location tracking mode. ([#1809](https://github.com/mapbox/mapbox-gl-native/pull/1809)) +- `-[MGLMapView convertPoint:toCoordinateFromView:]` now returns accurate coordinates on iPhone 6. ([#1827](https://github.com/mapbox/mapbox-gl-native/pull/1827)) +- Fixed an issue in which `-[MGLMapView direction]` would sometimes return 360 instead of 0. ([#1829](https://github.com/mapbox/mapbox-gl-native/pull/1829)) +- Build against iOS 8.4. ([#1868](https://github.com/mapbox/mapbox-gl-native/pull/1868)) + ## 0.4.0 ### Core diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..3348bf902 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,11 @@ +# Contributing + +If you have a usage question for a product built on Mapbox GL (such as Mapbox Mobile toolkits like our SDKs), please visit https://www.mapbox.com/help/. + +If you want to contribute code: + +1. Please familiarize yourself with the [install process](INSTALL.md). + +1. Ensure that the [existing issues](https://github.com/mapbox/mapbox-gl-native/issues?utf8=✓&q=) don't already cover your question or contribution. + +1. Pull requests gladly accepted. diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 000000000..fcc5d90c6 --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,351 @@ +# Install + +This document describes the process for setting up Mapbox GL for development _on the framework itself_. + +# Depends + + - Modern C++ compiler that supports `-std=c++14` (clang++ 3.5 or later or g++-4.9 or later) + - [Boost headers](http://boost.org/) + - [`zlib`](http://www.zlib.net/) + - [`libpng`](http://www.libpng.org/pub/png/libpng.html) + - [`libuv 0.10+`](https://github.com/joyent/libuv) + - [`glfw 3.1+`](http://www.glfw.org/) + - [`libcurl`](http://curl.haxx.se/libcurl/) (depends on OpenSSL; Linux only) + - Apple Command Line Tools (for build on OS X; available at [Apple Developer](https://developer.apple.com/downloads)) + - `pkg-config` (for build only) + - [Homebrew](http://brew.sh) (for building on OS X) + - [xcpretty](https://github.com/supermarin/xcpretty) (for building on OS X) + - [Cask](http://caskroom.io/) (for building Android on OS X) + - Python 2.x (for build only) + +# Build instructions + +We try to link to as many system-provided libraries as possible. When these are unavailable or too outdated, we run a thin build-script layer called [Mason](https://github.com/mapbox/mason) to automate builds, and load precompiled binary packages when possible. + +Be sure to pull down all submodules first: + + git submodule init + git submodule update + +## OS X + +This process gives you either a native OS X windowed app or a Linux desktop app with a map view for development purposes, built on an OS X host system. + +### Build + +ImageMagick is required for tests. It's recommended to install it with [Homebrew](http://brew.sh/) via `brew install imagemagick`. + +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 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`. +- `make linux run-linux`: Builds and runs the Linux application with `make`. + +Note that you can't have more than one project in Xcode open at a time since the static library project is shared across the OS X, Linux and iOS project. + +### Test + +- `make test-*` Builds and runs all tests. You can specify individual tests by replacing * with their name. + +### Usage + +- Press `S` to cycle through bundled styles +- Press `X` to reset the transform +- Press `N` to reset north +- Press `Tab` to toggle debug information +- Press `C` to toggle symbol collision debug boxes +- Press `Esc` to quit +- Press `1` through `6` to add increasing numbers of point annotations for testing + +### Target + +OS X 10.9+ + + +## iOS (Test App) + +This process gives you a native iOS app for simulator or device which compiles the source to GL within it for fastest turnaround time during development. Normal, production use of the library is via a precompiled binary linked into the app target. + +Developing for iOS implies an OS X host system. + +### Build + +If you want to build from source and/or contribute to development of the project, run `make iproj`, which will create and open an Xcode project which 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. + +### Test + +You can run `make itest` to run the included integration tests. Requires `gem install xcpretty`. If you want to run the tests in Xcode instead, first `make ipackage` to create a local static library version, then open `test/ios/ios-tests.xcodeproj`, and lastly `Command + U` on the `Mapbox GL Tests` application target. + +### Usage + +- Pan to move +- Pinch to zoom +- Use two fingers to rotate +- Double-tap to zoom in one level +- Two-finger single-tap to zoom out one level +- Double-tap, long-pressing the second, then pan up and down to "quick zoom" (iPhone only, meant for one-handed use) +- Use the debug menu to add test annotations, reset position, and toggle debug info. + +## iOS (Your App) + +**If you want a production version of this project, use [Mapbox Mobile](http://mapbox.com/mobile).** + +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/) 2.2v963 for API docs generation (only this exact version works right now ([#2117](https://github.com/mapbox/mapbox-gl-native/issues/2117)). + + ``` + 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. + +### 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. + + {...} + + m.source = { + :http => "http://{...}/mapbox-ios-sdk.zip", + :flatten => true + } + + {...} + +1. Update your app's `Podfile` to point to the `Mapbox-iOS-SDK.podspec`. + + 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: + + - `CoreTelephony.framework` + - `GLKit.framework` + - `ImageIO.framework` + - `MobileCoreServices.framework` + - `QuartzCore.framework` + - `SystemConfiguration.framework` + - `libc++.dylib` + - `libsqlite3.dylib` + - `libz.dylib` + +1. Add `-ObjC` to your target's "Other Linker Flags" build setting (`OTHER_LDFLAGS`). + +### Target + +Devices: iPhone 4S and above (5, 5c, 5s, 6, 6 Plus), iPad 2 and above (3, 4, Mini, Air, Mini 2, Air 2), iPod touch 5th gen and above + +iOS: 7.0+ + + +## Linux + +We are using Ubuntu for development. While the software should work on other distributions as well, we are not providing explicit build instructions here. + +This process gives you a Linux desktop app built on a Linux host system. + +### Build + +Install GCC 4.9+ if you are running Ubuntu 14.04 or older. Alternatively, you can also use [Clang 3.5+](http://llvm.org/apt/). + + sudo add-apt-repository --yes ppa:ubuntu-toolchain-r/test + sudo apt-get update + sudo apt-get install gcc-4.9 g++-4.9 + export CXX=g++-4.9 + +Ensure you have git and other build essentials: + + sudo apt-get install curl git build-essential zlib1g-dev automake \ + libtool xutils-dev make cmake pkg-config python-pip \ + libcurl4-openssl-dev libpng-dev libsqlite3-dev \ + libllvm3.4 + +Install glfw3 dependencies: + + sudo apt-get install libxi-dev libglu1-mesa-dev x11proto-randr-dev \ + x11proto-xext-dev libxrandr-dev \ + x11proto-xf86vidmode-dev libxxf86vm-dev \ + libxcursor-dev libxinerama-dev + +Then, you can then proceed to build the library: + + git submodule update --init + make linux + +Set an access token as described below, and then run: + + make run-linux + +### Test + +- `make test-*` Builds and runs all tests. You can specify individual tests by replacing * with their name. + +### Usage + +- Press `S` to cycle through bundled styles +- Press `X` to reset the transform +- Press `N` to reset north +- Press `Tab` to toggle debug information +- Press `C` to toggle symbol collision debug boxes +- Press `Esc` to quit +- Press `1` through `6` to add increasing numbers of point annotations for testing + +### Target + +Ubuntu + + +## Android + +### Build (Linux Host) + +Install a few build dependencies: + + apt-get install -y make git build-essential automake \ + libtool make cmake pkg-config lib32stdc++6 lib32z1 + +Install Oracle JDK 7 (requires license agreement) from http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html + + export JAVA_HOME="/dir/to/jdk1.7.0_71" + +Install the [Android NDK Revision 10e](https://developer.android.com/tools/sdk/ndk/index.html). + + export ANDROID_NDK_PATH="/dir/to/android-ndk-r10e" + +Install the Android SDK. We recommend doing this by way of [Android Studio](https://developer.android.com/sdk/installing/studio.html). + + export ANDROID_HOME="/dir/to/android-sdk-linux" + +Run: + + make android + +You can then open `android/java` in Android Studio via "Import Non-Android Studio Project". + +### Build (OS X Host) + +Install Oracle JDK 7+: + + brew cask install java + +Install the [Android NDK Revision 10e](https://developer.android.com/tools/sdk/ndk/index.html) for 64-bit OS X: + + brew install android-ndk + +This will also install the dependency `android-sdk`. + +Install [Android Studio](https://developer.android.com/sdk/installing/studio.html): + + brew cask install android-studio + android + +By default, the SDK will be installed to `/usr/local/opt/android-sdk`. If you open Android Studio at this point, you may get an error message telling you that it can't find a JVM, it's because you installed a custom Java VM from Oracle. Follow [these instructions](http://tools.android.com/recent/androidstudio1rc3_releasecandidate3released) to start Android Studio. You'll wind up setting these environment variables in your .bash_profile or similar: + + echo "export ANDROID_HOME=`brew --prefix android-sdk`" >> .bash_profile + echo "export ANDROID_NDK_PATH=`brew --cellar android-ndk`/r10e" >> .bash_profile + # Replace with something like /Library/Java/JavaVirtualMachines/jdk1.8.0_31.jdk/Contents/Home + echo "export JAVA_HOME=" >> .bash_profile + echo "export STUDIO_JDK=$JAVA_HOME" >> .bash_profile + +Run: + + make android + open -a Android\ Studio + +You can then open `android/java` in Android Studio via "import project (Eclipse ADT, Gradle, etc.)". + +### Setting up Android emulator + +If you want to run the test app in the emulator, we recommend the x86 build because it will run a lot faster. + +First ensure you have an `MAPBOX_ACCESS_TOKEN` environment variable set, as described below. Then, create an x86 build: + + ANDROID_ABI=x86 make android + +In Android Studio, create an x86 AVD (Android Virtual Device): + +- Open AVD Manager via the Tools menu -> Android -> AVD Manager +- Click "Create Virtual Device" at the bottom on AVD Manager window +- Select one of the device profiles, for example the Nexus 4 +- Click "Next" +- Select a Lollipop or Kitkat release with ABI of x86. If the line is greyed out click Download to download the OS files. +- Click "Next" +- Under "Emulated Performance" check "Host GPU" and uncheck "Store a snapshot for faster startup" +- Click "Finish" +- Close the AVD Manager + +Now when you run or debug the Android project you will see a window "Choose Device". Select your new AVD from drop down under "Launch emulator". If you select "Use same device for future launches" Android Studio will remember the selection and not ask again. + +### Running on a hardware device + +First read the [Google documentation](http://developer.android.com/tools/device.html) to set up your device and your OS to connect to the device. + +When you plug your device in and then run or debug the Android project you will see a window "Choose Device". Choose your device from the "Choose a running device" list. + +If your device does not show up, you have not set it up properly. Double check the [Google documentation](http://developer.android.com/tools/device.html). + +### Distribution + +Use the `Makefile` target `make apackage` in order to build JNI libraries for all supported ABI's for eventual distribution of the whole package. + +### Target + +Devices running Android 4.0+ (API level 14+). + + +# Troubleshooting + +To trigger a complete rebuild, run `make clean` and then start over generating the Xcode projects or `Makefile` as described above. + +If you are having trouble getting the dependencies right, you can blow away the `mason_packages` directory, or run `make distclean`. This means the `Makefile` and configure script will automatically install the dependencies again on the next try. + +On OS X, you can also try clearing the Xcode cache with `make clear_xcode_cache`. + +# Mapbox API 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/). + +For iOS and OS X use of the demo apps in Xcode, setup 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) + +For Linux, set the environment variable `MAPBOX_ACCESS_TOKEN` to your token. + +For Android, gradle will take the value of `MAPBOX_ACCESS_TOKEN` and save it to `android/java/MapboxGLAndroidSDKTestApp/src/main/res/raw/token.txt` where the app will read it from. + +# Style + +Default styles in the JSON-based [styling format](https://github.com/mapbox/mapbox-gl-style-spec) are included at `./styles` as a submodule. See the [styles project](https://github.com/mapbox/mapbox-gl-styles) to report problems or to contribute to these styles. diff --git a/LICENSE.md b/LICENSE.md index e5ed29c9a..58b949436 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,15 +1,15 @@ -mapbox-gl-native copyright (c) 2014, Mapbox. +mapbox-gl-native copyright (c) 2014, Mapbox. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, @@ -25,6 +25,525 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. =========================================================================== +Mapbox GL uses portions of Android Gesture Detectors Framework. + +Copyright (c) 2012, Almer Thie + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=========================================================================== + +Mapbox GL uses portions of Android Support Library. + +Copyright (c) 2005-2013, The Android Open Source Project + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +=========================================================================== + +Mapbox GL uses portions of Boost. + +Distributed under the Boost Software License, Version 1.0. + +http://www.boost.org/LICENSE_1_0.txt + +=========================================================================== + +Mapbox GL uses portions of Clipper. + +Author : Angus Johnson +Version : 6.1.3a +Date : 22 January 2014 +Website : http://www.angusj.com +Copyright : Angus Johnson 2010-2014 + +License: +Use, modification & distribution is subject to Boost Software License Ver 1. +http://www.boost.org/LICENSE_1_0.txt + +Attributions: +The code in this library is an extension of Bala Vatti's clipping algorithm: +"A generic solution to polygon clipping" +Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. +http://portal.acm.org/citation.cfm?id=129906 + +Computer graphics and geometric modeling: implementation and algorithms +By Max K. Agoston +Springer; 1 edition (January 4, 2005) +http://books.google.com/books?q=vatti+clipping+agoston + +See also: +"Polygon Offsetting by Computing Winding Numbers" +Paper no. DETC2005-85513 pp. 565-575 +ASME 2005 International Design Engineering Technical Conferences +and Computers and Information in Engineering Conference (IDETC/CIE2005) +September 24-28, 2005 , Long Beach, California, USA +http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf + +=========================================================================== + +Mapbox GL uses portions of CSS Color Parser. + +(c) Dean McNamee , 2012. +C++ port by Konstantin Käfer , 2014. + +https://github.com/deanm/css-color-parser-js +https://github.com/kkaefer/css-color-parser-cpp + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. + +=========================================================================== + +Mapbox GL uses portions of GLFW. + +Copyright (c) 2002-2006 Marcus Geelnard +Copyright (c) 2006-2010 Camilla Berglund + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + +=========================================================================== + +Mapbox GL uses portions of libc++. + +The libc++ library is dual licensed under both the University of Illinois +"BSD-Like" license and the MIT license. As a user of this code you may choose +to use it under either license. As a contributor, you agree to allow your code +to be used under both. + +Full text of the relevant licenses is included below. + +==== + +University of Illinois/NCSA +Open Source License + +Copyright (c) 2009-2015 by the contributors listed in CREDITS.TXT + +All rights reserved. + +Developed by: + + LLVM Team + + University of Illinois at Urbana-Champaign + + http://llvm.org + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal with +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimers. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimers in the + documentation and/or other materials provided with the distribution. + +* Neither the names of the LLVM Team, University of Illinois at + Urbana-Champaign, nor the names of its contributors may be used to + endorse or promote products derived from this Software without specific + prior written permission. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE +SOFTWARE. + +==== + +Copyright (c) 2009-2014 by the contributors listed in CREDITS.TXT + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +=========================================================================== + +Mapbox GL uses portions of libcurl. + +COPYRIGHT AND PERMISSION NOTICE + +Copyright (c) 1996 - 2015, Daniel Stenberg, . + +All rights reserved. + +Permission to use, copy, modify, and distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright +notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall not +be used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization of the copyright holder. + +=========================================================================== + +Mapbox GL uses portions of libjpeg. + +The authors make NO WARRANTY or representation, either express or implied, +with respect to this software, its quality, accuracy, merchantability, or +fitness for a particular purpose. This software is provided "AS IS", and you, +its user, assume the entire risk as to its quality and accuracy. + +This software is copyright (C) 1991-2014, Thomas G. Lane, Guido Vollbeding. +All Rights Reserved except as specified below. + +Permission is hereby granted to use, copy, modify, and distribute this +software (or portions thereof) for any purpose, without fee, subject to these +conditions: +(1) If any part of the source code for this software is distributed, then this +README file must be included, with this copyright and no-warranty notice +unaltered; and any additions, deletions, or changes to the original files +must be clearly indicated in accompanying documentation. +(2) If only executable code is distributed, then the accompanying +documentation must state that "this software is based in part on the work of +the Independent JPEG Group". +(3) Permission for use of this software is granted only if the user accepts +full responsibility for any undesirable consequences; the authors accept +NO LIABILITY for damages of any kind. + +These conditions apply to any software derived from or based on the IJG code, +not just to the unmodified library. If you use our work, you ought to +acknowledge us. + +Permission is NOT granted for the use of any IJG author's name or company name +in advertising or publicity relating to this software or products derived from +it. This software may be referred to only as "the Independent JPEG Group's +software". + +We specifically permit and encourage the use of this software as the basis of +commercial products, provided that all warranty or liability claims are +assumed by the product vendor. + + +The Unix configuration script "configure" was produced with GNU Autoconf. +It is copyright by the Free Software Foundation but is freely distributable. +The same holds for its supporting scripts (config.guess, config.sub, +ltmain.sh). Another support script, install-sh, is copyright by X Consortium +but is also freely distributable. + +The IJG distribution formerly included code to read and write GIF files. +To avoid entanglement with the Unisys LZW patent (now expired), GIF reading +support has been removed altogether, and the GIF writer has been simplified +to produce "uncompressed GIFs". This technique does not use the LZW +algorithm; the resulting GIF files are larger than usual, but are readable +by all standard GIF decoders. + +We are required to state that + "The Graphics Interchange Format(c) is the Copyright property of + CompuServe Incorporated. GIF(sm) is a Service Mark property of + CompuServe Incorporated." + +=========================================================================== + +Mapbox GL uses portions of libpng. + +This copy of the libpng notices is provided for your convenience. In case of +any discrepancy between this copy and the notices in the file png.h that is +included in the libpng distribution, the latter shall prevail. + +COPYRIGHT NOTICE, DISCLAIMER, and LICENSE: + +If you modify libpng you may insert additional notices immediately following +this sentence. + +This code is released under the libpng license. + +libpng versions 1.0.7, July 1, 2000, through 1.6.18, July 23, 2015, are +Copyright (c) 2000-2002, 2004, 2006-2015 Glenn Randers-Pehrson, and are +distributed according to the same disclaimer and license as libpng-1.0.6 +with the following individuals added to the list of Contributing Authors: + + Simon-Pierre Cadieux + Eric S. Raymond + Mans Rullgard + Cosmin Truta + Gilles Vollant + James Yu + +and with the following additions to the disclaimer: + + There is no warranty against interference with your enjoyment of the + library or against infringement. There is no warranty that our + efforts or the library will fulfill any of your particular purposes + or needs. This library is provided with all faults, and the entire + risk of satisfactory quality, performance, accuracy, and effort is with + the user. + +libpng versions 0.97, January 1998, through 1.0.6, March 20, 2000, are +Copyright (c) 1998-2000 Glenn Randers-Pehrson, and are distributed according +to the same disclaimer and license as libpng-0.96, with the following +individuals added to the list of Contributing Authors: + + Tom Lane + Glenn Randers-Pehrson + Willem van Schaik + +libpng versions 0.89, June 1996, through 0.96, May 1997, are +Copyright (c) 1996-1997 Andreas Dilger, and are +distributed according to the same disclaimer and license as libpng-0.88, +with the following individuals added to the list of Contributing Authors: + + John Bowler + Kevin Bracey + Sam Bushell + Magnus Holmgren + Greg Roelofs + Tom Tanner + +libpng versions 0.5, May 1995, through 0.88, January 1996, are +Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + +For the purposes of this copyright and license, "Contributing Authors" +is defined as the following set of individuals: + + Andreas Dilger + Dave Martindale + Guy Eric Schalnat + Paul Schmidt + Tim Wegner + +The PNG Reference Library is supplied "AS IS". The Contributing Authors +and Group 42, Inc. disclaim all warranties, expressed or implied, +including, without limitation, the warranties of merchantability and of +fitness for any purpose. The Contributing Authors and Group 42, Inc. +assume no liability for direct, indirect, incidental, special, exemplary, +or consequential damages, which may result from the use of the PNG +Reference Library, even if advised of the possibility of such damage. + +Permission is hereby granted to use, copy, modify, and distribute this +source code, or portions hereof, for any purpose, without fee, subject +to the following restrictions: + +1. The origin of this source code must not be misrepresented. + +2. Altered versions must be plainly marked as such and must not + be misrepresented as being the original source. + +3. This Copyright notice may not be removed or altered from any + source or altered source distribution. + +The Contributing Authors and Group 42, Inc. specifically permit, without +fee, and encourage the use of this source code as a component to +supporting the PNG file format in commercial products. If you use this +source code in a product, acknowledgment is not required but would be +appreciated. + +=========================================================================== + +Mapbox GL uses portions of libtess2. + +SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) +Copyright (C) [dates of first publication] Silicon Graphics, Inc. +All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice including the dates of first publication and either this +permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Silicon Graphics, Inc. shall not +be used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Silicon Graphics, Inc. + +=========================================================================== + +Mapbox GL uses portions of libuv. + +libuv is part of the Node project: http://nodejs.org/ +libuv may be distributed alone under Node's license: + +==== + +Copyright Joyent, Inc. and other Node contributors. All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. + +==== + +This license applies to all parts of libuv that are not externally +maintained libraries. + +The externally maintained libraries used by libuv are: + +- tree.h (from FreeBSD), copyright Niels Provos. Two clause BSD license. + +- inet_pton and inet_ntop implementations, contained in src/inet.c, are + copyright the Internet Systems Consortium, Inc., and licensed under the ISC + license. + +- stdint-msvc2008.h (from msinttypes), copyright Alexander Chemeris. Three + clause BSD license. + +- pthread-fixes.h, pthread-fixes.c, copyright Google Inc. and Sony Mobile + Communications AB. Three clause BSD license. + +- android-ifaddrs.h, android-ifaddrs.c, copyright Berkeley Software Design + Inc, Kenneth MacKay and Emergya (Cloud4all, FP7/2007-2013, grant agreement + n° 289016). Three clause BSD license. + +=========================================================================== + +Mapbox GL uses portions of libzip. + +Copyright (C) 1999-2014 Dieter Baron and Thomas Klausner + +The authors can be contacted at + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + +3. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=========================================================================== + +Mapbox GL uses portions of LOST. + +Copyright (c) 2014 Mapzen + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +=========================================================================== + Mapbox GL uses portions of the Mapbox iOS SDK, which was derived from the Route-Me open source project, including the Alpstein fork of it. @@ -37,10 +556,10 @@ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. +list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE @@ -53,3 +572,316 @@ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=========================================================================== + +Mapbox GL uses portions of nunicode. + +Copyright (c) 2013 Aleksey Tulinov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +=========================================================================== + +Mapbox GL uses portions of OkHTTP. + +Copyright 2014 Square, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +=========================================================================== + +Mapbox GL uses portions of OpenSSL. + +LICENSE ISSUES +============== + +The OpenSSL toolkit stays under a dual license, i.e. both the conditions of +the OpenSSL License and the original SSLeay license apply to the toolkit. +See below for the actual license texts. Actually both licenses are BSD-style +Open Source licenses. In case of any license issues related to OpenSSL +please contact openssl-core@openssl.org. + +OpenSSL License +--------------- + +Copyright (c) 1998-2011 The OpenSSL Project. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + +3. All advertising materials mentioning features or use of this + software must display the following acknowledgment: + "This product includes software developed by the OpenSSL Project + for use in the OpenSSL Toolkit. (http://www.openssl.org/)" + +4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + endorse or promote products derived from this software without + prior written permission. For written permission, please contact + openssl-core@openssl.org. + +5. Products derived from this software may not be called "OpenSSL" + nor may "OpenSSL" appear in their names without prior written + permission of the OpenSSL Project. + +6. Redistributions of any form whatsoever must retain the following + acknowledgment: + "This product includes software developed by the OpenSSL Project + for use in the OpenSSL Toolkit (http://www.openssl.org/)" + +THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY +EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR +ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. + +This product includes cryptographic software written by Eric Young +(eay@cryptsoft.com). This product includes software written by Tim +Hudson (tjh@cryptsoft.com). + +Original SSLeay License +----------------------- + +Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) +All rights reserved. + +This package is an SSL implementation written +by Eric Young (eay@cryptsoft.com). +The implementation was written so as to conform with Netscapes SSL. + +This library is free for commercial and non-commercial use as long as +The following conditions are aheared to. The following conditions +apply to all code found in this distribution, be it the RC4, RSA, +lhash, DES, etc., code; not just the SSL code. The SSL documentation +included with this distribution is covered by the same copyright terms +except that the holder is Tim Hudson (tjh@cryptsoft.com). + +Copyright remains Eric Young's, and as such any Copyright notices in +the code are not to be removed. +If this package is used in a product, Eric Young should be given attribution +as the author of the parts of the library used. +This can be in the form of a textual message at program startup or +in documentation (online or textual) provided with the package. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. All advertising materials mentioning features or use of this software + must display the following acknowledgement: + "This product includes cryptographic software written by + Eric Young (eay@cryptsoft.com)" + The word 'cryptographic' can be left out if the rouines from the library + being used are not cryptographic related :-). +4. If you include any Windows specific code (or a derivative thereof) from + the apps directory (application code) you must include an acknowledgement: + "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + +THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +The licence and distribution terms for any publically available version or +derivative of this code cannot be changed. i.e. this code cannot simply be +copied and put under another distribution licence +[including the GNU Public Licence.] + +=========================================================================== + +Mapbox GL uses portions of RapidJSON. + +Tencent is pleased to support the open source community by making RapidJSON available. + +Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. + +If you have downloaded a copy of the RapidJSON binary from Tencent, please note that the RapidJSON binary is licensed under the MIT License. +If you have downloaded a copy of the RapidJSON source code from Tencent, please note that RapidJSON source code is licensed under the MIT License, except for the third-party components listed below which are subject to different license terms. Your integration of RapidJSON into your own projects may require compliance with the MIT License, as well as the other licenses applicable to the third-party components included within RapidJSON. To avoid the problematic JSON license in your own projects, it's sufficient to exclude the bin/jsonchecker/ directory, as it's the only code under the JSON license. +A copy of the MIT License is included in this file. + +Other dependencies and licenses: + +Open Source Software Licensed Under the BSD License: +-------------------------------------------------------------------- + +The msinttypes r29 +Copyright (c) 2006-2013 Alexander Chemeris +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +* Neither the name of copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Open Source Software Licensed Under the JSON License: +-------------------------------------------------------------------- + +json.org +Copyright (c) 2002 JSON.org +All Rights Reserved. + +JSON_checker +Copyright (c) 2002 JSON.org +All Rights Reserved. + +Terms of the JSON License: +--------------------------------------------------- + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Terms of the MIT License: +-------------------------------------------------------------------- + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +=========================================================================== + +Mapbox GL uses portions of Reachability. + +Copyright (c) 2011, Tony Million. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +=========================================================================== + +Mapbox GL uses portions of SQLite. + +2001 September 15 + +The author disclaims copyright to this source code. In place of +a legal notice, here is a blessing: + + May you do good and not evil. + May you find forgiveness for yourself and forgive others. + May you share freely, never taking more than you give. + +=========================================================================== + +Mapbox GL uses portions of SVPulsingAnnotationView. + +Copyright (c) 2013, Sam Vermette + +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +=========================================================================== + +Mapbox GL uses portions of zlib. + +Acknowledgments: + +The deflate format used by zlib was defined by Phil Katz. The deflate and +zlib specifications were written by L. Peter Deutsch. Thanks to all the +people who reported problems and suggested various improvements in zlib; they +are too numerous to cite here. + +Copyright notice: + +(C) 1995-2013 Jean-loup Gailly and Mark Adler + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu diff --git a/Makefile b/Makefile index 34af9b85e..c69e426dd 100644 --- a/Makefile +++ b/Makefile @@ -1,225 +1,108 @@ -BUILDTYPE ?= Release -PYTHON ?= python -PREFIX ?= /usr/local -ANDROID_ABI ?= arm-v7 +export BUILDTYPE ?= Release +export BUILD_TEST ?= 1 +export BUILD_RENDER ?= 1 +# Determine build platform ifeq ($(shell uname -s), Darwin) -HOST = osx -HEADLESS = cgl -JOBS ?= $(shell sysctl -n hw.ncpu) -endif - -ifeq ($(shell uname -s), Linux) -HOST = linux -JOBS ?= $(shell grep --count processor /proc/cpuinfo) -BUILD_ROOT = $(shell pwd) -endif - -JOBS ?= 1 - -all: mbgl - -include config/defaults.mk - -#### Dependencies ############################################################## - -# Wildcard targets get removed after build by default, but we want to preserve the config. -.PRECIOUS: config/%.gypi -config/%.gypi: CMD = ./configure config/$*.gypi -config/%.gypi: configure - @$(ENV_$*) ./scripts/flock.py build/Configure.lock ./configure config/$*.gypi - -styles/styles: - git submodule update --init styles - -ifeq ($(shell uname -s), Darwin) -SMCalloutView: - git submodule update --init platform/ios/vendor/SMCalloutView +export BUILD = osx +export JOBS ?= $(shell sysctl -n hw.ncpu) +export XCPRETTY := $(shell ./scripts/xcpretty.sh) +else ifeq ($(shell uname -s), Linux) +export BUILD = linux +export JOBS ?= $(shell grep --count processor /proc/cpuinfo) else -SMCalloutView: +$(error Cannot determine build platform) +endif +export BUILD_VERSION = $(shell uname -m) + +RUN = +@$(MAKE) -f scripts/main.mk + +default: ; @printf "You must specify a valid target\n" + +#### OS X targets ############################################################## + +ifeq ($(BUILD),osx) +.PHONY: osx xosx run-osx run-xosx +osx: ; $(RUN) HOST=osx HOST_VERSION=x86_64 Makefile/osxapp +xosx: ; $(RUN) HOST=osx HOST_VERSION=x86_64 Xcode/osxapp +run-osx: osx ; @"build/osx-x86_64/$(BUILDTYPE)/Mapbox GL.app/Contents/MacOS/Mapbox GL" +run-xosx: xosx ; @"gyp/build/$(BUILDTYPE)/Mapbox GL.app/Contents/MacOS/Mapbox GL" + +.PHONY: Xcode/osx Xcode/ios +Xcode/ios: ; $(RUN) HOST=ios Xcode/__project__ +Xcode/osx: ; $(RUN) HOST=osx HOST_VERSION=x86_64 Xcode/__project__ + +.PHONY: xproj iproj +xproj: Xcode/osx ; @open ./build/osx-x86_64/gyp/osx.xcodeproj +iproj: Xcode/ios ; @open ./build/ios-all/gyp/ios.xcodeproj + +.PHONY: ios ibench isim +ios ibench isim: export XCODEBUILD_ARGS += PROVISIONING_PROFILE="$$(PROVISIONING_PROFILE)" + +ios: export XCODEBUILD_ARGS += -sdk iphoneos ARCHS="arm64 armv7 armv7s" +ios: ; $(RUN) HOST=ios Xcode/iosapp + +isim: export XCODEBUILD_ARGS += -sdk iphonesimulator ARCHS="x86_64 i386" +isim: ; $(RUN) HOST=ios Xcode/iosapp + +ibench: export XCODEBUILD_ARGS += -sdk iphoneos ARCHS="arm64" +ibench: ; $(RUN) HOST=ios Xcode/ios-bench + +.PHONY: ipackage ipackage-strip ipackage-sim itest +ipackage: Xcode/ios ; @JOBS=$(JOBS) ./scripts/ios/package.sh +ipackage-strip: Xcode/ios ; @JOBS=$(JOBS) ./scripts/ios/package.sh strip +ipackage-sim: Xcode/ios ; @JOBS=$(JOBS) ./scripts/ios/package.sh sim +itest: ipackage-sim ; ./scripts/ios/test.sh endif -geojsonvt: - git submodule update --init src/mbgl/util/geojsonvt +#### All platforms targets ##################################################### -KIF: - git submodule update --init test/ios/KIF +.PHONY: linux run-linux run-valgrind-linux +linux: ; $(RUN) Makefile/linuxapp +run-linux: linux ; (cd build/linux-x86_64/$(BUILDTYPE) && ./mapbox-gl) +run-valgrind-linux: linux + (cd build/linux-x86_64/$(BUILDTYPE) && valgrind --leak-check=full --suppressions=../../../scripts/valgrind.sup ./mapbox-gl) +.PHONY: android android-lib +# Builds a particular android architecture. +android-lib-%: ; $(RUN) HOST=android HOST_VERSION=$* Makefile/androidapp -#### Build files ############################################################### +# Builds the default Android library +android-lib: ; $(RUN) HOST=android Makefile/androidapp -.PRECIOUS: Makefile/project -Makefile/project: config/$(HOST).gypi styles/styles SMCalloutView geojsonvt - deps/run_gyp gyp/$(HOST).gyp $(CONFIG_$(HOST)) $(LIBS_$(HOST)) --generator-output=./build/$(HOST) -f make +# Builds the selected/default Android library +android: android-lib + cd android/java && ./gradlew --parallel --max-workers=$(JOBS) assemble$(BUILDTYPE) -.PRECIOUS: Xcode/project -Xcode/project: config/$(HOST).gypi styles/styles SMCalloutView geojsonvt - deps/run_gyp gyp/$(HOST).gyp $(CONFIG_$(HOST)) $(LIBS_$(HOST)) --generator-output=./build/$(HOST) -f xcode +# Builds all android architectures for distribution. +apackage: android-lib-arm-v5 android-lib-arm-v7 +apackage: android-lib-x86 +apackage: android-lib-mips + cd android/java && ./gradlew --parallel-threads=$(JOBS) assemble$(BUILDTYPE) - -#### Library builds ############################################################ - -mbgl: Makefile/project - $(MAKE) -C build/$(HOST) BUILDTYPE=$(BUILDTYPE) everything - -standalone: Makefile/project - LINK=`pwd`/gyp/link.py $(MAKE) -C build/$(HOST) BUILDTYPE=$(BUILDTYPE) standalone - -install: Makefile/project - LINK=`pwd`/gyp/link.py $(MAKE) -C build/$(HOST) BUILDTYPE=$(BUILDTYPE) install - - -##### Test builds ############################################################## +# Builds the Node.js library +.PHONY: node +node: ; $(RUN) HTTP=none ASSET=none CACHE=none Makefile/node .PHONY: test -test: Makefile/project - $(MAKE) -C build/$(HOST) BUILDTYPE=$(BUILDTYPE) test +test: ; $(RUN) Makefile/test +test-%: ; $(RUN) test-$* +ifeq ($(BUILD),osx) +xtest: ; $(RUN) HOST=osx HOST_VERSION=x86_64 Xcode/test +endif -test-%: test - ./scripts/run_tests.sh "build/$(HOST)/$(BUILDTYPE)/test" --gtest_filter=$* - -.PHONY: xtest -xtest: XCPRETTY := $(shell ./scripts/xcpretty.sh) -xtest: Xcode/project - xcodebuild -project ./build/osx/gyp/osx.xcodeproj -configuration $(BUILDTYPE) -target test -jobs $(JOBS) $(XCPRETTY) - -xtest-%: xtest - ./scripts/run_tests.sh "build/osx/Build/Products/$(BUILDTYPE)/test" --gtest_filter=$* - - -#### Mac OS X application builds ############################################### - -.PHONY: osx run-osx -osx: Makefile/project - $(MAKE) -C build/osx BUILDTYPE=$(BUILDTYPE) osxapp - -run-osx: osx - "build/osx/$(BUILDTYPE)/Mapbox GL.app/Contents/MacOS/Mapbox GL" - -run-valgrind-osx: osx - (valgrind --leak-check=full --suppressions=scripts/valgrind.sup build/osx/$(BUILDTYPE)/Mapbox\ GL.app/Contents/MacOS/Mapbox\ GL) - -.PHONY: xosx-proj xosx run-xosx -xosx-proj: Xcode/project - open ./build/osx/gyp/osx.xcodeproj - -xosx: XCPRETTY := $(shell ./scripts/xcpretty.sh) -xosx: Xcode/project - xcodebuild -project ./build/osx/gyp/osx.xcodeproj -configuration $(BUILDTYPE) -target osxapp -jobs $(JOBS) $(XCPRETTY) - -run-xosx: xosx - "gyp/build/$(BUILDTYPE)/Mapbox GL.app/Contents/MacOS/Mapbox GL" - -# Legacy name -xproj: xosx-proj - - - -#### iOS application builds #################################################### - -.PRECIOUS: Xcode/ios -Xcode/ios: gyp/ios.gyp config/ios.gypi styles/styles SMCalloutView geojsonvt - deps/run_gyp gyp/ios.gyp $(CONFIG_ios) $(LIBS_ios) --generator-output=./build/ios -f xcode - -.PHONY: ios-proj ios isim ipackage -ios-proj: Xcode/ios - open ./build/ios/gyp/ios.xcodeproj - -ios: XCPRETTY := $(shell ./scripts/xcpretty.sh) -ios: Xcode/ios - xcodebuild -sdk iphoneos ARCHS="arm64 armv7 armv7s" PROVISIONING_PROFILE="19324a54-7455-4f0b-8e1c-e6957c718ebc" -project ./build/ios/gyp/ios.xcodeproj -configuration Release -target iosapp -jobs $(JOBS) $(XCPRETTY) - -ibench: XCPRETTY := $(shell ./scripts/xcpretty.sh) -ibench: Xcode/ios - xcodebuild -sdk iphoneos ARCHS="arm64" PROVISIONING_PROFILE="19324a54-7455-4f0b-8e1c-e6957c718ebc" -project ./build/ios/gyp/ios.xcodeproj -configuration Release -target ios-bench -jobs $(JOBS) $(XCPRETTY) - -isim: XCPRETTY := $(shell ./scripts/xcpretty.sh) -isim: Xcode/ios - xcodebuild -sdk iphonesimulator ARCHS="x86_64 i386" -project ./build/ios/gyp/ios.xcodeproj -configuration Debug -target iosapp -jobs $(JOBS) $(XCPRETTY) - -ipackage: Xcode/ios - JOBS=$(JOBS) ./scripts/ios/package.sh - -ipackage-strip: Xcode/ios - JOBS=$(JOBS) ./scripts/ios/package.sh strip - -ipackage-sim: Xcode/ios - JOBS=$(JOBS) ./scripts/ios/package.sh sim - -itest: ipackage-sim KIF - ./scripts/ios/test.sh - -# Legacy name -iproj: ios-proj - - -#### Linux application builds ################################################## - -.PHONY: linux run-linux -linux: Makefile/project - $(MAKE) -C build/$(HOST) BUILDTYPE=$(BUILDTYPE) linuxapp - -run-linux: linux - (cd build/$(HOST)/$(BUILDTYPE) && ./mapbox-gl) - -run-valgrind-linux: linux - (cd build/$(HOST)/$(BUILDTYPE) && valgrind --leak-check=full --suppressions=$(BUILD_ROOT)/scripts/valgrind.sup ./mapbox-gl) - -.PHONY: xlinux run-xlinux -xlinux: XCPRETTY := $(shell ./scripts/xcpretty.sh) -xlinux: Xcode/project - xcodebuild -project ./build/osx/gyp/osx.xcodeproj -configuration $(BUILDTYPE) -target linuxapp -jobs $(JOBS) $(XCPRETTY) - -run-xlinux: xlinux - "gyp/build/$(BUILDTYPE)/mapbox-gl" - - -#### Android libaries ######################################################### - -.PRECIOUS: Makefile/android-% -Makefile/android-%: CMD = deps/run_gyp android/mapboxgl-app.gyp $(CONFIG_android-$*) $(LIBS_android) --generator-output=./build/android-$* -f make-android -Makefile/android-%: config/android-%.gypi styles/styles geojsonvt - @echo $(CMD) - @$(ENV_android-$*) $(CMD) - -# Builds a particular android architecture. -android-lib-%: Makefile/android-% - @$(ENV_android-$*) ./scripts/flock.py -v build/Android.lock $(MAKE) -C build/android-$* BUILDTYPE=$(BUILDTYPE) androidapp - -# Builds the selected/default Android library -android-lib: android-lib-$(ANDROID_ABI) - -# Builds the selected/default Android library -android: android-lib-$(ANDROID_ABI) - cd android/java && ./gradlew --parallel-threads=$(JOBS) assemble$(BUILDTYPE) - -# Builds all android architectures. -android-all: $(ANDROID_ABIS) - cd android/java && ./gradlew --parallel-threads=$(JOBS) assemble$(BUILDTYPE) - -# Full build of GL Core for each architecture, build the Android Library, publish to Maven Central -android-deploy: $(ANDROID_ABIS) - cd android/java/MapboxGLAndroidSDK && chmod ugo+x deploy.sh && ./deploy.sh - -android-project: android-lib - - -##### Render builds ############################################################ - -render: Makefile/project - $(MAKE) -C build/$(HOST) BUILDTYPE=$(BUILDTYPE) mbgl-render - -.PHONY: xrender -xrender: XCPRETTY := $(shell ./scripts/xcpretty.sh) -xrender: Xcode/project - xcodebuild -project ./build/osx/gyp/osx.xcodeproj -configuration $(BUILDTYPE) -target mbgl-render -jobs $(JOBS) $(XCPRETTY) +.PHONY: render xrender +render: ; $(RUN) Makefile/mbgl-render +ifeq ($(BUILD),osx) +xrender: ; $(RUN) HOST=osx HOST_VERSION=x86_64 Xcode/mbgl-render +endif ##### Maintenace operations #################################################### .PHONY: clear_xcode_cache clear_xcode_cache: -ifeq ($(PLATFORM), osx) +ifeq ($(BUILD), osx) @CUSTOM_DD=`defaults read com.apple.dt.Xcode IDECustomDerivedDataLocation 2>/dev/null`; \ if [ $$CUSTOM_DD ]; then \ echo clearing files in $$CUSTOM_DD older than one day; \ @@ -233,7 +116,7 @@ endif .PHONY: clear_sqlite_cache clear_sqlite_cache: -ifeq ($(PLATFORM), osx) +ifeq ($(BUILD), osx) rm -f ~/Library/Application\ Support/Mapbox\ GL/cache.db else rm -f /tmp/mbgl-cache.db diff --git a/README.md b/README.md index 5a3884297..00432f17c 100644 --- a/README.md +++ b/README.md @@ -1,276 +1,17 @@ +# Mapbox GL (Native) + [![Travis](https://api.travis-ci.org/mapbox/mapbox-gl-native.svg?branch=master)](https://travis-ci.org/mapbox/mapbox-gl-native/builds) -An OpenGL renderer for [Mapbox Vector Tiles](https://www.mapbox.com/blog/vector-tiles), consisting of a C++ library for OS X and Linux and SDK bindings for iOS and Android. +An OpenGL ES-based renderer for [Mapbox Vector Tiles](https://www.mapbox.com/blog/vector-tiles), consisting of a C++ library for OS X and Linux and SDK bindings for iOS, Android and Node.js. -# Depends +**This repository is for development on Mapbox GL itself.** - - Modern C++ compiler that supports `-std=c++14` (clang++ 3.5 or later or g++-4.9 or later) - - [Boost headers](http://boost.org/) - - [`zlib`](http://www.zlib.net/) - - [`libpng`](http://www.libpng.org/pub/png/libpng.html) - - [`libuv 0.10+`](https://github.com/joyent/libuv) - - [`glfw 3.1+`](http://www.glfw.org/) - - [`libcurl`](http://curl.haxx.se/libcurl/) (depends on OpenSSL; Linux only) - - Apple Command Line Tools (for build on OS X; available at [Apple Developer](https://developer.apple.com/downloads)) - - `pkg-config` (for build only) - - [Homebrew](http://brew.sh) (for building on OS X) - - [xcpretty](https://github.com/supermarin/xcpretty) (for building on OS X) - - [Cask](http://caskroom.io/) (for building Android on OS X) - - Python 2.x (for build only) +If you want to use products _based on_ Mapbox GL, check out: -# Build instructions +- [Mapbox Mobile](http://mapbox.com/mobile) — iOS and Android native development libraries for vector maps in apps +- [`react-native-mapbox-gl`](https://github.com/mapbox/react-native-mapbox-gl) — React Native library for vector maps in apps +- [`qmapboxgl`](https://github.com/tmpsantos/qmapboxgl) — Qt library for vector map rendering -We try to link to as many system-provided libraries as possible. When these are unavailable or too outdated, we run a thin build-script layer called [Mason](https://github.com/mapbox/mason) to automate builds, and load precompiled binary packages when possible. +-- -Be sure to pull down all submodules first: - - git submodule init - git submodule update - -## OS X - -ImageMagick is required for tests. It's recommended to install it with [Homebrew](http://brew.sh/) via `brew install imagemagick`. - -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 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`. -- `make linux run-linux`: Builds and runs the Linux application with `make`. -- `make test-*` Builds and runs all tests. You can specify individual tests by replacing * with their name. - -Note that you can't have more than one project in Xcode open at a time since they the static library project is shared across the OS X, Linux and iOS project. - -Target OS X: 10.9+ - - -## iOS - -### Use - -#### CocoaPods - -[CocoaPods](https://cocoapods.org) is preferred. Put the following in your `Podfile`: - -```ruby -use_frameworks! -pod 'MapboxGL' -``` - -#### Manually - -1. Install [appledoc](http://appledoc.gentlebytes.com/appledoc/) 2.2v963 for API docs generation (only this exact version works). - - ``` - 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 `libMapboxGL.a`, `MapboxGL.bundle` for resources, a `Headers` folder, and a `Docs` folder with HTML API documentation. -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. - - `MapboxGL.bundle` is in your target's *Copy Bundle Resources* build phase. - - `libMapboxGL.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: - - `CoreTelephony.framework` - - `GLKit.framework` - - `ImageIO.framework` - - `MobileCoreServices.framework` - - `QuartzCore.framework` - - `SystemConfiguration.framework` - - `libc++.dylib` - - `libsqlite3.dylib` - - `libz.dylib` -1. Add `-ObjC` to your target's "Other Linker Flags" build setting (`OTHER_LDFLAGS`). -1. [Set the Mapbox API access token](#mapbox-api-access-tokens). -1. `#import "MapboxGL.h"` - -### Development - -If you want to build from source and/or contribute to development of the project, run `make iproj`, which will create and open an Xcode project which 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. - -#### Testing -You can run `make itest` to run the included integration tests. Requires `gem install xcpretty`. If you want to run the tests in Xcode instead, first `make ipackage` to create a local static library version, then open `test/ios/ios-tests.xcodeproj`, and lastly `Command + U` on the `Mapbox GL Tests` application target. - -Target devices: iPhone 4S and above (5, 5c, 5s, 6, 6 Plus) and iPad 2 and above (3, 4, Mini, Air, Mini 2, Air 2). - -Target iOS: 7.0 through latest 8.x. - - -## Linux - -We are using Ubuntu for development. While the software should work on other distributions as well, we are not providing explicit build instructions here. - -Install GCC 4.9+ if you are running Ubuntu 14.04 or older. Alternatively, you can also use [Clang 3.5+](http://llvm.org/apt/). - - sudo add-apt-repository --yes ppa:ubuntu-toolchain-r/test - sudo apt-get update - sudo apt-get install gcc-4.9 g++-4.9 - export CXX=g++-4.9 - -Ensure you have git and other build essentials: - - sudo apt-get install curl git build-essential zlib1g-dev automake \ - libtool xutils-dev make cmake pkg-config python-pip \ - libcurl4-openssl-dev libpng-dev libsqlite3-dev \ - libllvm3.4 - -Install glfw3 dependencies: - - sudo apt-get install libxi-dev libglu1-mesa-dev x11proto-randr-dev \ - x11proto-xext-dev libxrandr-dev \ - x11proto-xf86vidmode-dev libxxf86vm-dev \ - libxcursor-dev libxinerama-dev - -Then, you can then proceed to build the library: - - git submodule update --init - make linux - -Set an access token as described below, and then run: - - make run-linux - -## Android - -Target devices: All Android devices on Android 4.0 or newer (API level >= 14). - -### On Linux - -Install a few build dependencies: - - apt-get install -y make git build-essential automake \ - libtool make cmake pkg-config lib32stdc++6 lib32z1 - -Install Oracle JDK 7 (requires license agreement) from http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html - - export JAVA_HOME="/dir/to/jdk1.7.0_71" - -Install the [Android NDK Revision 10e](https://developer.android.com/tools/sdk/ndk/index.html). - - export ANDROID_NDK_PATH="/dir/to/android-ndk-r10e" - -Install the Android SDK. We recommend doing this by way of [Android Studio](https://developer.android.com/sdk/installing/studio.html). - - export ANDROID_HOME="/dir/to/android-sdk-linux" - -Run: - - make android - -You can then open `android/java` in Android Studio via "Import Non-Android Studio Project". - -### On OS X - -Install Oracle JDK 7+: - - brew cask install java - -Install the [Android NDK Revision 10e](https://developer.android.com/tools/sdk/ndk/index.html) for 64-bit OS X: - - brew install android-ndk - -This will also install the dependency `android-sdk`. - -Install [Android Studio](https://developer.android.com/sdk/installing/studio.html): - - brew cask install android-studio - android - -By default, the SDK will be installed to `/usr/local/opt/android-sdk`. If you open Android Studio at this point, you may get an error message telling you that it can't find a JVM, it's because you installed a custom Java VM from Oracle. Follow [these instructions](http://tools.android.com/recent/androidstudio1rc3_releasecandidate3released) to start Android Studio. You'll wind up setting these environment variables in your .bash_profile or similar: - - echo "export ANDROID_HOME=`brew --prefix android-sdk`" >> .bash_profile - echo "export ANDROID_NDK_PATH=`brew --cellar android-ndk`/r10e" >> .bash_profile - # Replace with something like /Library/Java/JavaVirtualMachines/jdk1.8.0_31.jdk - echo "export JAVA_HOME=" >> .bash_profile - echo "export STUDIO_JDK=$JAVA_HOME" >> .bash_profile - -Run: - - make android - open -a Android\ Studio - -You can then open `android/java` in Android Studio via "Import Non-Android Studio Project". - -### Setting up Android emulator - -If you want to run the test app in the emulator, we recommend the x86 build because it will run a lot faster. - -First ensure you have an `MAPBOX_ACCESS_TOKEN` environment variable set, as described below. Then, create an x86 build: - - ANDROID_ABI=x86 make android - -In Android Studio, create an x86 AVD (Android Virtual Device): -- Open AVD Manager via the Tools menu -> Android -> AVD Manager -- Click "Create Virtual Device" at the bottom on AVD Manager window -- Select one of the device profiles, for example the Nexus 4 -- Click "Next" -- Select a Lollipop or Kitkat release with ABI of x86. If the line is greyed out click Download to download the OS files. -- Click "Next" -- Under "Emulated Performance" check "Host GPU" and uncheck "Store a snapshot for faster startup" -- Click "Finish" -- Close the AVD Manager - -Now when you run or debug the Android project you will see a window "Choose Device". Select your new AVD from drop down under "Launch emulator". If you select "Use same device for future launches" Android Studio will remember the selection and not ask again. - -### Running on a hardware device - -First read the [Google documentation](http://developer.android.com/tools/device.html) to set up your device and your OS to connect to the device. - -When you plug your device in and then run or debug the Android project you will see a window "Choose Device". Choose your device from the "Choose a running device" list. - -If your device does not show you have not set it up properly, double check the [Google documentation](http://developer.android.com/tools/device.html). - -# Troubleshooting - -To trigger a complete rebuild, run `make clean` and then start over generating the Xcode projects or Makefiles as described above. - -If you are having trouble getting the dependencies right, you can blow away the `mason_packages` directory, or run `make distclean`. This means the Makefile and configure script will automatically install the dependencies again on the next try. - -On OS X, you can also try clearing the Xcode cache with `make clear_xcode_cache`. - -# Mapbox API 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/). - -For iOS and OS X use of the demo apps in Xcode, setup 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) - -For Linux, set the environment variable `MAPBOX_ACCESS_TOKEN` to your token. - -For Android, gradle will take the value of `MAPBOX_ACCESS_TOKEN` and save it to `android/java/MapboxGLAndroidSDKTestApp/src/main/res/raw/token.txt` where the app will read it from. - -# Style - -Some styles in JSON format are included at `./styles`. See the [style spec](https://github.com/mapbox/mapbox-gl-style-spec) for more details. - -# Usage - -## Desktop - -- Press `S` to cycle through bundled styles -- Press `X` to reset the transform -- Press `N` to reset north -- Press `Tab` to toggle debug information -- Press `C` to toggle symbol collision debug boxes -- Press `Esc` to quit -- Press `1` through `6` to add increasing numbers of point annotations for testing - -## Mobile - -- Pan to move -- Pinch to zoom -- Use two fingers to rotate -- Double-tap to zoom in one level -- Two-finger single-tap to zoom out one level -- Double-tap, long-pressing the second, then pan up and down to "quick zoom" (iPhone only, meant for one-handed use) -- Use the debug menu to add test annotations, reset position, and toggle debug info. - -# Other notes - -Under early development, this project was called LLMR (Low-Level Map Renderer), in case you see any lingering references to it. +See [`INSTALL.md`](./INSTALL.md) for development setup and install instructions, including dependencies. diff --git a/android/LICENSE_COMMONS.txt b/android/LICENSE_COMMONS.txt deleted file mode 100644 index d64569567..000000000 --- a/android/LICENSE_COMMONS.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/android/LICENSE_GESTURES.md b/android/LICENSE_GESTURES.md deleted file mode 100644 index d2d488c6e..000000000 --- a/android/LICENSE_GESTURES.md +++ /dev/null @@ -1,13 +0,0 @@ -Android Gesture Detectors Framework Licence -=================================== - -Copyright (c) 2012, Almer Thie - -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/android/LICENSE_LOST.txt b/android/LICENSE_LOST.txt deleted file mode 100644 index a0fba4cbc..000000000 --- a/android/LICENSE_LOST.txt +++ /dev/null @@ -1,13 +0,0 @@ -Copyright (c) 2014 Mapzen - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/android/cpp/jni.cpp b/android/cpp/jni.cpp index 36ef273d6..140cc9f3c 100644 --- a/android/cpp/jni.cpp +++ b/android/cpp/jni.cpp @@ -16,6 +16,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -25,6 +28,8 @@ namespace mbgl { namespace android { +JavaVM* theJVM; + std::string cachePath; std::string dataPath; std::string apkPath; @@ -45,6 +50,36 @@ jfieldID latLngZoomLatitudeId = nullptr; jfieldID latLngZoomLongitudeId = nullptr; jfieldID latLngZoomZoomId = nullptr; +jclass bboxClass = nullptr; +jmethodID bboxConstructorId = nullptr; +jfieldID bboxLatNorthId = nullptr; +jfieldID bboxLatSouthId = nullptr; +jfieldID bboxLonEastId = nullptr; +jfieldID bboxLonWestId = nullptr; + +jclass markerClass = nullptr; +jmethodID markerConstructorId = nullptr; +jfieldID markerPositionId = nullptr; +jfieldID markerSpriteId = nullptr; + +jclass polylineClass = nullptr; +jmethodID polylineConstructorId = nullptr; +jfieldID polylineAlphaId = nullptr; +jfieldID polylineVisibleId = nullptr; +jfieldID polylineColorId = nullptr; +jfieldID polylineWidthId = nullptr; +jfieldID polylinePointsId = nullptr; + +jclass polygonClass = nullptr; +jmethodID polygonConstructorId = nullptr; +jfieldID polygonAlphaId = nullptr; +jfieldID polygonVisibleId = nullptr; +jfieldID polygonFillColorId = nullptr; +jfieldID polygonStrokeColorId = nullptr; +jfieldID polygonStrokeWidthId = nullptr; +jfieldID polygonPointsId = nullptr; +jfieldID polygonHolesId = nullptr; + jclass runtimeExceptionClass = nullptr; jclass nullPointerExceptionClass = nullptr; @@ -64,7 +99,15 @@ jmethodID pointFConstructorId = nullptr; jfieldID pointFXId = nullptr; jfieldID pointFYId = nullptr; -bool throw_error(JNIEnv *env, const char *msg) { +jclass httpContextClass = nullptr; +jmethodID httpContextGetInstanceId = nullptr; +jmethodID httpContextCreateRequestId = nullptr; + +jclass httpRequestClass = nullptr; +jmethodID httpRequestStartId = nullptr; +jmethodID httpRequestCancelId = nullptr; + +bool throw_jni_error(JNIEnv *env, const char *msg) { if (env->ThrowNew(runtimeExceptionClass, msg) < 0) { env->ExceptionDescribe(); return false; @@ -73,6 +116,41 @@ bool throw_error(JNIEnv *env, const char *msg) { return true; } +bool attach_jni_thread(JavaVM* vm, JNIEnv** env, std::string threadName) { + JavaVMAttachArgs args = {JNI_VERSION_1_2, threadName.c_str(), NULL}; + + jint ret; + *env = nullptr; + bool detach = false; + ret = vm->GetEnv(reinterpret_cast(env), JNI_VERSION_1_6); + if (ret != JNI_OK) { + if (ret != JNI_EDETACHED) { + mbgl::Log::Error(mbgl::Event::JNI, "GetEnv() failed with %i", ret); + throw new std::runtime_error("GetEnv() failed"); + } else { + ret = vm->AttachCurrentThread(env, &args); + if (ret != JNI_OK) { + mbgl::Log::Error(mbgl::Event::JNI, "AttachCurrentThread() failed with %i", ret); + throw new std::runtime_error("AttachCurrentThread() failed"); + } + detach = true; + } + } + + return detach; +} + +void detach_jni_thread(JavaVM* vm, JNIEnv** env, bool detach) { + if (detach) { + jint ret; + if ((ret = vm->DetachCurrentThread()) != JNI_OK) { + mbgl::Log::Error(mbgl::Event::JNI, "DetachCurrentThread() failed with %i", ret); + throw new std::runtime_error("DetachCurrentThread() failed"); + } + } + *env = nullptr; +} + std::string std_string_from_jstring(JNIEnv *env, jstring jstr) { std::string str; @@ -129,21 +207,21 @@ std::vector std_vector_string_from_jobject(JNIEnv *env, jobject jli return vector; } - jobjectArray array = + jobjectArray jarray = reinterpret_cast(env->CallObjectMethod(jlist, listToArrayId)); - if (env->ExceptionCheck() || (array == nullptr)) { + if (env->ExceptionCheck() || (jarray == nullptr)) { env->ExceptionDescribe(); return vector; } - jsize len = env->GetArrayLength(array); + jsize len = env->GetArrayLength(jarray); if (len < 0) { env->ExceptionDescribe(); return vector; } for (jsize i = 0; i < len; i++) { - jstring jstr = reinterpret_cast(env->GetObjectArrayElement(array, i)); + jstring jstr = reinterpret_cast(env->GetObjectArrayElement(jarray, i)); if (jstr == nullptr) { env->ExceptionDescribe(); return vector; @@ -152,6 +230,9 @@ std::vector std_vector_string_from_jobject(JNIEnv *env, jobject jli vector.push_back(std_string_from_jstring(env, jstr)); } + env->DeleteLocalRef(jarray); + jarray = nullptr; + return vector; } @@ -172,6 +253,113 @@ jobject std_vector_string_to_jobject(JNIEnv *env, std::vector vecto return jlist; } + +jlongArray std_vector_uint_to_jobject(JNIEnv *env, std::vector vector) { + jlongArray jarray = env->NewLongArray(vector.size()); + if (jarray == nullptr) { + env->ExceptionDescribe(); + return nullptr; + } + + std::vector v; + for (const uint32_t& id : vector) { + v.push_back((jlong)id); + } + + env->SetLongArrayRegion(jarray, 0, v.size(), &(v[0])); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return nullptr; + } + + return jarray; +} + +mbgl::AnnotationSegment annotation_segment_from_latlng_jlist(JNIEnv *env, jobject jlist) { + mbgl::AnnotationSegment segment; + + if (jlist == nullptr) { + if (env->ThrowNew(nullPointerExceptionClass, "List cannot be null.") < 0) { + env->ExceptionDescribe(); + return segment; + } + return segment; + } + + jobjectArray jarray = + reinterpret_cast(env->CallObjectMethod(jlist, listToArrayId)); + if (env->ExceptionCheck() || (jarray == nullptr)) { + env->ExceptionDescribe(); + return segment; + } + + jsize len = env->GetArrayLength(jarray); + if (len < 0) { + env->ExceptionDescribe(); + return segment; + } + + segment.reserve(len); + + for (jsize i = 0; i < len; i++) { + jobject latLng = reinterpret_cast(env->GetObjectArrayElement(jarray, i)); + if (latLng == nullptr) { + env->ExceptionDescribe(); + return segment; + } + + jdouble latitude = env->GetDoubleField(latLng, latLngLatitudeId); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return segment; + } + + jdouble longitude = env->GetDoubleField(latLng, latLngLongitudeId); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return segment; + } + + segment.push_back(mbgl::LatLng(latitude, longitude)); + env->DeleteLocalRef(latLng); + } + + env->DeleteLocalRef(jarray); + jarray = nullptr; + + return segment; +} + +std::pair annotation_std_pair_from_polygon_jobject(JNIEnv *env, jobject polygon) { + jfloat alpha = env->GetFloatField(polygon, polygonAlphaId); + //jboolean visible = env->GetBooleanField(polygon, polygonVisibleId); + jint fillColor = env->GetIntField(polygon, polygonFillColorId); + jint strokeColor = env->GetIntField(polygon, polygonStrokeColorId); + + int rF = (fillColor >> 16) & 0xFF; + int gF = (fillColor >> 8) & 0xFF; + int bF = (fillColor) & 0xFF; + int aF = (fillColor >> 24) & 0xFF; + + int rS = (strokeColor >> 16) & 0xFF; + int gS = (strokeColor >> 8) & 0xFF; + int bS = (strokeColor) & 0xFF; + int aS = (strokeColor >> 24) & 0xFF; + + mbgl::StyleProperties shapeProperties; + mbgl::FillProperties fillProperties; + fillProperties.opacity = alpha; + fillProperties.stroke_color = {{ static_cast(rS) / 255.0f, static_cast(gS) / 255.0f, static_cast(bS) / 255.0f, static_cast(aS) / 255.0f }}; + fillProperties.fill_color = {{ static_cast(rF) / 255.0f, static_cast(gF) / 255.0f, static_cast(bF) / 255.0f, static_cast(aF) / 255.0f }}; + shapeProperties.set(fillProperties); + + jobject points = env->GetObjectField(polygon, polygonPointsId); + mbgl::AnnotationSegment segment = annotation_segment_from_latlng_jlist(env, points); + env->DeleteLocalRef(points); + + return std::make_pair(segment, shapeProperties); +} + } } @@ -179,13 +367,12 @@ namespace { using namespace mbgl::android; -jlong JNICALL -nativeCreate(JNIEnv *env, jobject obj, jstring cachePath_, jstring dataPath_, jstring apkPath_) { +jlong JNICALL nativeCreate(JNIEnv *env, jobject obj, jstring cachePath_, jstring dataPath_, jstring apkPath_, jfloat pixelRatio, jint availableProcessors, jlong totalMemory) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeCreate"); cachePath = std_string_from_jstring(env, cachePath_); dataPath = std_string_from_jstring(env, dataPath_); apkPath = std_string_from_jstring(env, apkPath_); - NativeMapView *nativeMapView = new NativeMapView(env, obj); + NativeMapView *nativeMapView = new NativeMapView(env, obj, pixelRatio, availableProcessors, totalMemory); jlong mapViewPtr = reinterpret_cast(nativeMapView); return mapViewPtr; } @@ -207,7 +394,7 @@ void JNICALL nativeInitializeDisplay(JNIEnv *env, jobject obj, jlong nativeMapVi { nativeMapView->initializeDisplay(); } catch(const std::exception& e) { - throw_error(env, "Unable to initialize GL display."); + throw_jni_error(env, "Unable to initialize GL display."); } } @@ -226,7 +413,7 @@ void JNICALL nativeInitializeContext(JNIEnv *env, jobject obj, jlong nativeMapVi try { nativeMapView->initializeContext(); } catch(const std::exception& e) { - throw_error(env, "Unable to initialize GL context."); + throw_jni_error(env, "Unable to initialize GL context."); } } @@ -237,8 +424,7 @@ void JNICALL nativeTerminateContext(JNIEnv *env, jobject obj, jlong nativeMapVie nativeMapView->terminateContext(); } -void JNICALL -nativeCreateSurface(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jobject surface) { +void JNICALL nativeCreateSurface(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jobject surface) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeCreateSurface"); assert(nativeMapViewPtr != 0); NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); @@ -246,7 +432,7 @@ nativeCreateSurface(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jobject su try { nativeMapView->createSurface(ANativeWindow_fromSurface(env, surface)); } catch(const std::exception& e) { - throw_error(env, "Unable to create GL surface."); + throw_jni_error(env, "Unable to create GL surface."); } } @@ -275,7 +461,7 @@ void JNICALL nativeUpdate(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeUpdate"); assert(nativeMapViewPtr != 0); NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); - nativeMapView->getMap().update(); + nativeMapView->getMap().update(mbgl::Update::Repaint); } void JNICALL nativeOnInvalidate(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { @@ -285,20 +471,26 @@ void JNICALL nativeOnInvalidate(JNIEnv *env, jobject obj, jlong nativeMapViewPtr nativeMapView->onInvalidate(); } -void JNICALL nativeResize(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jint width, jint height, - jfloat ratio, jint fbWidth, jint fbHeight) { - mbgl::Log::Debug(mbgl::Event::JNI, "nativeResize"); +void JNICALL nativeViewResize(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jint width, jint height) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeViewResize"); assert(nativeMapViewPtr != 0); assert(width >= 0); assert(height >= 0); assert(width <= UINT16_MAX); assert(height <= UINT16_MAX); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->resizeView(width, height); +} + +void JNICALL nativeFramebufferResize(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jint fbWidth, jint fbHeight) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeFramebufferResize"); + assert(nativeMapViewPtr != 0); assert(fbWidth >= 0); assert(fbHeight >= 0); assert(fbWidth <= UINT16_MAX); assert(fbHeight <= UINT16_MAX); NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); - nativeMapView->getMap().resize(width, height, ratio); + nativeMapView->resizeFramebuffer(fbWidth, fbHeight); } void JNICALL nativeRemoveClass(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jstring clazz) { @@ -322,8 +514,7 @@ void JNICALL nativeAddClass(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, js nativeMapView->getMap().addClass(std_string_from_jstring(env, clazz)); } -void JNICALL -nativeSetClasses(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jobject classes) { +void JNICALL nativeSetClasses(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jobject classes) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetClasses"); assert(nativeMapViewPtr != 0); NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); @@ -376,8 +567,7 @@ jstring JNICALL nativeGetStyleJSON(JNIEnv *env, jobject obj, jlong nativeMapView return std_string_to_jstring(env, nativeMapView->getMap().getStyleJSON()); } -void JNICALL -nativeSetAccessToken(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jstring accessToken) { +void JNICALL nativeSetAccessToken(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jstring accessToken) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetAccessToken"); assert(nativeMapViewPtr != 0); NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); @@ -398,6 +588,13 @@ void JNICALL nativeCancelTransitions(JNIEnv *env, jobject obj, jlong nativeMapVi nativeMapView->getMap().cancelTransitions(); } +void JNICALL nativeSetGestureInProgress(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jboolean inProgress) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetGestureInProgress"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().setGestureInProgress(inProgress); +} + void JNICALL nativeMoveBy(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jdouble dx, jdouble dy, jlong duration) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeMoveBy"); @@ -472,8 +669,7 @@ jdouble JNICALL nativeGetScale(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) return nativeMapView->getMap().getScale(); } -void JNICALL -nativeSetZoom(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jdouble zoom, jlong duration) { +void JNICALL nativeSetZoom(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jdouble zoom, jlong duration) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetZoom"); assert(nativeMapViewPtr != 0); NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); @@ -589,6 +785,270 @@ void JNICALL nativeResetNorth(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) nativeMapView->getMap().resetNorth(); } +jlong JNICALL nativeAddMarker(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jobject marker) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeAddMarker"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + + jobject position = env->GetObjectField(marker, markerPositionId); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return -1; + } + + jstring jsprite = reinterpret_cast(env->GetObjectField(marker, markerSpriteId)); + std::string sprite = std_string_from_jstring(env, jsprite); + + jdouble latitude = env->GetDoubleField(position, latLngLatitudeId); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return -1; + } + + jdouble longitude = env->GetDoubleField(position, latLngLongitudeId); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return -1; + } + + // Because Java only has int, not unsigned int, we need to bump the annotation id up to a long. + return nativeMapView->getMap().addPointAnnotation(mbgl::PointAnnotation(mbgl::LatLng(latitude, longitude), sprite)); +} + +jlong JNICALL nativeAddPolyline(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jobject polyline) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeAddPolyline"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + + jfloat alpha = env->GetFloatField(polyline, polylineAlphaId); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return -1; + } + + /*jboolean visible = env->GetBooleanField(polyline, polylineVisibleId); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return -1; + }*/ + + jint color = env->GetIntField(polyline, polylineColorId); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return -1; + } + + int r = (color >> 16) & 0xFF; + int g = (color >> 8) & 0xFF; + int b = (color) & 0xFF; + int a = (color >> 24) & 0xFF; + + jfloat width = env->GetFloatField(polyline, polylineWidthId); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return -1; + } + + mbgl::StyleProperties shapeProperties; + mbgl::LineProperties lineProperties; + lineProperties.opacity = alpha; + lineProperties.color = {{ static_cast(r) / 255.0f, static_cast(g) / 255.0f, static_cast(b) / 255.0f, static_cast(a) / 255.0f }}; + lineProperties.width = width; + shapeProperties.set(lineProperties); + + jobject points = env->GetObjectField(polyline, polylinePointsId); + mbgl::AnnotationSegment segment = annotation_segment_from_latlng_jlist(env, points); + + std::vector shapes; + shapes.emplace_back(mbgl::AnnotationSegments { segment }, shapeProperties); + + std::vector shapeAnnotationIDs = nativeMapView->getMap().addShapeAnnotations(shapes); + uint32_t id = shapeAnnotationIDs.at(0); + + return id; +} + +jlong JNICALL nativeAddPolygon(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jobject polygon) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeAddPolygon"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + + std::vector shapes; + std::pair segment = annotation_std_pair_from_polygon_jobject(env, polygon); + + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return -1; + } + + shapes.emplace_back(mbgl::AnnotationSegments { segment.first }, segment.second); + + std::vector shapeAnnotationIDs = nativeMapView->getMap().addShapeAnnotations(shapes); + uint32_t id = shapeAnnotationIDs.at(0); + return id; +} + +jlongArray JNICALL nativeAddPolygons(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jobject jlist) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeAddPolygons"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + + std::vector shapes; + + if (jlist == nullptr) { + if (env->ThrowNew(nullPointerExceptionClass, "List cannot be null.") < 0) { + env->ExceptionDescribe(); + return nullptr; + } + return nullptr; + } + + jobjectArray jarray = + reinterpret_cast(env->CallObjectMethod(jlist, listToArrayId)); + if (env->ExceptionCheck() || (jarray == nullptr)) { + env->ExceptionDescribe(); + return nullptr; + } + + jsize len = env->GetArrayLength(jarray); + if (len < 0) { + env->ExceptionDescribe(); + return nullptr; + } + + shapes.reserve(len); + + for (jsize i = 0; i < len; i++) { + jobject polygon = reinterpret_cast(env->GetObjectArrayElement(jarray, i)); + + std::pair segment = annotation_std_pair_from_polygon_jobject(env, polygon); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return nullptr; + } + + shapes.emplace_back(mbgl::AnnotationSegments { segment.first }, segment.second); + + env->DeleteLocalRef(polygon); + } + + env->DeleteLocalRef(jarray); + + std::vector shapeAnnotationIDs = nativeMapView->getMap().addShapeAnnotations(shapes); + return std_vector_uint_to_jobject(env, shapeAnnotationIDs); +} + +void JNICALL nativeRemoveAnnotation(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jlong annotationId) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeRemoveAnnotation"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().removeAnnotation(static_cast(annotationId)); +} + +void JNICALL nativeRemoveAnnotations(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jlongArray jarray) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeRemoveAnnotations"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + + std::vector ids; + + if (env->ExceptionCheck() || (jarray == nullptr)) { + env->ExceptionDescribe(); + return; + } + + jsize len = env->GetArrayLength(jarray); + if (len < 0) { + env->ExceptionDescribe(); + return; + } + + ids.reserve(len); + jlong* jids = env->GetLongArrayElements(jarray, nullptr); + + for (jsize i = 0; i < len; i++) { + if(jids[i] == -1L) + continue; + ids.push_back(static_cast(jids[i])); + } + + env->ReleaseLongArrayElements(jarray, jids, JNI_ABORT); + + nativeMapView->getMap().removeAnnotations(ids); +} + +jlongArray JNICALL nativeGetAnnotationsInBounds(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jobject bbox) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetAnnotationsInBounds"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + + if (env->ExceptionCheck() || (bbox == nullptr)) { + env->ExceptionDescribe(); + return nullptr; + } + + jdouble swLat = env->GetDoubleField(bbox, bboxLatSouthId); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return nullptr; + } + + jdouble swLon = env->GetDoubleField(bbox, bboxLonWestId); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return nullptr; + } + + jdouble neLat = env->GetDoubleField(bbox, bboxLatNorthId); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return nullptr; + } + + jdouble neLon = env->GetDoubleField(bbox, bboxLonEastId); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return nullptr; + } + + mbgl::LatLngBounds bounds; + bounds.sw = { swLat, swLon }; + bounds.ne = { neLat, neLon }; + + // assume only points for now + std::vector annotations = nativeMapView->getMap().getAnnotationsInBounds(bounds, mbgl::AnnotationType::Point); + + return std_vector_uint_to_jobject(env, annotations); +} + +void JNICALL nativeSetSprite(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, + jstring symbol, jint width, jint height, jfloat scale, jbyteArray jpixels) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetSprite"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + + const std::string symbolName = std_string_from_jstring(env, symbol); + + jbyte* pixelData = env->GetByteArrayElements(jpixels, nullptr); + std::string pixels(reinterpret_cast(pixelData), width * height * 4); + env->ReleaseByteArrayElements(jpixels, pixelData, JNI_ABORT); + + auto spriteImage = std::make_shared( + uint16_t(width), + uint16_t(height), + float(scale), + std::move(pixels)); + + nativeMapView->getMap().setSprite(symbolName, spriteImage); +} + +void JNICALL nativeOnLowMemory(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeOnLowMemory"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().onLowMemory(); +} + void JNICALL nativeSetDebug(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jboolean debug) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetDebug"); assert(nativeMapViewPtr != 0); @@ -612,8 +1072,35 @@ jboolean JNICALL nativeGetDebug(JNIEnv *env, jobject obj, jlong nativeMapViewPtr return nativeMapView->getMap().getDebug(); } -void JNICALL -nativeSetReachability(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jboolean status) { +void JNICALL nativeSetCollisionDebug(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jboolean debug) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetCollisionDebug"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().setCollisionDebug(debug); +} + +void JNICALL nativeToggleCollisionDebug(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeToggleCollisionDebug"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().toggleCollisionDebug(); +} + +jboolean JNICALL nativeGetCollisionDebug(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetCollisionDebug"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + return nativeMapView->getMap().getCollisionDebug(); +} + +jboolean JNICALL nativeIsFullyLoaded(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeIsFullyLoaded"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + return nativeMapView->getMap().isFullyLoaded(); +} + +void JNICALL nativeSetReachability(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jboolean status) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetReachability"); assert(nativeMapViewPtr != 0); if (status) { @@ -717,13 +1204,13 @@ jobject JNICALL nativeLatLngForPixel(JNIEnv *env, jobject obj, jlong nativeMapVi assert(nativeMapViewPtr != 0); NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); - jfloat x = env->GetDoubleField(pixel, pointFXId); + jfloat x = env->GetFloatField(pixel, pointFXId); if (env->ExceptionCheck()) { env->ExceptionDescribe(); return nullptr; } - jfloat y = env->GetDoubleField(pixel, pointFYId); + jfloat y = env->GetFloatField(pixel, pointFYId); if (env->ExceptionCheck()) { env->ExceptionDescribe(); return nullptr; @@ -739,6 +1226,15 @@ jobject JNICALL nativeLatLngForPixel(JNIEnv *env, jobject obj, jlong nativeMapVi return ret; } + +jdouble JNICALL nativeGetTopOffsetPixelsForAnnotationSymbol(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jstring symbolName) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetTopOffsetPixelsForAnnotationSymbol"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + return nativeMapView->getMap().getTopOffsetPixelsForAnnotationSymbol(std_string_from_jstring(env, symbolName)); +} + + } extern "C" { @@ -746,6 +1242,8 @@ extern "C" { extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { mbgl::Log::Debug(mbgl::Event::JNI, "JNI_OnLoad"); + theJVM = vm; + JNIEnv *env = nullptr; jint ret = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); if (ret != JNI_OK) { @@ -807,6 +1305,162 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { return JNI_ERR; } + bboxClass = env->FindClass("com/mapbox/mapboxgl/geometry/BoundingBox"); + if (bboxClass == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + bboxConstructorId = env->GetMethodID(bboxClass, "", "(DDDD)V"); + if (bboxConstructorId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + bboxLatNorthId = env->GetFieldID(bboxClass, "mLatNorth", "D"); + if (bboxLatNorthId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + bboxLatSouthId = env->GetFieldID(bboxClass, "mLatSouth", "D"); + if (bboxLatSouthId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + bboxLonEastId = env->GetFieldID(bboxClass, "mLonEast", "D"); + if (bboxLonEastId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + bboxLonWestId = env->GetFieldID(bboxClass, "mLonWest", "D"); + if (bboxLonWestId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + markerClass = env->FindClass("com/mapbox/mapboxgl/annotations/Marker"); + if (markerClass == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + markerConstructorId = env->GetMethodID(markerClass, "", "()V"); + if (markerConstructorId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + markerPositionId = env->GetFieldID(markerClass, "position", "Lcom/mapbox/mapboxgl/geometry/LatLng;"); + if (markerPositionId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + markerSpriteId = env->GetFieldID(markerClass, "sprite", "Ljava/lang/String;"); + if (markerSpriteId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + polylineClass = env->FindClass("com/mapbox/mapboxgl/annotations/Polyline"); + if (polylineClass == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + polylineConstructorId = env->GetMethodID(polylineClass, "", "()V"); + if (polylineConstructorId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + polylineAlphaId = env->GetFieldID(polylineClass, "alpha", "F"); + if (polylineAlphaId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + polylineVisibleId = env->GetFieldID(polylineClass, "visible", "Z"); + if (polylineVisibleId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + polylineColorId = env->GetFieldID(polylineClass, "color", "I"); + if (polylineColorId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + polylineWidthId = env->GetFieldID(polylineClass, "width", "F"); + if (polylineWidthId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + polylinePointsId = env->GetFieldID(polylineClass, "points", "Ljava/util/List;"); + if (polylineWidthId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + polygonClass = env->FindClass("com/mapbox/mapboxgl/annotations/Polygon"); + if (polygonClass == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + polygonConstructorId = env->GetMethodID(polygonClass, "", "()V"); + if (polygonConstructorId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + polygonAlphaId = env->GetFieldID(polygonClass, "alpha", "F"); + if (polygonAlphaId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + polygonVisibleId = env->GetFieldID(polygonClass, "visible", "Z"); + if (polygonVisibleId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + polygonFillColorId = env->GetFieldID(polygonClass, "fillColor", "I"); + if (polygonFillColorId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + polygonStrokeColorId = env->GetFieldID(polygonClass, "strokeColor", "I"); + if (polygonStrokeColorId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + polygonStrokeWidthId = env->GetFieldID(polygonClass, "strokeWidth", "F"); + if (polygonStrokeWidthId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + polygonPointsId = env->GetFieldID(polygonClass, "points", "Ljava/util/List;"); + if (polygonPointsId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + polygonHolesId = env->GetFieldID(polygonClass, "holes", "Ljava/util/List;"); + if (polygonHolesId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + jclass nativeMapViewClass = env->FindClass("com/mapbox/mapboxgl/views/NativeMapView"); if (nativeMapViewClass == nullptr) { env->ExceptionDescribe(); @@ -819,7 +1473,7 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { return JNI_ERR; } - onMapChangedId = env->GetMethodID(nativeMapViewClass, "onMapChanged", "()V"); + onMapChangedId = env->GetMethodID(nativeMapViewClass, "onMapChanged", "(I)V"); if (onMapChangedId == nullptr) { env->ExceptionDescribe(); return JNI_ERR; @@ -921,8 +1575,38 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { return JNI_ERR; } + httpContextClass = env->FindClass("com/mapbox/mapboxgl/http/HTTPContext"); + if (httpContextClass == nullptr) { + env->ExceptionDescribe(); + } + + httpContextGetInstanceId = env->GetStaticMethodID(httpContextClass, "getInstance", "()Lcom/mapbox/mapboxgl/http/HTTPContext;"); + if (httpContextGetInstanceId == nullptr) { + env->ExceptionDescribe(); + } + + httpContextCreateRequestId = env->GetMethodID(httpContextClass, "createRequest", "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/mapbox/mapboxgl/http/HTTPContext$HTTPRequest;"); + if (httpContextCreateRequestId == nullptr) { + env->ExceptionDescribe(); + } + + httpRequestClass = env->FindClass("com/mapbox/mapboxgl/http/HTTPContext$HTTPRequest"); + if (httpRequestClass == nullptr) { + env->ExceptionDescribe(); + } + + httpRequestStartId = env->GetMethodID(httpRequestClass, "start", "()V"); + if (httpRequestStartId == nullptr) { + env->ExceptionDescribe(); + } + + httpRequestCancelId = env->GetMethodID(httpRequestClass, "cancel", "()V"); + if (httpRequestCancelId == nullptr) { + env->ExceptionDescribe(); + } + const std::vector methods = { - {"nativeCreate", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)J", + {"nativeCreate", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;FIJ)J", reinterpret_cast(&nativeCreate)}, {"nativeDestroy", "(J)V", reinterpret_cast(&nativeDestroy)}, {"nativeInitializeDisplay", "(J)V", reinterpret_cast(&nativeInitializeDisplay)}, @@ -936,9 +1620,12 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { {"nativeResume", "(J)V", reinterpret_cast(&nativeResume)}, {"nativeUpdate", "(J)V", reinterpret_cast(&nativeUpdate)}, {"nativeOnInvalidate", "(J)V", reinterpret_cast(&nativeOnInvalidate)}, - {"nativeResize", "(JIIFII)V", + {"nativeViewResize", "(JII)V", reinterpret_cast(static_cast(&nativeResize))}, + *)(JNIEnv *, jobject, jlong, jint, jint)>(&nativeViewResize))}, + {"nativeFramebufferResize", "(JII)V", + reinterpret_cast(static_cast(&nativeFramebufferResize))}, {"nativeAddClass", "(JLjava/lang/String;)V", reinterpret_cast(&nativeAddClass)}, {"nativeRemoveClass", "(JLjava/lang/String;)V", @@ -964,6 +1651,7 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { {"nativeGetAccessToken", "(J)Ljava/lang/String;", reinterpret_cast(&nativeGetAccessToken)}, {"nativeCancelTransitions", "(J)V", reinterpret_cast(&nativeCancelTransitions)}, + {"nativeSetGestureInProgress", "(JZ)V", reinterpret_cast(&nativeSetGestureInProgress)}, {"nativeMoveBy", "(JDDJ)V", reinterpret_cast(&nativeMoveBy)}, {"nativeSetLatLng", "(JLcom/mapbox/mapboxgl/geometry/LatLng;J)V", reinterpret_cast(&nativeSetLatLng)}, @@ -993,17 +1681,43 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { &nativeSetBearing))}, {"nativeGetBearing", "(J)D", reinterpret_cast(&nativeGetBearing)}, {"nativeResetNorth", "(J)V", reinterpret_cast(&nativeResetNorth)}, + {"nativeAddMarker", "(JLcom/mapbox/mapboxgl/annotations/Marker;)J", + reinterpret_cast(&nativeAddMarker)}, + {"nativeAddPolyline", "(JLcom/mapbox/mapboxgl/annotations/Polyline;)J", + reinterpret_cast(&nativeAddPolyline)}, + {"nativeAddPolygon", "(JLcom/mapbox/mapboxgl/annotations/Polygon;)J", + reinterpret_cast(&nativeAddPolygon)}, + {"nativeAddPolygons", "(JLjava/util/List;)[J", + reinterpret_cast(&nativeAddPolygons)}, + {"nativeRemoveAnnotation", "(JJ)V", reinterpret_cast(&nativeRemoveAnnotation)}, + {"nativeRemoveAnnotations", "(J[J)V", reinterpret_cast(&nativeRemoveAnnotations)}, + {"nativeGetAnnotationsInBounds", "(JLcom/mapbox/mapboxgl/geometry/BoundingBox;)[J", + reinterpret_cast(&nativeGetAnnotationsInBounds)}, + {"nativeSetSprite", "(JLjava/lang/String;IIF[B)V", reinterpret_cast(&nativeSetSprite)}, + {"nativeOnLowMemory", "(J)V", reinterpret_cast(&nativeOnLowMemory)}, {"nativeSetDebug", "(JZ)V", reinterpret_cast(&nativeSetDebug)}, {"nativeToggleDebug", "(J)V", reinterpret_cast(&nativeToggleDebug)}, {"nativeGetDebug", "(J)Z", reinterpret_cast(&nativeGetDebug)}, + {"nativeSetCollisionDebug", "(JZ)V", reinterpret_cast(&nativeSetCollisionDebug)}, + {"nativeToggleCollisionDebug", "(J)V", reinterpret_cast(&nativeToggleCollisionDebug)}, + {"nativeGetCollisionDebug", "(J)Z", reinterpret_cast(&nativeGetCollisionDebug)}, + {"nativeIsFullyLoaded", "(J)Z", reinterpret_cast(&nativeIsFullyLoaded)}, {"nativeSetReachability", "(JZ)V", reinterpret_cast(&nativeSetReachability)}, //{"nativeGetWorldBoundsMeters", "(J)V", reinterpret_cast(&nativeGetWorldBoundsMeters)}, //{"nativeGetWorldBoundsLatLng", "(J)V", reinterpret_cast(&nativeGetWorldBoundsLatLng)}, {"nativeGetMetersPerPixelAtLatitude", "(JDD)D", reinterpret_cast(&nativeGetMetersPerPixelAtLatitude)}, - {"nativeProjectedMetersForLatLng", "(JLcom/mapbox/mapboxgl/geometry/LatLng;)Lcom/mapbox/mapboxgl/geometry/ProjectedMeters;", reinterpret_cast(&nativeProjectedMetersForLatLng)}, - {"nativeLatLngForProjectedMeters", "(JLcom/mapbox/mapboxgl/geometry/ProjectedMeters;)Lcom/mapbox/mapboxgl/geometry/LatLng;", reinterpret_cast(&nativeLatLngForProjectedMeters)}, - {"nativePixelForLatLng", "(JLcom/mapbox/mapboxgl/geometry/LatLng;)Landroid/graphics/PointF;", reinterpret_cast(&nativePixelForLatLng)}, - {"nativeLatLngForPixel", "(JLandroid/graphics/PointF;)Lcom/mapbox/mapboxgl/geometry/LatLng;", reinterpret_cast(&nativeLatLngForPixel)}, + {"nativeProjectedMetersForLatLng", + "(JLcom/mapbox/mapboxgl/geometry/LatLng;)Lcom/mapbox/mapboxgl/geometry/ProjectedMeters;", + reinterpret_cast(&nativeProjectedMetersForLatLng)}, + {"nativeLatLngForProjectedMeters", + "(JLcom/mapbox/mapboxgl/geometry/ProjectedMeters;)Lcom/mapbox/mapboxgl/geometry/LatLng;", + reinterpret_cast(&nativeLatLngForProjectedMeters)}, + {"nativePixelForLatLng", "(JLcom/mapbox/mapboxgl/geometry/LatLng;)Landroid/graphics/PointF;", + reinterpret_cast(&nativePixelForLatLng)}, + {"nativeLatLngForPixel", "(JLandroid/graphics/PointF;)Lcom/mapbox/mapboxgl/geometry/LatLng;", + reinterpret_cast(&nativeLatLngForPixel)}, + {"nativeGetTopOffsetPixelsForAnnotationSymbol", "(JLjava/lang/String;)D", + reinterpret_cast(&nativeGetTopOffsetPixelsForAnnotationSymbol)}, }; if (env->RegisterNatives(nativeMapViewClass, methods.data(), methods.size()) < 0) { @@ -1024,11 +1738,54 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { return JNI_ERR; } + bboxClass = reinterpret_cast(env->NewGlobalRef(bboxClass)); + if (bboxClass == nullptr) { + env->ExceptionDescribe(); + env->DeleteGlobalRef(latLngClass); + env->DeleteGlobalRef(latLngZoomClass); + env->DeleteGlobalRef(bboxClass); + return JNI_ERR; + } + + markerClass = reinterpret_cast(env->NewGlobalRef(markerClass)); + if (markerClass == nullptr) { + env->ExceptionDescribe(); + env->DeleteGlobalRef(latLngClass); + env->DeleteGlobalRef(latLngZoomClass); + env->DeleteGlobalRef(bboxClass); + return JNI_ERR; + } + + polylineClass = reinterpret_cast(env->NewGlobalRef(polylineClass)); + if (polylineClass == nullptr) { + env->ExceptionDescribe(); + env->DeleteGlobalRef(latLngClass); + env->DeleteGlobalRef(latLngZoomClass); + env->DeleteGlobalRef(bboxClass); + env->DeleteGlobalRef(markerClass); + return JNI_ERR; + } + + polygonClass = reinterpret_cast(env->NewGlobalRef(polygonClass)); + if (polygonClass == nullptr) { + env->ExceptionDescribe(); + env->DeleteGlobalRef(latLngClass); + env->DeleteGlobalRef(latLngZoomClass); + env->DeleteGlobalRef(bboxClass); + env->DeleteGlobalRef(markerClass); + env->DeleteGlobalRef(polylineClass); + return JNI_ERR; + } + runtimeExceptionClass = reinterpret_cast(env->NewGlobalRef(runtimeExceptionClass)); if (runtimeExceptionClass == nullptr) { env->ExceptionDescribe(); env->DeleteGlobalRef(latLngClass); env->DeleteGlobalRef(latLngZoomClass); + env->DeleteGlobalRef(bboxClass); + env->DeleteGlobalRef(markerClass); + env->DeleteGlobalRef(polylineClass); + env->DeleteGlobalRef(polygonClass); return JNI_ERR; } @@ -1038,6 +1795,10 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { env->ExceptionDescribe(); env->DeleteGlobalRef(latLngClass); env->DeleteGlobalRef(latLngZoomClass); + env->DeleteGlobalRef(bboxClass); + env->DeleteGlobalRef(markerClass); + env->DeleteGlobalRef(polylineClass); + env->DeleteGlobalRef(polygonClass); env->DeleteGlobalRef(runtimeExceptionClass); return JNI_ERR; } @@ -1047,6 +1808,10 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { env->ExceptionDescribe(); env->DeleteGlobalRef(latLngClass); env->DeleteGlobalRef(latLngZoomClass); + env->DeleteGlobalRef(bboxClass); + env->DeleteGlobalRef(markerClass); + env->DeleteGlobalRef(polylineClass); + env->DeleteGlobalRef(polygonClass); env->DeleteGlobalRef(runtimeExceptionClass); env->DeleteGlobalRef(nullPointerExceptionClass); return JNI_ERR; @@ -1057,18 +1822,25 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { env->ExceptionDescribe(); env->DeleteGlobalRef(latLngClass); env->DeleteGlobalRef(latLngZoomClass); + env->DeleteGlobalRef(bboxClass); + env->DeleteGlobalRef(markerClass); + env->DeleteGlobalRef(polylineClass); + env->DeleteGlobalRef(polygonClass); env->DeleteGlobalRef(runtimeExceptionClass); env->DeleteGlobalRef(nullPointerExceptionClass); env->DeleteGlobalRef(arrayListClass); return JNI_ERR; } - pointFClass = reinterpret_cast(env->NewGlobalRef(pointFClass)); if (pointFClass == nullptr) { env->ExceptionDescribe(); env->DeleteGlobalRef(latLngClass); + env->DeleteGlobalRef(markerClass); env->DeleteGlobalRef(latLngZoomClass); + env->DeleteGlobalRef(bboxClass); + env->DeleteGlobalRef(polylineClass); + env->DeleteGlobalRef(polygonClass); env->DeleteGlobalRef(runtimeExceptionClass); env->DeleteGlobalRef(nullPointerExceptionClass); env->DeleteGlobalRef(arrayListClass); @@ -1076,6 +1848,38 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { return JNI_ERR; } + httpContextClass = reinterpret_cast(env->NewGlobalRef(httpContextClass)); + if (httpContextClass == nullptr) { + env->ExceptionDescribe(); + env->DeleteGlobalRef(latLngClass); + env->DeleteGlobalRef(markerClass); + env->DeleteGlobalRef(latLngZoomClass); + env->DeleteGlobalRef(bboxClass); + env->DeleteGlobalRef(polylineClass); + env->DeleteGlobalRef(polygonClass); + env->DeleteGlobalRef(runtimeExceptionClass); + env->DeleteGlobalRef(nullPointerExceptionClass); + env->DeleteGlobalRef(arrayListClass); + env->DeleteGlobalRef(projectedMetersClass); + env->DeleteGlobalRef(pointFClass); + } + + httpRequestClass = reinterpret_cast(env->NewGlobalRef(httpRequestClass)); + if (httpRequestClass == nullptr) { + env->ExceptionDescribe(); + env->DeleteGlobalRef(latLngClass); + env->DeleteGlobalRef(markerClass); + env->DeleteGlobalRef(latLngZoomClass); + env->DeleteGlobalRef(bboxClass); + env->DeleteGlobalRef(polylineClass); + env->DeleteGlobalRef(polygonClass); + env->DeleteGlobalRef(runtimeExceptionClass); + env->DeleteGlobalRef(nullPointerExceptionClass); + env->DeleteGlobalRef(arrayListClass); + env->DeleteGlobalRef(projectedMetersClass); + env->DeleteGlobalRef(pointFClass); + env->DeleteGlobalRef(httpContextClass); + } char release[PROP_VALUE_MAX] = ""; __system_property_get("ro.build.version.release", release); @@ -1087,6 +1891,8 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { extern "C" JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) { mbgl::Log::Debug(mbgl::Event::JNI, "JNI_OnUnload"); + theJVM = vm; + JNIEnv *env = nullptr; jint ret = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); if (ret != JNI_OK) { @@ -1107,6 +1913,40 @@ extern "C" JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) { latLngZoomLatitudeId = nullptr; latLngZoomZoomId = nullptr; + env->DeleteGlobalRef(bboxClass); + bboxClass = nullptr; + bboxConstructorId = nullptr; + bboxLatNorthId = nullptr; + bboxLatSouthId = nullptr; + bboxLonEastId = nullptr; + bboxLonWestId = nullptr; + + env->DeleteGlobalRef(markerClass); + markerClass = nullptr; + markerConstructorId = nullptr; + markerPositionId = nullptr; + markerSpriteId = nullptr; + + env->DeleteGlobalRef(polylineClass); + polylineClass = nullptr; + polylineConstructorId = nullptr; + polylineAlphaId = nullptr; + polylineVisibleId = nullptr; + polylineColorId = nullptr; + polylineWidthId = nullptr; + polylinePointsId = nullptr; + + env->DeleteGlobalRef(polygonClass); + polygonClass = nullptr; + polygonConstructorId = nullptr; + polygonAlphaId = nullptr; + polygonVisibleId = nullptr; + polygonFillColorId = nullptr; + polygonStrokeColorId = nullptr; + polygonStrokeWidthId = nullptr; + polygonPointsId = nullptr; + polygonHolesId = nullptr; + onInvalidateId = nullptr; onMapChangedId = nullptr; onFpsChangedId = nullptr; @@ -1135,5 +1975,15 @@ extern "C" JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) { pointFConstructorId = nullptr; pointFXId = nullptr; pointFYId = nullptr; + + env->DeleteGlobalRef(httpContextClass); + httpContextGetInstanceId = nullptr; + httpContextCreateRequestId = nullptr; + + env->DeleteGlobalRef(httpRequestClass); + httpRequestStartId = nullptr; + httpRequestCancelId = nullptr; + + theJVM = nullptr; } } diff --git a/android/cpp/native_map_view.cpp b/android/cpp/native_map_view.cpp index 983797767..f38513b32 100644 --- a/android/cpp/native_map_view.cpp +++ b/android/cpp/native_map_view.cpp @@ -15,6 +15,7 @@ #include #include #include +#include namespace mbgl { namespace android { @@ -52,8 +53,11 @@ void log_gl_string(GLenum name, const char *label) { } } -NativeMapView::NativeMapView(JNIEnv *env, jobject obj_) +NativeMapView::NativeMapView(JNIEnv *env, jobject obj_, float pixelRatio_, int availableProcessors_, size_t totalMemory_) : mbgl::View(*this), + pixelRatio(pixelRatio_), + availableProcessors(availableProcessors_), + totalMemory(totalMemory_), fileCache(mbgl::android::cachePath + "/mbgl-cache.db"), fileSource(&fileCache), map(*this, fileSource, MapMode::Continuous) { @@ -98,6 +102,18 @@ NativeMapView::~NativeMapView() { vm = nullptr; } +float NativeMapView::getPixelRatio() const { + return pixelRatio; +} + +std::array NativeMapView::getSize() const { + return {{ static_cast(width), static_cast(height) }}; +} + +std::array NativeMapView::getFramebufferSize() const { + return {{ static_cast(fbWidth), static_cast(fbHeight) }}; +} + void NativeMapView::activate() { mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::activate"); if ((display != EGL_NO_DISPLAY) && (surface != EGL_NO_SURFACE) && (context != EGL_NO_CONTEXT)) { @@ -132,41 +148,22 @@ void NativeMapView::invalidate() { assert(vm != nullptr); assert(obj != nullptr); - JavaVMAttachArgs args = {JNI_VERSION_1_2, "NativeMapView::invalidate()", NULL}; - - jint ret; JNIEnv *env = nullptr; - bool detach = false; - ret = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); - if (ret != JNI_OK) { - if (ret != JNI_EDETACHED) { - mbgl::Log::Error(mbgl::Event::JNI, "GetEnv() failed with %i", ret); - throw new std::runtime_error("GetEnv() failed"); - } else { - ret = vm->AttachCurrentThread(&env, &args); - if (ret != JNI_OK) { - mbgl::Log::Error(mbgl::Event::JNI, "AttachCurrentThread() failed with %i", ret); - throw new std::runtime_error("AttachCurrentThread() failed"); - } - detach = true; - } - } + bool detach = attach_jni_thread(vm, &env, "NativeMapView::invalidate()"); env->CallVoidMethod(obj, onInvalidateId); if (env->ExceptionCheck()) { env->ExceptionDescribe(); } - if (detach) { - if ((ret = vm->DetachCurrentThread()) != JNI_OK) { - mbgl::Log::Error(mbgl::Event::JNI, "DetachCurrentThread() failed with %i", ret); - throw new std::runtime_error("DetachCurrentThread() failed"); - } - } - env = nullptr; + detach_jni_thread(vm, &env, detach); } -void NativeMapView::swap() { +void NativeMapView::beforeRender() { + // no-op +} + +void NativeMapView::afterRender() { mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::swap"); if ((display != EGL_NO_DISPLAY) && (surface != EGL_NO_SURFACE)) { @@ -640,44 +637,21 @@ void NativeMapView::resume() { } } -void NativeMapView::notifyMapChange(mbgl::MapChange) { +void NativeMapView::notifyMapChange(mbgl::MapChange change) { mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::notifyMapChange()"); assert(vm != nullptr); assert(obj != nullptr); - JavaVMAttachArgs args = {JNI_VERSION_1_2, "NativeMapView::notifyMapChange()", NULL}; - - jint ret; JNIEnv *env = nullptr; - bool detach = false; - ret = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); - if (ret != JNI_OK) { - if (ret != JNI_EDETACHED) { - mbgl::Log::Error(mbgl::Event::JNI, "GetEnv() failed with %i", ret); - throw new std::runtime_error("GetEnv() failed"); - } else { - ret = vm->AttachCurrentThread(&env, &args); - if (ret != JNI_OK) { - mbgl::Log::Error(mbgl::Event::JNI, "AttachCurrentThread() failed with %i", ret); - throw new std::runtime_error("AttachCurrentThread() failed"); - } - detach = true; - } - } + bool detach = attach_jni_thread(vm, &env, "NativeMapView::notifyMapChange()"); - env->CallVoidMethod(obj, onMapChangedId); + env->CallVoidMethod(obj, onMapChangedId, change); if (env->ExceptionCheck()) { env->ExceptionDescribe(); } - if (detach) { - if ((ret = vm->DetachCurrentThread()) != JNI_OK) { - mbgl::Log::Error(mbgl::Event::JNI, "DetachCurrentThread() failed with %i", ret); - throw new std::runtime_error("DetachCurrentThread() failed"); - } - } - env = nullptr; + detach_jni_thread(vm, &env, detach); } void NativeMapView::enableFps(bool enable) { @@ -711,38 +685,15 @@ void NativeMapView::updateFps() { assert(vm != nullptr); assert(obj != nullptr); - JavaVMAttachArgs args = {JNI_VERSION_1_2, "NativeMapView::updateFps()", NULL}; - - jint ret; JNIEnv *env = nullptr; - bool detach = false; - ret = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); - if (ret != JNI_OK) { - if (ret != JNI_EDETACHED) { - mbgl::Log::Error(mbgl::Event::JNI, "GetEnv() failed with %i", ret); - throw new std::runtime_error("GetEnv() failed"); - } else { - ret = vm->AttachCurrentThread(&env, &args); - if (ret != JNI_OK) { - mbgl::Log::Error(mbgl::Event::JNI, "AttachCurrentThread() failed with %i", ret); - throw new std::runtime_error("AttachCurrentThread() failed"); - } - detach = true; - } - } + bool detach = attach_jni_thread(vm, &env, "NativeMapView::updateFps()"); env->CallVoidMethod(obj, onFpsChangedId, fps); if (env->ExceptionCheck()) { env->ExceptionDescribe(); } - if (detach) { - if ((ret = vm->DetachCurrentThread()) != JNI_OK) { - mbgl::Log::Error(mbgl::Event::JNI, "DetachCurrentThread() failed with %i", ret); - throw new std::runtime_error("DetachCurrentThread() failed"); - } - } - env = nullptr; + detach_jni_thread(vm, &env, detach); } void NativeMapView::onInvalidate() { @@ -750,9 +701,31 @@ void NativeMapView::onInvalidate() { const bool dirty = !clean.test_and_set(); if (dirty) { + float zoomFactor = map.getMaxZoom() - map.getMinZoom() + 1; + float cpuFactor = availableProcessors; + float memoryFactor = static_cast(totalMemory) / 1000.0f / 1000.0f / 1000.0f; + float sizeFactor = (static_cast(map.getWidth()) / mbgl::util::tileSize) * + (static_cast(map.getHeight()) / mbgl::util::tileSize); + + size_t cacheSize = zoomFactor * cpuFactor * memoryFactor * sizeFactor * 0.5f; + + map.setSourceTileCacheSize(cacheSize); + map.renderSync(); } } +void NativeMapView::resizeView(int w, int h) { + width = w; + height = h; + map.update(mbgl::Update::Dimensions); +} + +void NativeMapView::resizeFramebuffer(int w, int h) { + fbWidth = w; + fbHeight = h; + map.update(mbgl::Update::Repaint); +} + } } diff --git a/android/java/.gitignore b/android/java/.gitignore index 5f00b1092..866441810 100644 --- a/android/java/.gitignore +++ b/android/java/.gitignore @@ -18,3 +18,6 @@ local.properties # Token file MapboxGLAndroidSDKTestApp/src/main/res/raw/token.txt + +# Twitter Fabric / Crashlytics +fabric.properties diff --git a/android/java/MapboxGLAndroidSDK/build.gradle b/android/java/MapboxGLAndroidSDK/build.gradle index 39e46fcc0..00ea98820 100644 --- a/android/java/MapboxGLAndroidSDK/build.gradle +++ b/android/java/MapboxGLAndroidSDK/build.gradle @@ -3,7 +3,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:1.2.3' + classpath 'com.android.tools.build:gradle:1.3.0' classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0' } } @@ -28,10 +28,11 @@ repositories { } dependencies { - compile 'commons-validator:commons-validator:1.4.1' - compile 'com.android.support:support-annotations:22.1.1' - compile 'com.android.support:support-v4:22.1.1' - compile 'com.squareup.okhttp:okhttp:2.3.0' + compile 'com.android.support:support-annotations:23.0.1' + compile 'com.android.support:support-v4:23.0.1' + compile 'com.android.support:design:23.0.1' + compile 'com.squareup.okhttp:okhttp:2.4.0' + compile 'com.mapzen.android:lost:1.0.1' } android { @@ -64,9 +65,8 @@ android { } release { - minifyEnabled false jniDebuggable false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + consumerProguardFiles 'proguard-rules.pro' } } } diff --git a/android/java/MapboxGLAndroidSDK/gradle.properties b/android/java/MapboxGLAndroidSDK/gradle.properties index 8ba98ea4f..680aad376 100644 --- a/android/java/MapboxGLAndroidSDK/gradle.properties +++ b/android/java/MapboxGLAndroidSDK/gradle.properties @@ -12,10 +12,10 @@ POM_LICENCE_DIST=repo POM_DEVELOPER_ID=mapbox POM_DEVELOPER_NAME=Mapbox -ANDROID_MIN_SDK=8 -ANDROID_BUILD_TARGET_SDK_VERSION=22 -ANDROID_BUILD_TOOLS_VERSION=22.0.1 -ANDROID_BUILD_SDK_VERSION=22 +ANDROID_MIN_SDK=15 +ANDROID_BUILD_TARGET_SDK_VERSION=23 +ANDROID_BUILD_TOOLS_VERSION=23.0.1 +ANDROID_BUILD_SDK_VERSION=23 POM_NAME=Mapbox GL Android SDK POM_ARTIFACT_ID=mapbox-gl-android-sdk diff --git a/android/java/MapboxGLAndroidSDK/proguard-rules.pro b/android/java/MapboxGLAndroidSDK/proguard-rules.pro new file mode 100644 index 000000000..6d3ca44d4 --- /dev/null +++ b/android/java/MapboxGLAndroidSDK/proguard-rules.pro @@ -0,0 +1,24 @@ +# By default, the flags in this file are appended to flags specified +# in ../sdk/tools/proguard/proguard-android.txt, +# contents of this file will be appended into proguard-android.txt + +# Square okio, ignoring warnings, +# see https://github.com/square/okio/issues/60 +-dontwarn okio.** + +# Package: annotations +-keep class com.mapbox.mapboxgl.annotations.** { *; } + +# Package: geometry +-keep class com.mapbox.mapboxgl.geometry.** { *; } + +# Package: http +-keep class com.mapbox.mapboxgl.http.** { *; } + +# Package views +-keep class com.mapbox.mapboxgl.views.MapView** +-keep class com.mapbox.mapboxgl.views.NativeMapView** +-keepclassmembers class com.mapbox.mapboxgl.views.NativeMapView** { + *; +} + diff --git a/android/java/MapboxGLAndroidSDK/src/main/AndroidManifest.xml b/android/java/MapboxGLAndroidSDK/src/main/AndroidManifest.xml index dea0f1e76..471582eb6 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/AndroidManifest.xml +++ b/android/java/MapboxGLAndroidSDK/src/main/AndroidManifest.xml @@ -1,8 +1,8 @@ - + - + diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/BaseGestureDetector.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/BaseGestureDetector.java deleted file mode 100644 index 8c0c54498..000000000 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/BaseGestureDetector.java +++ /dev/null @@ -1,150 +0,0 @@ -package com.almeros.android.multitouch; - -import android.content.Context; -import android.view.MotionEvent; - -/** - * @author Almer Thie (code.almeros.com) - * Copyright (c) 2013, Almer Thie (code.almeros.com) - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY - * OF SUCH DAMAGE. - */ -public abstract class BaseGestureDetector { - protected final Context mContext; - protected boolean mGestureInProgress; - - protected MotionEvent mPrevEvent; - protected MotionEvent mCurrEvent; - - protected float mCurrPressure; - protected float mPrevPressure; - protected long mTimeDelta; - - - /** - * This value is the threshold ratio between the previous combined pressure - * and the current combined pressure. When pressure decreases rapidly - * between events the position values can often be imprecise, as it usually - * indicates that the user is in the process of lifting a pointer off of the - * device. This value was tuned experimentally. - */ - protected static final float PRESSURE_THRESHOLD = 0.67f; - - - public BaseGestureDetector(Context context) { - mContext = context; - } - - /** - * All gesture detectors need to be called through this method to be able to - * detect gestures. This method delegates work to handler methods - * (handleStartProgressEvent, handleInProgressEvent) implemented in - * extending classes. - * - * @param event MotionEvent - * @return true - */ - public boolean onTouchEvent(MotionEvent event){ - final int actionCode = event.getAction() & MotionEvent.ACTION_MASK; - if (!mGestureInProgress) { - handleStartProgressEvent(actionCode, event); - } else { - handleInProgressEvent(actionCode, event); - } - return true; - } - - /** - * Called when the current event occurred when NO gesture is in progress - * yet. The handling in this implementation may set the gesture in progress - * (via mGestureInProgress) or out of progress - * @param actionCode Action Code from MotionEvent - * @param event MotionEvent - */ - protected abstract void handleStartProgressEvent(int actionCode, MotionEvent event); - - /** - * Called when the current event occurred when a gesture IS in progress. The - * handling in this implementation may set the gesture out of progress (via - * mGestureInProgress). - * @param actionCode Action Code from MotionEvent - * @param event MotionEvent - */ - protected abstract void handleInProgressEvent(int actionCode, MotionEvent event); - - - protected void updateStateByEvent(MotionEvent curr){ - final MotionEvent prev = mPrevEvent; - - // Reset mCurrEvent - if (mCurrEvent != null) { - mCurrEvent.recycle(); - mCurrEvent = null; - } - mCurrEvent = MotionEvent.obtain(curr); - - - // Delta time - mTimeDelta = curr.getEventTime() - prev.getEventTime(); - - // Pressure - mCurrPressure = curr.getPressure(curr.getActionIndex()); - mPrevPressure = prev.getPressure(prev.getActionIndex()); - } - - protected void resetState() { - if (mPrevEvent != null) { - mPrevEvent.recycle(); - mPrevEvent = null; - } - if (mCurrEvent != null) { - mCurrEvent.recycle(); - mCurrEvent = null; - } - mGestureInProgress = false; - } - - - /** - * Returns {@code true} if a gesture is currently in progress. - * @return {@code true} if a gesture is currently in progress, {@code false} otherwise. - */ - public boolean isInProgress() { - return mGestureInProgress; - } - - /** - * Return the time difference in milliseconds between the previous accepted - * GestureDetector event and the current GestureDetector event. - * - * @return Time difference since the last move event in milliseconds. - */ - public long getTimeDelta() { - return mTimeDelta; - } - - /** - * Return the event time of the current GestureDetector event being - * processed. - * - * @return Current GestureDetector event time in milliseconds. - */ - public long getEventTime() { - return mCurrEvent.getEventTime(); - } - -} diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/MoveGestureDetector.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/MoveGestureDetector.java deleted file mode 100644 index 42d6bf32c..000000000 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/MoveGestureDetector.java +++ /dev/null @@ -1,174 +0,0 @@ -package com.almeros.android.multitouch; - -import android.content.Context; -import android.graphics.PointF; -import android.view.MotionEvent; -import com.almeros.android.multitouch.BaseGestureDetector; - -/** - * @author Almer Thie (code.almeros.com) - * Copyright (c) 2013, Almer Thie (code.almeros.com) - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY - * OF SUCH DAMAGE. - */ -public class MoveGestureDetector extends BaseGestureDetector { - - /** - * Listener which must be implemented which is used by MoveGestureDetector - * to perform callbacks to any implementing class which is registered to a - * MoveGestureDetector via the constructor. - * - * @see MoveGestureDetector.SimpleOnMoveGestureListener - */ - public interface OnMoveGestureListener { - public boolean onMove(MoveGestureDetector detector); - public boolean onMoveBegin(MoveGestureDetector detector); - public void onMoveEnd(MoveGestureDetector detector); - } - - /** - * Helper class which may be extended and where the methods may be - * implemented. This way it is not necessary to implement all methods - * of OnMoveGestureListener. - */ - public static class SimpleOnMoveGestureListener implements OnMoveGestureListener { - public boolean onMove(MoveGestureDetector detector) { - return false; - } - - public boolean onMoveBegin(MoveGestureDetector detector) { - return true; - } - - public void onMoveEnd(MoveGestureDetector detector) { - // Do nothing, overridden implementation may be used - } - } - - private static final PointF FOCUS_DELTA_ZERO = new PointF(); - - private final OnMoveGestureListener mListener; - - private PointF mCurrFocusInternal; - private PointF mPrevFocusInternal; - private PointF mFocusExternal = new PointF(); - private PointF mFocusDeltaExternal = new PointF(); - - - public MoveGestureDetector(Context context, OnMoveGestureListener listener) { - super(context); - mListener = listener; - } - - @Override - protected void handleStartProgressEvent(int actionCode, MotionEvent event){ - switch (actionCode) { - case MotionEvent.ACTION_DOWN: - resetState(); // In case we missed an UP/CANCEL event - - mPrevEvent = MotionEvent.obtain(event); - mTimeDelta = 0; - - updateStateByEvent(event); - break; - - case MotionEvent.ACTION_MOVE: - mGestureInProgress = mListener.onMoveBegin(this); - break; - } - } - - @Override - protected void handleInProgressEvent(int actionCode, MotionEvent event){ - switch (actionCode) { - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - mListener.onMoveEnd(this); - resetState(); - break; - - case MotionEvent.ACTION_MOVE: - updateStateByEvent(event); - - // Only accept the event if our relative pressure is within - // a certain limit. This can help filter shaky data as a - // finger is lifted. - if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) { - final boolean updatePrevious = mListener.onMove(this); - if (updatePrevious) { - mPrevEvent.recycle(); - mPrevEvent = MotionEvent.obtain(event); - } - } - break; - } - } - - protected void updateStateByEvent(MotionEvent curr) { - super.updateStateByEvent(curr); - - final MotionEvent prev = mPrevEvent; - - // Focus intenal - mCurrFocusInternal = determineFocalPoint(curr); - mPrevFocusInternal = determineFocalPoint(prev); - - // Focus external - // - Prevent skipping of focus delta when a finger is added or removed - boolean mSkipNextMoveEvent = prev.getPointerCount() != curr.getPointerCount(); - mFocusDeltaExternal = mSkipNextMoveEvent ? FOCUS_DELTA_ZERO : new PointF(mCurrFocusInternal.x - mPrevFocusInternal.x, mCurrFocusInternal.y - mPrevFocusInternal.y); - - // - Don't directly use mFocusInternal (or skipping will occur). Add - // unskipped delta values to mFocusExternal instead. - mFocusExternal.x += mFocusDeltaExternal.x; - mFocusExternal.y += mFocusDeltaExternal.y; - } - - /** - * Determine (multi)finger focal point (a.k.a. center point between all - * fingers) - * - * @param MotionEvent e - * @return PointF focal point - */ - private PointF determineFocalPoint(MotionEvent e){ - // Number of fingers on screen - final int pCount = e.getPointerCount(); - float x = 0f; - float y = 0f; - - for(int i = 0; i < pCount; i++){ - x += e.getX(i); - y += e.getY(i); - } - - return new PointF(x/pCount, y/pCount); - } - - public float getFocusX() { - return mFocusExternal.x; - } - - public float getFocusY() { - return mFocusExternal.y; - } - - public PointF getFocusDelta() { - return mFocusDeltaExternal; - } - -} diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/RotateGestureDetector.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/RotateGestureDetector.java deleted file mode 100644 index ba27af18a..000000000 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/RotateGestureDetector.java +++ /dev/null @@ -1,169 +0,0 @@ -package com.almeros.android.multitouch; - -import android.content.Context; -import android.view.MotionEvent; - -/** - * @author Almer Thie (code.almeros.com) - * Copyright (c) 2013, Almer Thie (code.almeros.com) - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY - * OF SUCH DAMAGE. - */ -public class RotateGestureDetector extends TwoFingerGestureDetector { - - /** - * Listener which must be implemented which is used by RotateGestureDetector - * to perform callbacks to any implementing class which is registered to a - * RotateGestureDetector via the constructor. - * - * @see RotateGestureDetector.SimpleOnRotateGestureListener - */ - public interface OnRotateGestureListener { - public boolean onRotate(RotateGestureDetector detector); - public boolean onRotateBegin(RotateGestureDetector detector); - public void onRotateEnd(RotateGestureDetector detector); - } - - /** - * Helper class which may be extended and where the methods may be - * implemented. This way it is not necessary to implement all methods - * of OnRotateGestureListener. - */ - public static class SimpleOnRotateGestureListener implements OnRotateGestureListener { - public boolean onRotate(RotateGestureDetector detector) { - return false; - } - - public boolean onRotateBegin(RotateGestureDetector detector) { - return true; - } - - public void onRotateEnd(RotateGestureDetector detector) { - // Do nothing, overridden implementation may be used - } - } - - - private final OnRotateGestureListener mListener; - private boolean mSloppyGesture; - - public RotateGestureDetector(Context context, OnRotateGestureListener listener) { - super(context); - mListener = listener; - } - - @Override - protected void handleStartProgressEvent(int actionCode, MotionEvent event){ - switch (actionCode) { - case MotionEvent.ACTION_POINTER_DOWN: - // At least the second finger is on screen now - - resetState(); // In case we missed an UP/CANCEL event - mPrevEvent = MotionEvent.obtain(event); - mTimeDelta = 0; - - updateStateByEvent(event); - - // See if we have a sloppy gesture - mSloppyGesture = isSloppyGesture(event); - if(!mSloppyGesture){ - // No, start gesture now - mGestureInProgress = mListener.onRotateBegin(this); - } - break; - - case MotionEvent.ACTION_MOVE: - if (!mSloppyGesture) { - break; - } - - // See if we still have a sloppy gesture - mSloppyGesture = isSloppyGesture(event); - if(!mSloppyGesture){ - // No, start normal gesture now - mGestureInProgress = mListener.onRotateBegin(this); - } - - break; - - case MotionEvent.ACTION_POINTER_UP: - if (!mSloppyGesture) { - break; - } - - break; - } - } - - - @Override - protected void handleInProgressEvent(int actionCode, MotionEvent event){ - switch (actionCode) { - case MotionEvent.ACTION_POINTER_UP: - // Gesture ended but - updateStateByEvent(event); - - if (!mSloppyGesture) { - mListener.onRotateEnd(this); - } - - resetState(); - break; - - case MotionEvent.ACTION_CANCEL: - if (!mSloppyGesture) { - mListener.onRotateEnd(this); - } - - resetState(); - break; - - case MotionEvent.ACTION_MOVE: - updateStateByEvent(event); - - // Only accept the event if our relative pressure is within - // a certain limit. This can help filter shaky data as a - // finger is lifted. - if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) { - final boolean updatePrevious = mListener.onRotate(this); - if (updatePrevious) { - mPrevEvent.recycle(); - mPrevEvent = MotionEvent.obtain(event); - } - } - break; - } - } - - @Override - protected void resetState() { - super.resetState(); - mSloppyGesture = false; - } - - - /** - * Return the rotation difference from the previous rotate event to the current - * event. - * - * @return The current rotation //difference in degrees. - */ - public float getRotationDegreesDelta() { - double diffRadians = Math.atan2(mPrevFingerDiffY, mPrevFingerDiffX) - Math.atan2(mCurrFingerDiffY, mCurrFingerDiffX); - return (float) (diffRadians * 180 / Math.PI); - } -} diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/ShoveGestureDetector.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/ShoveGestureDetector.java deleted file mode 100644 index cb8f81870..000000000 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/ShoveGestureDetector.java +++ /dev/null @@ -1,202 +0,0 @@ -package com.almeros.android.multitouch; - -import android.content.Context; -import android.view.MotionEvent; - -/** - * @author Robert Nordan (robert.nordan@norkart.no) - * - * Copyright (c) 2013, Norkart AS - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY - * OF SUCH DAMAGE. - */ -public class ShoveGestureDetector extends TwoFingerGestureDetector { - - /** - * Listener which must be implemented which is used by ShoveGestureDetector - * to perform callbacks to any implementing class which is registered to a - * ShoveGestureDetector via the constructor. - * - * @see ShoveGestureDetector.SimpleOnShoveGestureListener - */ - public interface OnShoveGestureListener { - public boolean onShove(ShoveGestureDetector detector); - public boolean onShoveBegin(ShoveGestureDetector detector); - public void onShoveEnd(ShoveGestureDetector detector); - } - - /** - * Helper class which may be extended and where the methods may be - * implemented. This way it is not necessary to implement all methods - * of OnShoveGestureListener. - */ - public static class SimpleOnShoveGestureListener implements OnShoveGestureListener { - public boolean onShove(ShoveGestureDetector detector) { - return false; - } - - public boolean onShoveBegin(ShoveGestureDetector detector) { - return true; - } - - public void onShoveEnd(ShoveGestureDetector detector) { - // Do nothing, overridden implementation may be used - } - } - - private float mPrevAverageY; - private float mCurrAverageY; - - private final OnShoveGestureListener mListener; - private boolean mSloppyGesture; - - public ShoveGestureDetector(Context context, OnShoveGestureListener listener) { - super(context); - mListener = listener; - } - - @Override - protected void handleStartProgressEvent(int actionCode, MotionEvent event){ - switch (actionCode) { - case MotionEvent.ACTION_POINTER_DOWN: - // At least the second finger is on screen now - - resetState(); // In case we missed an UP/CANCEL event - mPrevEvent = MotionEvent.obtain(event); - mTimeDelta = 0; - - updateStateByEvent(event); - - // See if we have a sloppy gesture - mSloppyGesture = isSloppyGesture(event); - if(!mSloppyGesture){ - // No, start gesture now - mGestureInProgress = mListener.onShoveBegin(this); - } - break; - - case MotionEvent.ACTION_MOVE: - if (!mSloppyGesture) { - break; - } - - // See if we still have a sloppy gesture - mSloppyGesture = isSloppyGesture(event); - if(!mSloppyGesture){ - // No, start normal gesture now - mGestureInProgress = mListener.onShoveBegin(this); - } - - break; - - case MotionEvent.ACTION_POINTER_UP: - if (!mSloppyGesture) { - break; - } - - break; - } - } - - - @Override - protected void handleInProgressEvent(int actionCode, MotionEvent event){ - switch (actionCode) { - case MotionEvent.ACTION_POINTER_UP: - // Gesture ended but - updateStateByEvent(event); - - if (!mSloppyGesture) { - mListener.onShoveEnd(this); - } - - resetState(); - break; - - case MotionEvent.ACTION_CANCEL: - if (!mSloppyGesture) { - mListener.onShoveEnd(this); - } - - resetState(); - break; - - case MotionEvent.ACTION_MOVE: - updateStateByEvent(event); - - // Only accept the event if our relative pressure is within - // a certain limit. This can help filter shaky data as a - // finger is lifted. Also check that shove is meaningful. - if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD - && Math.abs(getShovePixelsDelta()) > 0.5f) { - final boolean updatePrevious = mListener.onShove(this); - if (updatePrevious) { - mPrevEvent.recycle(); - mPrevEvent = MotionEvent.obtain(event); - } - } - break; - } - } - - @Override - protected void resetState() { - super.resetState(); - mSloppyGesture = false; - mPrevAverageY = 0.0f; - mCurrAverageY = 0.0f; - } - - @Override - protected void updateStateByEvent(MotionEvent curr){ - super.updateStateByEvent(curr); - - final MotionEvent prev = mPrevEvent; - float py0 = prev.getY(0); - float py1 = prev.getY(1); - mPrevAverageY = (py0 + py1) / 2.0f; - - float cy0 = curr.getY(0); - float cy1 = curr.getY(1); - mCurrAverageY = (cy0 + cy1) / 2.0f; - } - - @Override - protected boolean isSloppyGesture(MotionEvent event){ - boolean sloppy = super.isSloppyGesture(event); - if (sloppy) - return true; - - // If it's not traditionally sloppy, we check if the angle between fingers - // is acceptable. - double angle = Math.abs(Math.atan2(mCurrFingerDiffY, mCurrFingerDiffX)); - //about 20 degrees, left or right - return !(( 0.0f < angle && angle < 0.35f) - || 2.79f < angle && angle < Math.PI); - } - - - /** - * Return the distance in pixels from the previous shove event to the current - * event. - * - * @return The current distance in pixels. - */ - public float getShovePixelsDelta() { - return mCurrAverageY - mPrevAverageY; - } -} diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/TwoFingerGestureDetector.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/TwoFingerGestureDetector.java deleted file mode 100644 index 6c6a0e4bf..000000000 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/almeros/android/multitouch/TwoFingerGestureDetector.java +++ /dev/null @@ -1,179 +0,0 @@ -package com.almeros.android.multitouch; - -import android.content.Context; -import android.util.DisplayMetrics; -import android.view.MotionEvent; -import android.view.ViewConfiguration; - -/** - * @author Almer Thie (code.almeros.com) - * Copyright (c) 2013, Almer Thie (code.almeros.com) - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY - * OF SUCH DAMAGE. - */ -public abstract class TwoFingerGestureDetector extends BaseGestureDetector { - - private final float mEdgeSlop; - private float mRightSlopEdge; - private float mBottomSlopEdge; - - protected float mPrevFingerDiffX; - protected float mPrevFingerDiffY; - protected float mCurrFingerDiffX; - protected float mCurrFingerDiffY; - - private float mCurrLen; - private float mPrevLen; - - public TwoFingerGestureDetector(Context context) { - super(context); - - ViewConfiguration config = ViewConfiguration.get(context); - mEdgeSlop = config.getScaledEdgeSlop(); - } - - @Override - protected abstract void handleStartProgressEvent(int actionCode, MotionEvent event); - - @Override - protected abstract void handleInProgressEvent(int actionCode, MotionEvent event); - - protected void updateStateByEvent(MotionEvent curr){ - super.updateStateByEvent(curr); - - final MotionEvent prev = mPrevEvent; - - mCurrLen = -1; - mPrevLen = -1; - - // Previous - final float px0 = prev.getX(0); - final float py0 = prev.getY(0); - final float px1 = prev.getX(1); - final float py1 = prev.getY(1); - final float pvx = px1 - px0; - final float pvy = py1 - py0; - mPrevFingerDiffX = pvx; - mPrevFingerDiffY = pvy; - - // Current - final float cx0 = curr.getX(0); - final float cy0 = curr.getY(0); - final float cx1 = curr.getX(1); - final float cy1 = curr.getY(1); - final float cvx = cx1 - cx0; - final float cvy = cy1 - cy0; - mCurrFingerDiffX = cvx; - mCurrFingerDiffY = cvy; - } - - /** - * Return the current distance between the two pointers forming the - * gesture in progress. - * - * @return Distance between pointers in pixels. - */ - public float getCurrentSpan() { - if (mCurrLen == -1) { - final float cvx = mCurrFingerDiffX; - final float cvy = mCurrFingerDiffY; - mCurrLen = (float)Math.sqrt(cvx*cvx + cvy*cvy); - } - return mCurrLen; - } - - /** - * Return the previous distance between the two pointers forming the - * gesture in progress. - * - * @return Previous distance between pointers in pixels. - */ - public float getPreviousSpan() { - if (mPrevLen == -1) { - final float pvx = mPrevFingerDiffX; - final float pvy = mPrevFingerDiffY; - mPrevLen = (float)Math.sqrt(pvx*pvx + pvy*pvy); - } - return mPrevLen; - } - - /** - * MotionEvent has no getRawX(int) method; simulate it pending future API approval. - * @param event - * @param pointerIndex - * @return Raw X value or 0 - */ - protected static float getRawX(MotionEvent event, int pointerIndex) { - float offset = event.getX() - event.getRawX(); - if(pointerIndex < event.getPointerCount()){ - return event.getX(pointerIndex) + offset; - } - return 0f; - } - - /** - * MotionEvent has no getRawY(int) method; simulate it pending future API approval. - * @param event - * @param pointerIndex - * @return Raw Y value or 0 - */ - protected static float getRawY(MotionEvent event, int pointerIndex) { - float offset = event.getY() - event.getRawY(); - if(pointerIndex < event.getPointerCount()){ - return event.getY(pointerIndex) + offset; - } - return 0f; - } - - /** - * Check if we have a sloppy gesture. Sloppy gestures can happen if the edge - * of the user's hand is touching the screen, for example. - * - * @param event MotionEvent - * @return {@code true} if is a sloppy gesture, {@code false} if not - */ - protected boolean isSloppyGesture(MotionEvent event){ - // As orientation can change, query the metrics in touch down - DisplayMetrics metrics = mContext.getResources().getDisplayMetrics(); - mRightSlopEdge = metrics.widthPixels - mEdgeSlop; - mBottomSlopEdge = metrics.heightPixels - mEdgeSlop; - - final float edgeSlop = mEdgeSlop; - final float rightSlop = mRightSlopEdge; - final float bottomSlop = mBottomSlopEdge; - - final float x0 = event.getRawX(); - final float y0 = event.getRawY(); - final float x1 = getRawX(event, 1); - final float y1 = getRawY(event, 1); - - boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop - || x0 > rightSlop || y0 > bottomSlop; - boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop - || x1 > rightSlop || y1 > bottomSlop; - - if (p0sloppy && p1sloppy) { - return true; - } else if (p0sloppy) { - return true; - } else if (p1sloppy) { - return true; - } - return false; - } - -} diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/Annotation.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/Annotation.java new file mode 100644 index 000000000..9cddf9984 --- /dev/null +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/Annotation.java @@ -0,0 +1,93 @@ +package com.mapbox.mapboxgl.annotations; + +import com.mapbox.mapboxgl.views.MapView; + +public abstract class Annotation implements Comparable { + + /** + * The annotation id + * + * Internal C++ id is stored as unsigned int. + */ + protected long id = -1; // -1 unless added to a MapView + protected MapView mapView; + + float alpha = 1.0f; + boolean visible = true; + + public Annotation() {} + + public float getAlpha() { + return alpha; + } + + public long getId() { + return id; + } + + public boolean isVisible() { + return visible; + } + + public void remove() { + if (mapView == null) return; + mapView.removeAnnotation(this); + } + + public void setAlpha(float alpha) { + this.alpha = alpha; + } + + public void setId(long id) { + this.id = id; + } + + public void setMapView(MapView mapView) { + this.mapView = mapView; + } + + public void setVisible(boolean visible) { + this.visible = visible; + } + + // TODO: Implement getZIndex of Google Maps Android API +// public float getZIndex() { +// +// } + + // TODO: Implement setZIndex of Google Maps Android API +// public void setZIndex(float zIndex) { +// +// } + + + @Override + public boolean equals(Object o) { + if (o == null) { + return false; + } + + if (o instanceof Annotation) { + Annotation comp = (Annotation) o; + return id == comp.id; + } + return false; + } + + @Override + public int compareTo(Annotation annotation) { + + if (annotation == null) { + return -1; + } + + if (id < annotation.getId()) { + return 1; + } else if (id > annotation.getId()) { + return -1; + } + + // Equal + return 0; + } +} diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/AnnotationOptions.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/AnnotationOptions.java new file mode 100644 index 000000000..f03ff45f7 --- /dev/null +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/AnnotationOptions.java @@ -0,0 +1,22 @@ +package com.mapbox.mapboxgl.annotations; + +public abstract class AnnotationOptions { + + protected Annotation annotation; + + public AnnotationOptions() {} + + public AnnotationOptions alpha(float alpha) { + annotation.alpha = alpha; + return this; + } + + public float getAlpha() { + return annotation.alpha; + } + + public boolean isVisible() { + return annotation.visible; + } + +} diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/Circle.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/Circle.java new file mode 100644 index 000000000..f8908465b --- /dev/null +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/Circle.java @@ -0,0 +1,67 @@ +package com.mapbox.mapboxgl.annotations; + + +import android.graphics.Color; + +import com.mapbox.mapboxgl.geometry.LatLng; + +/** + * UNIMPLEMENTED: Needs implementation in Native. + * + * https://github.com/mapbox/mapbox-gl-native/issues/1882 + * https://github.com/mapbox/mapbox-gl-native/issues/1726 + */ +public class Circle extends Annotation { + + LatLng center; + int fillColor = Color.BLACK; + double radius; + int strokeColor = Color.BLACK; + float strokeWidth = 10; // Google Maps API defaults to 10 + + public LatLng getCenter() { + return center; + } + + public int getFillColor() { + return fillColor; + } + + /** + * Returns the circle's radius, in meters. + * + * @return radius in meters + */ + public double getRadius() { + return radius; + } + + public int getStrokeColor() { + return strokeColor; + } + + public float getStrokeWidth() { + return strokeWidth; + } + + public void setCenter(LatLng center) { + this.center = center; + } + + public void setFillColor(int color) { + fillColor = color; + } + + public void setRadius(double radius) { + this.radius = radius; + } + + public void setStrokeColor (int color) { + strokeColor = color; + } + + public void setStrokeWidth (float width) { + strokeWidth = width; + } + +} diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/CircleOptions.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/CircleOptions.java new file mode 100644 index 000000000..5e97d5b5e --- /dev/null +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/CircleOptions.java @@ -0,0 +1,64 @@ +package com.mapbox.mapboxgl.annotations; + + +import com.mapbox.mapboxgl.geometry.LatLng; + +/** + * UNIMPLEMENTED: Needs implementation in Native. + * + * https://github.com/mapbox/mapbox-gl-native/issues/1882 + * https://github.com/mapbox/mapbox-gl-native/issues/1726 + */ +public class CircleOptions extends AnnotationOptions { + + private Circle circle; + + public CircleOptions() { + circle = new Circle(); + } + public CircleOptions center(LatLng center) { + circle.center = center; + return this; + } + + public CircleOptions fillColor(int color) { + circle.fillColor = color; + return this; + } + + public LatLng getCenter() { + return circle.center; + } + + public int getFillColor() { + return circle.fillColor; + } + + public double getRadius() { + return circle.radius; + } + + public int getStrokeColor () { + return circle.strokeColor; + } + + public float getStrokeWidth() { + return circle.strokeWidth; + } + + public CircleOptions radius (double radius) { + circle.radius = radius; + return this; + } + + public CircleOptions strokeColor(int color) { + circle.strokeColor = color; + return this; + } + + public CircleOptions strokeWidth (float width) { + circle.strokeWidth = width; + return this; + } + +} diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindow.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindow.java new file mode 100644 index 000000000..56eae1951 --- /dev/null +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindow.java @@ -0,0 +1,199 @@ +package com.mapbox.mapboxgl.annotations; + +import android.content.Context; +import android.graphics.PointF; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import com.mapbox.mapboxgl.geometry.LatLng; +import com.mapbox.mapboxgl.views.MapView; + +/** + * A tooltip view + */ +public class InfoWindow { + + private Marker boundMarker; + private MapView mMapView; + private boolean mIsVisible; + protected View mView; + + static int mTitleId = 0; + static int mDescriptionId = 0; + static int mSubDescriptionId = 0; + static int mImageId = 0; + + public InfoWindow(int layoutResId, MapView mapView) { + mMapView = mapView; + mIsVisible = false; + Context context = mapView.getContext(); + LayoutInflater inflater = + (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mView = inflater.inflate(layoutResId, (ViewGroup) mapView, false); + + if (mTitleId == 0) { + setResIds(mapView.getContext()); + } + + // default behavior: close it when clicking on the tooltip: + setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent e) { + if (e.getAction() == MotionEvent.ACTION_UP) { + close(); + } + return true; + } + }); + } + + public InfoWindow(View view, MapView mapView) { + mMapView = mapView; + mIsVisible = false; + ViewGroup parent = (ViewGroup) mapView.getParent(); + Context context = mapView.getContext(); + LayoutInflater inflater = + (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mView = view; + + // default behavior: close it when clicking on the tooltip: + setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent e) { + if (e.getAction() == MotionEvent.ACTION_UP) { + close(); + } + return true; + } + }); + } + + /** + * open the window at the specified position. + * + * @param object the graphical object on which is hooked the view + * @param position to place the window on the map + * @param offsetX (&offsetY) the offset of the view to the position, in pixels. + * This allows to offset the view from the object position. + * @return this infowindow + */ + public InfoWindow open(Marker object, LatLng position, int offsetX, int offsetY) { + onOpen(object); + MapView.LayoutParams lp = new MapView.LayoutParams(MapView.LayoutParams.WRAP_CONTENT, MapView.LayoutParams.WRAP_CONTENT); + mView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + + PointF coords = mMapView.toScreenLocation(position); + double y = mMapView.getTopOffsetPixelsForAnnotationSymbol(object.sprite); + y = y * mMapView.getScreenDensity(); + + // Flip y coordinate as Android view origin is upper left corner + coords.y = mMapView.getHeight() - coords.y; + lp.leftMargin = (int) coords.x - (mView.getMeasuredWidth() / 2); + // Add y because it's a negative value + lp.topMargin = (int) coords.y - mView.getMeasuredHeight() + (int) y; + + close(); //if it was already opened + mMapView.addView(mView, lp); + mIsVisible = true; + return this; + } + + /** + * Close this InfoWindow if it is visible, otherwise don't do anything. + * + * @return this info window + */ + public InfoWindow close() { + if (mIsVisible) { + mIsVisible = false; + ((ViewGroup) mView.getParent()).removeView(mView); +// this.boundMarker.blur(); + setBoundMarker(null); + onClose(); + } + return this; + } + + /** + * Returns the Android view. This allows to set its content. + * + * @return the Android view + */ + public View getView() { + return mView; + } + + /** + * Returns the mapView this InfoWindow is bound to + * + * @return the mapView + */ + public MapView getMapView() { + return mMapView; + } + + /** + * Constructs the view that is displayed when the InfoWindow opens. + * This retrieves data from overlayItem and shows it in the tooltip. + * + * @param overlayItem the tapped overlay item + */ + public void onOpen(Marker overlayItem) { + String title = overlayItem.getTitle(); + ((TextView) mView.findViewById(mTitleId /*R.id.title*/)).setText(title); + String snippet = overlayItem.getSnippet(); + ((TextView) mView.findViewById(mDescriptionId /*R.id.description*/)).setText(snippet); + +/* + //handle sub-description, hiding or showing the text view: + TextView subDescText = (TextView) mView.findViewById(mSubDescriptionId); + String subDesc = overlayItem.getSubDescription(); + if ("".equals(subDesc)) { + subDescText.setVisibility(View.GONE); + } else { + subDescText.setText(subDesc); + subDescText.setVisibility(View.VISIBLE); + } +*/ + } + + public void onClose() { + //by default, do nothing + } + + public InfoWindow setBoundMarker(Marker aBoundMarker) { + this.boundMarker = aBoundMarker; + return this; + } + + public Marker getBoundMarker() { + return boundMarker; + } + + /** + * Given a context, set the resource ids for the layout + * of the InfoWindow. + * + * @param context + */ + private static void setResIds(Context context) { + String packageName = context.getPackageName(); //get application package name + mTitleId = context.getResources().getIdentifier("id/infowindow_title", null, packageName); + mDescriptionId = + context.getResources().getIdentifier("id/infowindow_description", null, packageName); + mSubDescriptionId = context.getResources() + .getIdentifier("id/infowindow_subdescription", null, packageName); + mImageId = context.getResources().getIdentifier("id/infowindow_image", null, packageName); + } + + /** + * Use to override default touch events handling on InfoWindow (ie, close automatically) + * + * @param listener New View.OnTouchListener to use + */ + public void setOnTouchListener(View.OnTouchListener listener) { + mView.setOnTouchListener(listener); + } +} diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindowTipView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindowTipView.java new file mode 100644 index 000000000..657e63a84 --- /dev/null +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/InfoWindowTipView.java @@ -0,0 +1,40 @@ +package com.mapbox.mapboxgl.annotations; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.util.AttributeSet; +import android.view.View; + +public class InfoWindowTipView extends View { + + private Paint mPaint; + private Path mPath; + + public InfoWindowTipView(Context context, AttributeSet attrs) { + super(context, attrs); + + mPath = new Path(); + + this.mPaint = new Paint(); + this.mPaint.setColor(Color.WHITE); + this.mPaint.setAntiAlias(true); + this.mPaint.setStrokeWidth(0.0f); + this.mPaint.setStyle(Paint.Style.FILL); + } + + @Override + protected void onDraw(Canvas canvas) { + int height = getMeasuredHeight(); + int width = getMeasuredWidth(); + + mPath.rewind(); + mPath.moveTo((width / 2) - height, 0); + mPath.lineTo((width / 2) + height, 0); + mPath.lineTo((width / 2), height); + mPath.lineTo((width / 2) - height, 0); + canvas.drawPath(mPath, this.mPaint); + } +} diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/Marker.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/Marker.java new file mode 100644 index 000000000..17317bffb --- /dev/null +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/Marker.java @@ -0,0 +1,192 @@ +package com.mapbox.mapboxgl.annotations; + +import android.graphics.Point; +import android.view.View; +import com.mapbox.mapboxgl.geometry.LatLng; +import com.mapbox.mapboxgl.views.R; + +public class Marker extends Annotation { + + float anchorU; + float anchorV; + boolean draggable; + boolean flat; + float infoWindowAnchorU; + float infoWindowAnchorV; + LatLng position; + float rotation; + String snippet; + String sprite = "default_marker"; + String title; + private InfoWindow infoWindow = null; + + private boolean infoWindowShown = false; + + /** + * Constructor + */ + public Marker() { + super(); + } + + /** + * If two markers have the same LatLng, they are equal. + * + * @param other object + * @return boolean - do they have the same LatLng + */ + public boolean equals(Object other) { + if (!(other instanceof Marker)) return false; + double lat = position.getLatitude(); + double lng = position.getLongitude(); + LatLng otherPosition = ((Marker)other).getPosition(); + double otherLat = otherPosition.getLatitude(); + double otherLng = otherPosition.getLongitude(); + return (lat == otherLat && otherLng == lng); + } + + public float getAlpha() { + return alpha; + } + + public Point getAnchor() { + return new Point((int)anchorU, (int)anchorV); + } + + public float getAnchorU() { + return anchorU; + } + + public float getAnchorV() { + return anchorV; + } + + public LatLng getPosition() { + return position; + } + + public float getRotation() { + return rotation; + } + + public String getSnippet() { + return snippet; + } + + public String getTitle() { + return title; + } + + public void hideInfoWindow() { + infoWindow.close(); + infoWindowShown = false; + } + + public boolean isDraggable() { + return draggable; + } + + public boolean isFlat() { + return flat; + } + + public boolean isInfoWindowShown () { + return infoWindowShown; + } + + public void setAnchor(float u, float v) { + this.anchorU = u; + this.anchorV = v; + } + + public void setDraggable(boolean draggable) { + this.draggable = draggable; + } + + public void setFlat(boolean flat) { + this.flat = flat; + } + + public void setInfoWindowAnchor(float u, float v) { + infoWindowAnchorU = u; + infoWindowAnchorV = v; + } + + public void setPosition(LatLng latlng) { + this.position = position; + } + + public void setRotation(float rotation) { + this.rotation = rotation; + } + + public void setSnippet(String snippet) { + this.snippet = snippet; + } + + /** + * You can specify the name of a sprite to get a marker other than the default marker. + * This name can be found in the sprite json file: + * + * https://github.com/mapbox/mapbox-gl-styles/blob/mb-pages/sprites/mapbox-streets.json + * + * @param sprite + */ + public void setSprite(String sprite) { + this.sprite = sprite; + } + + public void setTitle(String title) { + this.title = title; + } + + public void showInfoWindow() { + + if (!isVisible() || mapView == null) { + return; + } + + getInfoWindow().open(this, getPosition(), (int) anchorU, (int) anchorV); + getInfoWindow().setBoundMarker(this); + infoWindowShown = true; + } + + /** + * Use to set a custom OnTouchListener for the InfoWindow. + * By default the InfoWindow will close on touch. + * @param listener Custom OnTouchListener + */ + public void setInfoWindowOnTouchListener(View.OnTouchListener listener) { + if (listener == null) { + return; + } + getInfoWindow().setOnTouchListener(listener); + } + + /** + * Common internal InfoWindow initialization method + * @return InfoWindow for Marker + */ + private InfoWindow getInfoWindow() { + if (infoWindow == null) { + infoWindow = new InfoWindow(R.layout.infowindow, mapView); + } + return infoWindow; + } + + @Override + public void setVisible(boolean visible) { + super.setVisible(visible); + if (!visible && infoWindowShown) { + hideInfoWindow(); + } + } + + // TODO Method in Google Maps Android API +// public int hashCode() + + // TODO: Implement this method of Google Maps Android API +// void setIcon(BitmapDescriptor icon) { +// +// } +} diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/MarkerOptions.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/MarkerOptions.java new file mode 100644 index 000000000..2e577067d --- /dev/null +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/MarkerOptions.java @@ -0,0 +1,125 @@ +package com.mapbox.mapboxgl.annotations; + +import com.mapbox.mapboxgl.geometry.LatLng; + +public class MarkerOptions extends AnnotationOptions { + + public MarkerOptions() { + annotation = new Marker(); + } + + public MarkerOptions anchor(float u, float v) { + ((Marker)annotation).anchorU = u; + ((Marker)annotation).anchorV = v; + return this; + } + + public MarkerOptions draggable(boolean draggable) { + ((Marker)annotation).draggable = draggable; + return this; + } + + public MarkerOptions flat(boolean flat) { + ((Marker)annotation).flat = flat; + return this; + } + + public float getAnchorU() { + return ((Marker)annotation).anchorU; + } + + public float getAnchorV() { + return ((Marker)annotation).anchorV; + } + + // TODO: Implement this method of Google Maps Android API +// public BitmapDescriptor getIcon () { +// +// } + + public float getInfoWindowAnchorU() { + return ((Marker)annotation).infoWindowAnchorU; + } + + public float getInfoWindowAnchorV() { + return ((Marker)annotation).infoWindowAnchorV; + } + + public Marker getMarker() { + return (Marker)annotation; + } + + public LatLng getPosition() { + return ((Marker)annotation).position; + } + + public float getRotation() { + return ((Marker)annotation).rotation; + } + + public String getSnippet() { + return ((Marker)annotation).snippet; + } + + public String getTitle() { + return ((Marker)annotation).title; + } + + public MarkerOptions infoWindowAnchor(float u, float v) { + ((Marker)annotation).infoWindowAnchorU = u; + ((Marker)annotation).infoWindowAnchorV = v; + return this; + } + + public boolean isDraggable() { + return ((Marker)annotation).draggable; + } + + public boolean isFlat() { + return ((Marker)annotation).flat; + } + + public boolean isVisible() { + return ((Marker)annotation).visible; + } + + public MarkerOptions position(LatLng position) { + ((Marker)annotation).position = position; + return this; + } + + public MarkerOptions rotation(float rotation) { + ((Marker)annotation).rotation = rotation; + return this; + } + + public MarkerOptions snippet(String snippet) { + ((Marker)annotation).snippet = snippet; + return this; + } + + public MarkerOptions sprite(String sprite) { + ((Marker)annotation).sprite = sprite; + return this; + } + + public MarkerOptions title(String title) { + ((Marker)annotation).title = title; + return this; + } + + public MarkerOptions visible(boolean visible) { + annotation.visible = visible; + return this; + } + + + // TODO: Implement this method of Google Maps Android API +// public MarkerOptions icon(BitmapDescriptor icon) { +// +// } + + // TODO: Implement this method of Google Maps Android API +// public void writeToParcel (Parcel out, int flags) + +} diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/MultiPoint.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/MultiPoint.java new file mode 100644 index 000000000..4ebdd5071 --- /dev/null +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/MultiPoint.java @@ -0,0 +1,53 @@ +package com.mapbox.mapboxgl.annotations; + +import com.mapbox.mapboxgl.geometry.LatLng; + +import java.util.ArrayList; +import java.util.List; + +public abstract class MultiPoint extends Annotation { + + List points; + + public MultiPoint() { + super(); + points = new ArrayList<>(); + } + + /** + * Returns a copy of the points. + * + * @return points - as a copy + */ + public List getPoints() { + return new ArrayList<>(points); + } + + /** + * Sets the points of this polyline. This method will take a copy + * of the points, so further mutations to points will have no effect + * on this polyline. + * + * @param points + */ + void setPoints(List points) { + this.points = new ArrayList<>(points); + } + + + // TODO: Implement hashCode of Google Maps Android API +// public int hashCode() { +// +// } + + // TODO: Implement isGeodesic of Google Maps Android API +// public boolean isGeodesic() { +// +// } + + // TODO: Implement setGeodesic of Google Maps Android API +// public void setGeodesic(boolean geodesic) { +// +// } + +} diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/MultiPointOptions.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/MultiPointOptions.java new file mode 100644 index 000000000..581c42bfd --- /dev/null +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/MultiPointOptions.java @@ -0,0 +1,15 @@ +package com.mapbox.mapboxgl.annotations; + +import com.mapbox.mapboxgl.geometry.LatLng; + +import java.util.List; + +public abstract class MultiPointOptions extends AnnotationOptions { + + public MultiPointOptions() {} + + public List getPoints() { + // the getter gives us a copy, which is the safe thing to do... + return ((MultiPoint)annotation).getPoints(); + } +} diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/Polygon.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/Polygon.java new file mode 100644 index 000000000..41edb2a5d --- /dev/null +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/Polygon.java @@ -0,0 +1,99 @@ +package com.mapbox.mapboxgl.annotations; + + +import android.graphics.Color; + +import com.mapbox.mapboxgl.geometry.LatLng; + +import java.util.ArrayList; +import java.util.List; + +public class Polygon extends MultiPoint { + + int fillColor = Color.BLACK; // default fillColor is black + float strokeAlpha = 1; + int strokeColor = Color.BLACK; // default strokeColor is black + float strokeWidth = 10; // As specified by Google API Docs (in pixels) + + List> holes; + + public Polygon() { + super(); + holes = new ArrayList<>(); + } + + public int getFillColor() { + return fillColor; + } + + public List> getHoles () { + return holes; + } + + public int getStrokeColor() { + return strokeColor; + } + + /** + * UNIMPLEMENTED: Needs implementation in Native. + * https://github.com/mapbox/mapbox-gl-native/issues/1737 + * @return stroke width as float + */ + public float getStrokeWidth() { + return strokeWidth; + } + + public void setFillAlpha(float alpha) { + this.alpha = alpha; + } + + public void setFillColor(int color) { + fillColor = color; + } + + /** + * Sets the holes of this polygon. This method will take a copy of the holes, + * so further mutations to holes parameter will have no effect on this polygon. + * + * @param holes + */ + public void setHoles(List> holes) { + this.holes = new ArrayList<>(); + for (List hole : holes) { + this.holes.add(new ArrayList<>(hole)); + } + } + + /** + * Sets the alpha (opacity) of the stroke + * + * UNIMPLEMENTED: Needs implementation in Native. + */ + public void setStrokeAlpha(float alpha) { + strokeAlpha = alpha; + } + + public void setStrokeColor(int color) { + strokeColor = color; + } + + /** + * UNIMPLEMENTED: Needs implementation in Native. + * https://github.com/mapbox/mapbox-gl-native/issues/1737 + * @return stroke width as float + */ + public void setStrokeWidth(float width) { + strokeWidth = width; + } + + + // TODO: Implement equals of Google Maps Android API +// public boolean equals (Object other) { +// +// } + + // TODO: Implement hashCode of Google Maps Android API +// public int hashCode () { +// +// } +} diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/PolygonOptions.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/PolygonOptions.java new file mode 100644 index 000000000..25ee75019 --- /dev/null +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/PolygonOptions.java @@ -0,0 +1,125 @@ +package com.mapbox.mapboxgl.annotations; + + +import com.mapbox.mapboxgl.geometry.LatLng; + +import java.util.ArrayList; +import java.util.List; + +public class PolygonOptions extends MultiPointOptions { + + public PolygonOptions() { + annotation = new Polygon(); + } + + public PolygonOptions add(LatLng point) { + ((MultiPoint)annotation).points.add(point); + return this; + } + + public PolygonOptions add(LatLng... points) { + for (LatLng point : points) { + add(point); + } + return this; + } + + public PolygonOptions addAll(Iterable points) { + for (LatLng point : points) { + add(point); + } + return this; + } + + /** + * UNIMPLEMENTED: Needs implementation in Native. + * https://github.com/mapbox/mapbox-gl-native/issues/1729 + * + * @param points - an iterable (list) of points for cutting a hole + * @return PolygonOptions - the options object + */ + public PolygonOptions addHole (Iterable points) { + List hole = new ArrayList<>(); + for (LatLng point : points) { + hole.add(point); + } + ((Polygon)annotation).holes.add(hole); + return this; + } + + /** + * Sets the color of the polygon. + * + * @param color - the color in ARGB format + * @return PolygonOptions - the options object + */ + public PolygonOptions fillColor(int color) { + ((Polygon)annotation).fillColor = color; + return this; + } + + public int getFillColor() { + return ((Polygon)annotation).fillColor; + } + + /** + * UNIMPLEMENTED: Needs implementation in Native. + * https://github.com/mapbox/mapbox-gl-native/issues/1729 + * + * @return a list of lists of points for cutting holes + */ + public List> getHoles() { + return ((Polygon)annotation).holes; + } + + public Polygon getPolygon() { + return ((Polygon)annotation); + } + + public int getStrokeColor() { + return ((Polygon)annotation).strokeColor; + } + + /** + * UNIMPLEMENTED: Needs implementation in Native. + * https://github.com/mapbox/mapbox-gl-native/issues/1737 + * + * @return stroke width as float + */ + public float getStrokeWidth() { + return ((Polygon)annotation).strokeWidth; + } + + /** + * Sets the color of the stroke of the polygon. + * + * @param color - the color in ARGB format + * @return PolygonOptions - the options object + */ + public PolygonOptions strokeColor(int color) { + ((Polygon)annotation).strokeColor = color; + return this; + } + + /** + * UNIMPLEMENTED: Needs implementation in Native. + * https://github.com/mapbox/mapbox-gl-native/issues/1737 + * + * @return stroke width as float + */ + public PolygonOptions strokeWidth(float width) { + ((Polygon)annotation).strokeWidth = width; + return this; + } + + public PolygonOptions visible(boolean visible) { + annotation.visible = visible; + return this; + } + + // TODO: Implement writeToParcel of Google Maps Android API +// public void writeToParcel(Parcel out, int flags) { +// +// } + +} diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/Polyline.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/Polyline.java new file mode 100644 index 000000000..764723833 --- /dev/null +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/Polyline.java @@ -0,0 +1,51 @@ +package com.mapbox.mapboxgl.annotations; + +import android.graphics.Color; + +import com.mapbox.mapboxgl.geometry.LatLng; + +import java.util.List; + +public class Polyline extends MultiPoint { + + int color = Color.BLACK; // default color is black + float width = 10; // As specified by Google API Docs (in pixels) + + public Polyline() { + super(); + } + + public int getColor() { + return color; + } + + public float getWidth() { + return width; + } + + + /** + * Sets the color of the polyline. + * + * @param color - the color in ARGB format + */ + public void setColor(int color) { + this.color = color; + } + + + /** + * Sets the width of the polyline. + * + * @param width in pixels + * @return + */ + public void setWidth(float width) { + this.width = width; + } + + // TODO: Implement equals of Google Maps Android API +// public boolean equals(Object other) { +// +// } +} diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/PolylineOptions.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/PolylineOptions.java new file mode 100644 index 000000000..d825399f7 --- /dev/null +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/annotations/PolylineOptions.java @@ -0,0 +1,75 @@ +package com.mapbox.mapboxgl.annotations; + +import com.mapbox.mapboxgl.geometry.LatLng; + +import java.util.List; + +public class PolylineOptions extends MultiPointOptions { + + public PolylineOptions() { + annotation = new Polyline(); + } + + public PolylineOptions add(LatLng point) { + ((MultiPoint)annotation).points.add(point); + return this; + } + + public PolylineOptions add(LatLng... points) { + for (LatLng point : points) { + add(point); + } + return this; + } + + public PolylineOptions addAll(Iterable points) { + for (LatLng point : points) { + add(point); + } + return this; + } + + /** + * Sets the color of the polyline. + * + * @param color - the color in ARGB format + */ + public PolylineOptions color(int color) { + ((Polyline)annotation).color = color; + return this; + } + + public int getColor() { + return ((Polyline)annotation).color; + } + + public Polyline getPolyline() { + return ((Polyline)annotation); + } + + public float getWidth() { + return ((Polyline)annotation).width; + } + + public PolylineOptions visible(boolean visible) { + annotation.visible = visible; + return this; + } + + /** + * Sets the width of the polyline. + * + * @param width in pixels + * @return + */ + public PolylineOptions width(float width) { + ((Polyline)annotation).width = width; + return this; + } + + // TODO: Implement writeToParcel of Google Maps Android API +// public void writeToParcel(Parcel out, int flags) { +// +// } + +} diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/geometry/BoundingBox.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/geometry/BoundingBox.java index cb0409d3e..091ac4d06 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/geometry/BoundingBox.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/geometry/BoundingBox.java @@ -119,15 +119,7 @@ public final class BoundingBox implements Parcelable, Serializable { @Override public String toString() { - return new StringBuffer().append("N:") - .append(this.mLatNorth) - .append("; E:") - .append(this.mLonEast) - .append("; S:") - .append(this.mLatSouth) - .append("; W:") - .append(this.mLonWest) - .toString(); + return "N:" + this.mLatNorth + "; E:" + this.mLonEast + "; S:" + this.mLatSouth + "; W:" + this.mLonWest; } /** @@ -164,17 +156,8 @@ public final class BoundingBox implements Parcelable, Serializable { */ public boolean equals(final BoundingBox other) { - if (other == null) { - return false; - } - if (other == this) { - return true; - } + return other != null && (other == this || mLatNorth == other.getLatNorth() && mLatSouth == other.getLatSouth() && mLonEast == other.getLonEast() && mLonWest == other.getLonWest()); - return mLatNorth == other.getLatNorth() - && mLatSouth == other.getLatSouth() - && mLonEast == other.getLonEast() - && mLonWest == other.getLonWest(); } /** diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/geometry/LatLng.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/geometry/LatLng.java index 9b0991b08..141f0dc96 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/geometry/LatLng.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/geometry/LatLng.java @@ -97,18 +97,8 @@ public class LatLng implements ILatLng, Parcelable, Serializable { LatLng latLng = (LatLng) o; - if (Double.compare(latLng.altitude, altitude) != 0) { - return false; - } + return Double.compare(latLng.altitude, altitude) == 0 && Double.compare(latLng.latitude, latitude) == 0 && Double.compare(latLng.longitude, longitude) == 0; - if (Double.compare(latLng.latitude, latitude) != 0) { - return false; - } - if (Double.compare(latLng.longitude, longitude) != 0) { - return false; - } - - return true; } @Override diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/geometry/LatLngZoom.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/geometry/LatLngZoom.java index f1d2862b2..5d841477a 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/geometry/LatLngZoom.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/geometry/LatLngZoom.java @@ -56,11 +56,8 @@ public class LatLngZoom extends LatLng implements Parcelable, Serializable { LatLngZoom that = (LatLngZoom) o; - if (Double.compare(that.zoom, zoom) != 0) { - return false; - } + return Double.compare(that.zoom, zoom) == 0; - return true; } @Override diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/geometry/ProjectedMeters.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/geometry/ProjectedMeters.java index d28d86887..156d4355b 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/geometry/ProjectedMeters.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/geometry/ProjectedMeters.java @@ -56,14 +56,8 @@ public class ProjectedMeters implements IProjectedMeters, Parcelable, Serializab ProjectedMeters projectedMeters = (ProjectedMeters) o; - if (Double.compare(projectedMeters.easting, easting) != 0) { - return false; - } - if (Double.compare(projectedMeters.northing, northing) != 0) { - return false; - } + return Double.compare(projectedMeters.easting, easting) == 0 && Double.compare(projectedMeters.northing, northing) == 0; - return true; } @Override diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/http/HTTPContext.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/http/HTTPContext.java new file mode 100644 index 000000000..4558bbfe3 --- /dev/null +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/http/HTTPContext.java @@ -0,0 +1,100 @@ +package com.mapbox.mapboxgl.http; + +import com.mapbox.mapboxgl.constants.MapboxConstants; +import com.squareup.okhttp.Call; +import com.squareup.okhttp.Callback; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.Response; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.ProtocolException; +import java.net.SocketException; +import java.net.UnknownHostException; + +import javax.net.ssl.SSLException; + +class HTTPContext { + + private static final int CONNECTION_ERROR = 0; + private static final int TEMPORARY_ERROR = 1; + private static final int PERMANENT_ERROR = 2; + + private static HTTPContext mInstance = null; + + private OkHttpClient mClient; + + private HTTPContext() { + super(); + mClient = new OkHttpClient(); + } + + public static HTTPContext getInstance() { + if (mInstance == null) { + mInstance = new HTTPContext(); + } + + return mInstance; + } + + public HTTPRequest createRequest(long nativePtr, String resourceUrl, String userAgent, String etag, String modified) { + return new HTTPRequest(nativePtr, resourceUrl, userAgent, etag, modified); + } + + public class HTTPRequest implements Callback { + private long mNativePtr = 0; + + private Call mCall; + private Request mRequest; + + private native void nativeOnFailure(long nativePtr, int type, String message); + private native void nativeOnResponse(long nativePtr, int code, String message, String etag, String modified, String cacheControl, String expires, byte[] body); + + private HTTPRequest(long nativePtr, String resourceUrl, String userAgent, String etag, String modified) { + mNativePtr = nativePtr; + Request.Builder builder = new Request.Builder().url(resourceUrl).tag(resourceUrl.toLowerCase(MapboxConstants.MAPBOX_LOCALE)).addHeader("User-Agent", userAgent); + if (etag.length() > 0) { + builder = builder.addHeader("If-None-Match", etag); + } else if (modified.length() > 0) { + builder = builder.addHeader("If-Modified-Since", modified); + } + mRequest = builder.build(); + } + + public void start() { + mCall = HTTPContext.getInstance().mClient.newCall(mRequest); + mCall.enqueue(this); + } + + public void cancel() { + mCall.cancel(); + } + + @Override + public void onFailure(Request request, IOException e) { + int type = PERMANENT_ERROR; + if ((e instanceof UnknownHostException) || (e instanceof SocketException) || (e instanceof ProtocolException) || (e instanceof SSLException)) { + type = CONNECTION_ERROR; + } else if ((e instanceof InterruptedIOException)) { + type = TEMPORARY_ERROR; + } + nativeOnFailure(mNativePtr, type, e.getMessage()); + } + + @Override + public void onResponse(Response response) throws IOException { + byte[] body; + try { + body = response.body().bytes(); + } catch (IOException e) { + onFailure(mRequest, e); + return; + } finally { + response.body().close(); + } + + nativeOnResponse(mNativePtr, response.code(), response.message(), response.header("ETag"), response.header("Last-Modified"), response.header("Cache-Control"), response.header("Expires"), body); + } + } +} diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/util/JavaFileSource.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/util/JavaFileSource.java deleted file mode 100644 index bd8624188..000000000 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/util/JavaFileSource.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.mapbox.mapboxgl.util; - -import com.mapbox.mapboxgl.constants.MapboxConstants; -import com.squareup.okhttp.Callback; -import com.squareup.okhttp.OkHttpClient; -import com.squareup.okhttp.Request; - -public class JavaFileSource { - - private static JavaFileSource instance = null; - - // Single reference to OkHttp for performance gains - private OkHttpClient client; - - /** - * Private Constructor to support Singleton pattern - */ - private JavaFileSource() { - super(); - client = new OkHttpClient(); - } - - /** - * Get the singleton instance of JavaFileSource - * @return Reference to the Singleton Instance of JavaFileSource - */ - public static JavaFileSource getInstance() { - if (instance == null) { - instance = new JavaFileSource(); - } - return instance; - } - - /** - * Make an HTTP Request - * @param resourceUrl URL to resource - * @param callback Callback class - */ - public void request(final String resourceUrl, final Callback callback) { - Request request = new Request.Builder().url(resourceUrl).tag(resourceUrl.toLowerCase(MapboxConstants.MAPBOX_LOCALE)).build(); - client.newCall(request).enqueue(callback); - } - - /** - * Attempt to cancel HTTP Request made - * @param resourceUrl URL of request to cancel - */ - public void cancel(final String resourceUrl) { - client.cancel(resourceUrl.toLowerCase(MapboxConstants.MAPBOX_LOCALE)); - } -} diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java index f5ebad4fb..614ccdb11 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java @@ -1,50 +1,85 @@ package com.mapbox.mapboxgl.views; -import android.annotation.TargetApi; +import android.app.ActivityManager; +import android.app.Dialog; import android.content.BroadcastReceiver; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Matrix; import android.graphics.PointF; +import android.graphics.RectF; +import android.graphics.SurfaceTexture; +import android.hardware.GeomagneticField; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.location.Location; import android.net.ConnectivityManager; import android.net.NetworkInfo; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; +import android.support.v4.content.ContextCompat; import android.support.v4.view.GestureDetectorCompat; import android.support.v4.view.ScaleGestureDetectorCompat; +import android.support.v7.app.AlertDialog; +import android.text.TextUtils; import android.view.GestureDetector; +import android.view.Gravity; import android.view.ScaleGestureDetector; import android.util.AttributeSet; import android.view.InputDevice; import android.view.KeyEvent; import android.view.MotionEvent; -import android.view.SurfaceHolder; -import android.view.SurfaceView; +import android.view.Surface; +import android.view.TextureView; import android.view.View; import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.FrameLayout; +import android.widget.ImageView; import android.widget.ZoomButtonsController; -import android.util.Log; - import com.almeros.android.multitouch.gesturedetectors.RotateGestureDetector; import com.almeros.android.multitouch.gesturedetectors.TwoFingerGestureDetector; +import com.mapbox.mapboxgl.annotations.Annotation; +import com.mapbox.mapboxgl.annotations.Marker; +import com.mapbox.mapboxgl.annotations.MarkerOptions; +import com.mapbox.mapboxgl.annotations.Polygon; +import com.mapbox.mapboxgl.annotations.PolygonOptions; +import com.mapbox.mapboxgl.annotations.Polyline; +import com.mapbox.mapboxgl.annotations.PolylineOptions; +import com.mapbox.mapboxgl.geometry.BoundingBox; import com.mapbox.mapboxgl.geometry.LatLng; import com.mapbox.mapboxgl.geometry.LatLngZoom; - -import org.apache.commons.validator.routines.UrlValidator; - +import com.mapzen.android.lost.api.LocationListener; +import com.mapzen.android.lost.api.LocationRequest; +import com.mapzen.android.lost.api.LocationServices; +import com.mapzen.android.lost.api.LostApiClient; +import com.squareup.okhttp.HttpUrl; +import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.Iterator; import java.util.List; // Custom view that shows a Map // Based on SurfaceView as we use OpenGL ES to render -public class MapView extends SurfaceView { +public class MapView extends FrameLayout implements LocationListener { // // Static members // + private static final String TAG = "MapView"; // Used for animation private static final long ANIMATION_DURATION = 300; @@ -60,8 +95,41 @@ public class MapView extends SurfaceView { private static final String STATE_DEBUG_ACTIVE = "debugActive"; private static final String STATE_STYLE_URL = "styleUrl"; private static final String STATE_ACCESS_TOKEN = "accessToken"; - private static final String STATE_CLASSES = "classes"; + private static final String STATE_STYLE_CLASSES = "styleClasses"; private static final String STATE_DEFAULT_TRANSITION_DURATION = "defaultTransitionDuration"; + private static final String STATE_MY_LOCATION_ENABLED = "myLocationEnabled"; + private static final String STATE_USER_LOCATION_TRACKING_MODE = "userLocationTrackingMode"; + private static final String STATE_COMPASS_ENABLED = "compassEnabled"; + private static final String STATE_COMPASS_GRAVITY = "compassGravity"; + private static final String STATE_COMPASS_MARGIN_LEFT = "compassMarginLeft"; + private static final String STATE_COMPASS_MARGIN_TOP = "compassMarginTop"; + private static final String STATE_COMPASS_MARGIN_RIGHT = "compassMarginRight"; + private static final String STATE_COMPASS_MARGIN_BOTTOM = "compassMarginBottom"; + private static final String STATE_LOGO_GRAVITY = "logoGravity"; + private static final String STATE_LOGO_MARGIN_LEFT = "logoMarginLeft"; + private static final String STATE_LOGO_MARGIN_TOP = "logoMarginTop"; + private static final String STATE_LOGO_MARGIN_RIGHT = "logoMarginRight"; + private static final String STATE_LOGO_MARGIN_BOTTOM = "logoMarginBottom"; + private static final String STATE_ATTRIBUTION_GRAVITY = "attrGravity"; + private static final String STATE_ATTRIBUTION_MARGIN_LEFT = "attrMarginLeft"; + private static final String STATE_ATTRIBUTION_MARGIN_TOP = "attrMarginTop"; + private static final String STATE_ATTRIBUTION_MARGIN_RIGHT = "attrMarginRight"; + private static final String STATE_ATTRIBUTION_MARGIN_BOTTOM = "atrrMarginBottom"; + + // Used for positioning views + private static final float DIMENSION_SEVEN_DP = 7f; + private static final float DIMENSION_TEN_DP = 10f; + private static final float DIMENSION_SIXTEEN_DP = 16f; + private static final float DIMENSION_SEVENTYSIX_DP = 76f; + + private static final int ATTRIBUTION_INDEX_IMPROVE_THIS_MAP = 2; + + /** + * Every annotation that has been added to the map. + */ + private List mAnnotations = new ArrayList<>(); + private List mAnnotationsNearLastTap = new ArrayList<>(); + private Annotation mSelectedAnnotation = null; // // Instance members @@ -90,6 +158,125 @@ public class MapView extends SurfaceView { // Holds the context private Context mContext; + // Used for GPS / Location + private boolean mIsMyLocationEnabled = false; + private LostApiClient mLocationClient; + private LocationRequest mLocationRequest; + private ImageView mGpsMarker; + private Location mGpsLocation; + + public enum UserLocationTrackingMode { + NONE, FOLLOW, FOLLOW_BEARING + } + private UserLocationTrackingMode mUserLocationTrackingMode = UserLocationTrackingMode.FOLLOW; + + // Used for compass + private boolean mIsCompassEnabled = true; + private ImageView mCompassView; + private SensorManager mSensorManager; + private Sensor mSensorAccelerometer; + private Sensor mSensorMagneticField; + private CompassListener mCompassListener; + private float[] mValuesAccelerometer = new float[3]; + private float[] mValuesMagneticField = new float[3]; + private float[] mMatrixR = new float[9]; + private float[] mMatrixI = new float[9]; + private float[] mMatrixValues = new float[3]; + private float mCompassBearing; + private boolean mCompassValid = false; + + // Used for MapboxLogo + private ImageView mLogoView; + + // Used for attributions control + private ImageView mAttributionsView; + + // Used for map toggle mode + private long t0 = new Date().getTime(); + + // Used to manage Event Listeners + private ArrayList mOnMapChangedListener; + + private enum MapChange { + MapChangeNullChange(-1), + MapChangeRegionWillChange(0), + MapChangeRegionWillChangeAnimated(1), + MapChangeRegionIsChanging(2), + MapChangeRegionDidChange(3), + MapChangeRegionDidChangeAnimated(4), + MapChangeWillStartLoadingMap(5), + MapChangeDidFinishLoadingMap(6), + MapChangeDidFailLoadingMap(7), + MapChangeWillStartRenderingFrame(8), + MapChangeDidFinishRenderingFrame(9), + MapChangeDidFinishRenderingFrameFullyRendered(10), + MapChangeWillStartRenderingMap(11), + MapChangeDidFinishRenderingMap(12), + MapChangeDidFinishRenderingMapFullyRendered(13); + + private int value; + + private MapChange(int value) { + this.value = value; + } + + public static MapChange fromInteger(int value) { + switch (value) { + case -1: + return MapChange.MapChangeNullChange; + case 0: + return MapChange.MapChangeRegionWillChange; + case 1: + return MapChange.MapChangeRegionWillChangeAnimated; + case 2: + return MapChange.MapChangeRegionIsChanging; + case 3: + return MapChange.MapChangeRegionDidChange; + case 4: + return MapChange.MapChangeRegionDidChangeAnimated; + case 5: + return MapChange.MapChangeWillStartLoadingMap; + case 6: + return MapChange.MapChangeDidFinishLoadingMap; + case 7: + return MapChange.MapChangeDidFailLoadingMap; + case 8: + return MapChange.MapChangeWillStartRenderingFrame; + case 9: + return MapChange.MapChangeDidFinishRenderingFrame; + case 10: + return MapChange.MapChangeDidFinishRenderingFrameFullyRendered; + case 11: + return MapChange.MapChangeWillStartRenderingMap; + case 12: + return MapChange.MapChangeDidFinishRenderingMap; + case 13: + return MapChange.MapChangeDidFinishRenderingMapFullyRendered; + default: + return null; + } + } + } + + public interface OnFlingListener { + void onFling(); + } + + public interface OnScrollListener { + void onScroll(); + } + + private OnFlingListener onFlingListener; + private OnScrollListener onScrollListener; + + public void setOnScrollListener(OnScrollListener onScrollListener) { + this.onScrollListener = onScrollListener; + } + + public void setOnFlingListener(OnFlingListener onFlingListener) { + this.onFlingListener = onFlingListener; + } + // // Properties // @@ -109,6 +296,19 @@ public class MapView extends SurfaceView { initialize(context, null); } + public MapView(Context context, @NonNull String accessToken) { + super(context); + initialize(context, null); + setAccessToken(accessToken); + } + + public MapView(Context context, @NonNull String accessToken, String styleUrl) { + super(context); + initialize(context, null); + setAccessToken(accessToken); + setStyleUrl(styleUrl); + } + // Called when properties are being set from XML public MapView(Context context, AttributeSet attrs) { super(context, attrs); @@ -131,6 +331,9 @@ public class MapView extends SurfaceView { // Save the context mContext = context; + TextureView textureView = new TextureView(mContext); + addView(textureView); + // Check if we are in Eclipse UI editor if (isInEditMode()) { return; @@ -138,6 +341,8 @@ public class MapView extends SurfaceView { // Get the screen's density mScreenDensity = context.getResources().getDisplayMetrics().density; + int tenDp = (int)(10 * mScreenDensity); + int sixteenDp = (int)(16 * mScreenDensity); // Get the cache path String cachePath = context.getCacheDir().getAbsolutePath(); @@ -145,27 +350,15 @@ public class MapView extends SurfaceView { String apkPath = context.getPackageCodePath(); // Create the NativeMapView - mNativeMapView = new NativeMapView(this, cachePath, dataPath, apkPath); - - // Load the attributes - TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MapView, 0, 0); - try { - double centerLatitude = typedArray.getFloat(R.styleable.MapView_centerLatitude, 0.0f); - double centerLongitude = typedArray.getFloat(R.styleable.MapView_centerLongitude, 0.0f); - LatLng centerCoordinate = new LatLng(centerLatitude, centerLongitude); - setCenterCoordinate(centerCoordinate); - setZoomLevel(typedArray.getFloat(R.styleable.MapView_zoomLevel, 0.0f)); // need to set zoom level first because of limitation on rotating when zoomed out - setDirection(typedArray.getFloat(R.styleable.MapView_direction, 0.0f)); - setZoomEnabled(typedArray.getBoolean(R.styleable.MapView_zoomEnabled, true)); - setScrollEnabled(typedArray.getBoolean(R.styleable.MapView_scrollEnabled, true)); - setRotateEnabled(typedArray.getBoolean(R.styleable.MapView_rotateEnabled, true)); - setDebugActive(typedArray.getBoolean(R.styleable.MapView_debugActive, false)); - if (typedArray.getString(R.styleable.MapView_styleUrl) != null) { - setStyleUrl(typedArray.getString(R.styleable.MapView_styleUrl)); - } - } finally { - typedArray.recycle(); + int availableProcessors = Runtime.getRuntime().availableProcessors(); + ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo(); + ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + activityManager.getMemoryInfo(memoryInfo); + long maxMemory = memoryInfo.availMem; + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + maxMemory = memoryInfo.totalMem; } + mNativeMapView = new NativeMapView(this, cachePath, dataPath, apkPath, mScreenDensity, availableProcessors, maxMemory); // Ensure this view is interactable setClickable(true); @@ -174,12 +367,8 @@ public class MapView extends SurfaceView { setFocusableInTouchMode(true); requestFocus(); - // Register the SurfaceHolder callbacks - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { - getHolder().addCallback(new Callbacks2()); - } else { - getHolder().addCallback(new Callbacks()); - } + // Register the TextureView callbacks + textureView.setSurfaceTextureListener(new SurfaceTextureListener()); // Touch gesture detectors mGestureDetector = new GestureDetectorCompat(context, new GestureListener()); @@ -203,6 +392,226 @@ public class MapView extends SurfaceView { boolean isConnected = (activeNetwork != null) && activeNetwork.isConnectedOrConnecting(); onConnectivityChanged(isConnected); } + + // Setup Location Services + mLocationClient = new LostApiClient.Builder(getContext()).build(); + mLocationRequest = LocationRequest.create() + .setInterval(1000) + .setSmallestDisplacement(1) + .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); + + // Setup Compass + mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE); + mSensorAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); + mSensorMagneticField = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD); + mCompassListener = new CompassListener(); + + mCompassView = new ImageView(mContext); + mCompassView.setImageDrawable(ContextCompat.getDrawable(getContext(), R.drawable.compass)); + mCompassView.setContentDescription(getResources().getString(R.string.compassContentDescription)); + LayoutParams lp = new FrameLayout.LayoutParams((int)(48 * mScreenDensity), (int)(48 * mScreenDensity)); + mCompassView.setLayoutParams(lp); + addView(mCompassView); + mCompassView.setOnClickListener(new CompassOnClickListener()); + + // Setup Mapbox logo + mLogoView = new ImageView(mContext); + mLogoView.setImageDrawable(ContextCompat.getDrawable(mContext, R.drawable.ic_logo_mapbox)); + mLogoView.setContentDescription(getResources().getString(R.string.mapboxIconContentDescription)); + LayoutParams logoParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + mLogoView.setLayoutParams(logoParams); + addView(mLogoView); + + // Setup Attributions control + mAttributionsView = new ImageView(mContext); + mAttributionsView.setClickable(true); + mAttributionsView.setImageResource(R.drawable.ic_info_selector); + int attrPadding = (int) (DIMENSION_SEVEN_DP * mScreenDensity); + mAttributionsView.setPadding(attrPadding, attrPadding, attrPadding, attrPadding); + mAttributionsView.setAdjustViewBounds(true); + mAttributionsView.setContentDescription(getResources().getString(R.string.attributionsIconContentDescription)); + LayoutParams attrParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + mAttributionsView.setLayoutParams(attrParams); + addView(mAttributionsView); + mAttributionsView.setOnClickListener(new AttributionOnClickListener(this)); + + // Setup Support For Listener Tracking + // MapView's internal listener is setup in onCreate() + mOnMapChangedListener = new ArrayList<>(); + + // Load the attributes + TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MapView, 0, 0); + try { + double centerLatitude = typedArray.getFloat(R.styleable.MapView_centerLatitude, 0.0f); + double centerLongitude = typedArray.getFloat(R.styleable.MapView_centerLongitude, 0.0f); + LatLng centerCoordinate = new LatLng(centerLatitude, centerLongitude); + setCenterCoordinate(centerCoordinate); + setZoomLevel(typedArray.getFloat(R.styleable.MapView_zoomLevel, 0.0f)); // need to set zoom level first because of limitation on rotating when zoomed out + setDirection(typedArray.getFloat(R.styleable.MapView_direction, 0.0f)); + setZoomEnabled(typedArray.getBoolean(R.styleable.MapView_zoomEnabled, true)); + setScrollEnabled(typedArray.getBoolean(R.styleable.MapView_scrollEnabled, true)); + setRotateEnabled(typedArray.getBoolean(R.styleable.MapView_rotateEnabled, true)); + setDebugActive(typedArray.getBoolean(R.styleable.MapView_debugActive, false)); + if (typedArray.getString(R.styleable.MapView_styleUrl) != null) { + setStyleUrl(typedArray.getString(R.styleable.MapView_styleUrl)); + } + if (typedArray.getString(R.styleable.MapView_accessToken) != null) { + setAccessToken(typedArray.getString(R.styleable.MapView_accessToken)); + } + if (typedArray.getString(R.styleable.MapView_styleClasses) != null) { + List styleClasses = Arrays.asList(typedArray.getString(R.styleable.MapView_styleClasses).split("\\s*,\\s*")); + for (String styleClass : styleClasses) { + if (styleClass.length() == 0) { + styleClasses.remove(styleClass); + } + } + setStyleClasses(styleClasses); + } + + // Compass + setCompassEnabled(typedArray.getBoolean(R.styleable.MapView_compassEnabled, true)); + setCompassGravity(typedArray.getInt(R.styleable.MapView_compassGravity, Gravity.TOP | Gravity.END)); + setWidgetMargins(mCompassView, typedArray.getDimension(R.styleable.MapView_compassMarginLeft, DIMENSION_TEN_DP) + , typedArray.getDimension(R.styleable.MapView_compassMarginTop, DIMENSION_TEN_DP) + , typedArray.getDimension(R.styleable.MapView_compassMarginRight, DIMENSION_TEN_DP) + , typedArray.getDimension(R.styleable.MapView_compassMarginBottom, DIMENSION_TEN_DP)); + + // Logo + setLogoGravity(typedArray.getInt(R.styleable.MapView_logoGravity, Gravity.BOTTOM | Gravity.START)); + setWidgetMargins(mLogoView, typedArray.getDimension(R.styleable.MapView_logoMarginLeft, DIMENSION_SIXTEEN_DP) + , typedArray.getDimension(R.styleable.MapView_logoMarginTop, DIMENSION_SIXTEEN_DP) + , typedArray.getDimension(R.styleable.MapView_logoMarginRight, DIMENSION_SIXTEEN_DP) + , typedArray.getDimension(R.styleable.MapView_logoMarginBottom, DIMENSION_SIXTEEN_DP)); + + // Attribution + setAttributionGravity(typedArray.getInt(R.styleable.MapView_attributionGravity, Gravity.BOTTOM)); + setWidgetMargins(mAttributionsView, typedArray.getDimension(R.styleable.MapView_attributionMarginLeft, DIMENSION_SEVENTYSIX_DP) + , typedArray.getDimension(R.styleable.MapView_attributionMarginTop, DIMENSION_SEVEN_DP) + , typedArray.getDimension(R.styleable.MapView_attributionMarginRight, DIMENSION_SEVEN_DP) + , typedArray.getDimension(R.styleable.MapView_attributionMarginBottom, DIMENSION_SEVEN_DP)); + + setMyLocationEnabled(typedArray.getBoolean(R.styleable.MapView_myLocationEnabled, false)); + } finally { + typedArray.recycle(); + } + } + + // + // Annotations + // + + public void setSprite(String symbol, float scale, Bitmap bitmap) { + if(bitmap.getConfig() != Bitmap.Config.ARGB_8888) { + bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false); + } + ByteBuffer buffer = ByteBuffer.allocate(bitmap.getRowBytes() * bitmap.getHeight()); + bitmap.copyPixelsToBuffer(buffer); + + mNativeMapView.setSprite(symbol, bitmap.getWidth(), bitmap.getHeight(), scale, buffer.array()); + } + + public Marker addMarker(MarkerOptions markerOptions) { + Marker marker = markerOptions.getMarker(); + long id = mNativeMapView.addMarker(marker); + marker.setId(id); // the annotation needs to know its id + marker.setMapView(this); // the annotation needs to know which map view it is in + mAnnotations.add(marker); + return marker; + } + + public Polyline addPolyline(PolylineOptions polylineOptions) { + Polyline polyline = polylineOptions.getPolyline(); + long id = mNativeMapView.addPolyline(polyline); + polyline.setId(id); + polyline.setMapView(this); + mAnnotations.add(polyline); + return polyline; + } + + public Polygon addPolygon(PolygonOptions polygonOptions) { + Polygon polygon = polygonOptions.getPolygon(); + long id = mNativeMapView.addPolygon(polygon); + polygon.setId(id); + polygon.setMapView(this); + mAnnotations.add(polygon); + return polygon; + } + + public List addPolygons(List polygonOptions) { + List polygons = new ArrayList<>(); + for(PolygonOptions popts : polygonOptions) { + polygons.add(popts.getPolygon()); + } + + long[] ids = mNativeMapView.addPolygons(polygons); + + for(int i=0; i < polygons.size(); i++) { + polygons.get(i).setId(ids[i]); + polygons.get(i).setMapView(this); + mAnnotations.add(polygons.get(i)); + } + + return Collections.unmodifiableList(polygons); + } + + private void removeAnnotationsWithId(long annotationId){ + for (Iterator iterator = mAnnotations.iterator(); iterator.hasNext();) { + Annotation annotation = iterator.next(); + if (annotation instanceof Marker) { + ((Marker)annotation).hideInfoWindow(); + } + if (annotation.getId() == annotationId) { + iterator.remove(); + } + } + } + + public void removeAnnotation(Annotation annotation) { + if (annotation instanceof Marker) { + ((Marker)annotation).hideInfoWindow(); + } + long id = annotation.getId(); + mNativeMapView.removeAnnotation(id); + mAnnotations.remove(annotation); + } + + public void removeAnnotation(long annotationId) { + mNativeMapView.removeAnnotation(annotationId); + removeAnnotationsWithId(annotationId); + } + + public void removeAnnotations() { + long[] ids = new long[mAnnotations.size()]; + for(int i = 0; i < mAnnotations.size(); i++) { + Annotation annotation = mAnnotations.get(i); + long id = annotation.getId(); + ids[i] = id; + if (annotation instanceof Marker) { + ((Marker)annotation).hideInfoWindow(); + } + } + mNativeMapView.removeAnnotations(ids); + mAnnotations.clear(); + } + + public List getAnnotations() { + return Collections.unmodifiableList(mAnnotations); + } + + public List getAnnotationsInBounds(BoundingBox bbox) { + List annotations = new ArrayList<>(); + long[] ids = mNativeMapView.getAnnotationsInBounds(bbox); + List idsList = new ArrayList<>(); + for(int i = 0; i < ids.length; i++) { + idsList.add(new Long(ids[i])); + } + for(int i = 0; i < mAnnotations.size(); i++) { + Annotation annotation = mAnnotations.get(i); + if (annotation instanceof Marker && idsList.contains(annotation.getId())) { + annotations.add(annotation); + } + } + return annotations; } // @@ -219,6 +628,7 @@ public class MapView extends SurfaceView { public void setCenterCoordinate(LatLng centerCoordinate, boolean animated) { long duration = animated ? ANIMATION_DURATION : 0; + mNativeMapView.cancelTransitions(); mNativeMapView.setLatLng(centerCoordinate, duration); } @@ -229,6 +639,7 @@ public class MapView extends SurfaceView { public void setCenterCoordinate(LatLngZoom centerCoordinate, boolean animated) { long duration = animated ? ANIMATION_DURATION : 0; + mNativeMapView.cancelTransitions(); mNativeMapView.setLatLngZoom(centerCoordinate, duration); } @@ -251,14 +662,17 @@ public class MapView extends SurfaceView { public void setDirection(double direction, boolean animated) { long duration = animated ? ANIMATION_DURATION : 0; + mNativeMapView.cancelTransitions(); mNativeMapView.setBearing(-direction, duration); } public void resetPosition() { + mNativeMapView.cancelTransitions(); mNativeMapView.resetPosition(); } public void resetNorth() { + mNativeMapView.cancelTransitions(); mNativeMapView.resetNorth(); } @@ -272,6 +686,7 @@ public class MapView extends SurfaceView { public void setZoomLevel(double zoomLevel, boolean animated) { long duration = animated ? ANIMATION_DURATION : 0; + mNativeMapView.cancelTransitions(); mNativeMapView.setZoom(zoomLevel, duration); } @@ -304,26 +719,41 @@ public class MapView extends SurfaceView { } public boolean isDebugActive() { - return mNativeMapView.getDebug(); + return mNativeMapView.getDebug() || mNativeMapView.getCollisionDebug(); } public void setDebugActive(boolean debugActive) { mNativeMapView.setDebug(debugActive); + mNativeMapView.setCollisionDebug(debugActive); } public void toggleDebug() { mNativeMapView.toggleDebug(); + mNativeMapView.toggleCollisionDebug(); + } + + public UserLocationTrackingMode getUserLocationTrackingMode() { + return mUserLocationTrackingMode; + } + + public void setUserLocationTrackingMode(UserLocationTrackingMode userLocationTrackingMode) { + this.mUserLocationTrackingMode = userLocationTrackingMode; + } + + public boolean isFullyLoaded() { + return mNativeMapView.isFullyLoaded(); } private void validateStyleUrl(String url) { - String[] schemes = {"http", "https", "file", "asset"}; - UrlValidator urlValidator = new UrlValidator(schemes, UrlValidator.NO_FRAGMENTS | UrlValidator.ALLOW_LOCAL_URLS); - if (!urlValidator.isValid(url)) { - throw new RuntimeException("Style URL is not a valid http, https, file or asset URL."); + url = url.replaceFirst("asset://", "http://"); + HttpUrl parsedUrl = HttpUrl.parse(url); + if (parsedUrl == null) { + throw new RuntimeException("Style URL is not a valid http, https or asset URL."); } } public void setStyleUrl(String url) { + validateStyleUrl(url); mStyleUrl = url; mNativeMapView.setStyleUrl(url); } @@ -332,14 +762,14 @@ public class MapView extends SurfaceView { return mStyleUrl; } - private void validateAccessToken(String accessToken) { + private void validateAccessToken(@NonNull String accessToken) { - if (!accessToken.startsWith("pk.") && !accessToken.startsWith("sk.")) { + if (TextUtils.isEmpty(accessToken) || (!accessToken.startsWith("pk.") && !accessToken.startsWith("sk."))) { throw new RuntimeException("Using MapView requires setting a valid access token. See the README.md"); } } - public void setAccessToken(String accessToken) { + public void setAccessToken(@NonNull String accessToken) { validateAccessToken(accessToken); mNativeMapView.setAccessToken(accessToken); } @@ -348,47 +778,60 @@ public class MapView extends SurfaceView { return mNativeMapView.getAccessToken(); } - public List getClasses() { - return mNativeMapView.getClasses(); + public List getStyleClasses() { + return Collections.unmodifiableList(mNativeMapView.getClasses()); } - public void setClasses(List classes) { - setClasses(classes, 0); + public void setStyleClasses(List styleClasses) { + setStyleClasses(styleClasses, 0); } - public void setClasses(List classes, long transitionDuration) { + public void setStyleClasses(List styleClasses, long transitionDuration) { mNativeMapView.setDefaultTransitionDuration(transitionDuration); - mNativeMapView.setClasses(classes); + mNativeMapView.setClasses(styleClasses); } - public void addClass(String clazz) { - mNativeMapView.addClass(clazz); + public void addStyleClass(String styleClass) { + mNativeMapView.addClass(styleClass); } - public void removeClass(String clazz) { - mNativeMapView.removeClass(clazz); + public void removeStyleClass(String styleClass) { + mNativeMapView.removeClass(styleClass); } - public boolean hasClass(String clazz) { - return mNativeMapView.hasClass(clazz); + public boolean hasStyleClass(String styleClass) { + return mNativeMapView.hasClass(styleClass); } - public void removeAllClasses() { - removeAllClasses(0); + public void removeAllStyleClasses() { + removeAllStyleClasses(0); } - public void removeAllClasses(long transitionDuration) { + public void removeAllStyleClasses(long transitionDuration) { mNativeMapView.setDefaultTransitionDuration(transitionDuration); - ArrayList classes = new ArrayList<>(0); - setClasses(classes); + ArrayList styleClasses = new ArrayList<>(0); + setStyleClasses(styleClasses); } public LatLng fromScreenLocation(PointF point) { - return mNativeMapView.latLngForPixel(point); + return mNativeMapView.latLngForPixel(new PointF(point.x / mScreenDensity, point.y / mScreenDensity)); } public PointF toScreenLocation(LatLng location) { - return mNativeMapView.pixelForLatLng(location); + PointF point = mNativeMapView.pixelForLatLng(location); + return new PointF(point.x * mScreenDensity, point.y * mScreenDensity); + } + + public double getTopOffsetPixelsForAnnotationSymbol(@NonNull String symbolName) { + return mNativeMapView.getTopOffsetPixelsForAnnotationSymbol(symbolName); + } + + /** + * Common Screen Density + * @return Screen Density + */ + public float getScreenDensity() { + return mScreenDensity; } // @@ -409,17 +852,49 @@ public class MapView extends SurfaceView { setDebugActive(savedInstanceState.getBoolean(STATE_DEBUG_ACTIVE)); setStyleUrl(savedInstanceState.getString(STATE_STYLE_URL)); setAccessToken(savedInstanceState.getString(STATE_ACCESS_TOKEN)); - List appliedClasses = savedInstanceState.getStringArrayList(STATE_CLASSES); - if (!appliedClasses.isEmpty()) { - setClasses(appliedClasses); + List appliedStyleClasses = savedInstanceState.getStringArrayList(STATE_STYLE_CLASSES); + if (!appliedStyleClasses.isEmpty()) { + setStyleClasses(appliedStyleClasses); } mNativeMapView.setDefaultTransitionDuration(savedInstanceState.getLong(STATE_DEFAULT_TRANSITION_DURATION)); + setMyLocationEnabled(savedInstanceState.getBoolean(STATE_MY_LOCATION_ENABLED)); + setUserLocationTrackingMode((UserLocationTrackingMode) savedInstanceState.getSerializable(STATE_USER_LOCATION_TRACKING_MODE)); + + // Compass + setCompassEnabled(savedInstanceState.getBoolean(STATE_COMPASS_ENABLED)); + setCompassGravity(savedInstanceState.getInt(STATE_COMPASS_GRAVITY)); + setCompassMargins(savedInstanceState.getInt(STATE_COMPASS_MARGIN_LEFT) + , savedInstanceState.getInt(STATE_COMPASS_MARGIN_TOP) + , savedInstanceState.getInt(STATE_COMPASS_MARGIN_RIGHT) + , savedInstanceState.getInt(STATE_COMPASS_MARGIN_BOTTOM)); + + // Logo + setLogoGravity(savedInstanceState.getInt(STATE_LOGO_GRAVITY)); + setLogoMargins(savedInstanceState.getInt(STATE_LOGO_MARGIN_LEFT) + , savedInstanceState.getInt(STATE_LOGO_MARGIN_TOP) + , savedInstanceState.getInt(STATE_LOGO_MARGIN_RIGHT) + , savedInstanceState.getInt(STATE_LOGO_MARGIN_BOTTOM)); + + // Attribution + setAttributionGravity(savedInstanceState.getInt(STATE_ATTRIBUTION_GRAVITY)); + setAttributionMargins(savedInstanceState.getInt(STATE_ATTRIBUTION_MARGIN_LEFT) + , savedInstanceState.getInt(STATE_ATTRIBUTION_MARGIN_TOP) + , savedInstanceState.getInt(STATE_ATTRIBUTION_MARGIN_RIGHT) + , savedInstanceState.getInt(STATE_ATTRIBUTION_MARGIN_BOTTOM)); } + // Force a check for an access token validateAccessToken(getAccessToken()); mNativeMapView.initializeDisplay(); mNativeMapView.initializeContext(); + + addOnMapChangedListener(new OnMapChangedListener() { + @Override + public void onMapChanged(MapChange change) { + updateMap(change); + } + }); } // Called when we need to save instance state @@ -434,8 +909,35 @@ public class MapView extends SurfaceView { outState.putBoolean(STATE_DEBUG_ACTIVE, isDebugActive()); outState.putString(STATE_STYLE_URL, mStyleUrl); outState.putString(STATE_ACCESS_TOKEN, getAccessToken()); - outState.putStringArrayList(STATE_CLASSES, new ArrayList<>(getClasses())); + outState.putStringArrayList(STATE_STYLE_CLASSES, new ArrayList<>(getStyleClasses())); outState.putLong(STATE_DEFAULT_TRANSITION_DURATION, mNativeMapView.getDefaultTransitionDuration()); + outState.putBoolean(STATE_MY_LOCATION_ENABLED, isMyLocationEnabled()); + outState.putSerializable(STATE_USER_LOCATION_TRACKING_MODE, getUserLocationTrackingMode()); + + // Compass + LayoutParams compassParams = (LayoutParams) mCompassView.getLayoutParams(); + outState.putBoolean(STATE_COMPASS_ENABLED, isCompassEnabled()); + outState.putInt(STATE_COMPASS_GRAVITY, compassParams.gravity ); + outState.putInt(STATE_COMPASS_MARGIN_LEFT, compassParams.leftMargin); + outState.putInt(STATE_COMPASS_MARGIN_TOP, compassParams.topMargin); + outState.putInt(STATE_COMPASS_MARGIN_BOTTOM, compassParams.bottomMargin); + outState.putInt(STATE_COMPASS_MARGIN_RIGHT, compassParams.rightMargin); + + // Logo + LayoutParams logoParams = (LayoutParams) mLogoView.getLayoutParams(); + outState.putInt(STATE_LOGO_GRAVITY, logoParams.gravity); + outState.putInt(STATE_LOGO_MARGIN_LEFT, logoParams.leftMargin); + outState.putInt(STATE_LOGO_MARGIN_TOP, logoParams.topMargin); + outState.putInt(STATE_LOGO_MARGIN_RIGHT, logoParams.rightMargin); + outState.putInt(STATE_LOGO_MARGIN_BOTTOM, logoParams.bottomMargin); + + // Attribution + LayoutParams attrParams = (LayoutParams) mAttributionsView.getLayoutParams(); + outState.putInt(STATE_ATTRIBUTION_GRAVITY, attrParams.gravity); + outState.putInt(STATE_ATTRIBUTION_MARGIN_LEFT, attrParams.leftMargin); + outState.putInt(STATE_ATTRIBUTION_MARGIN_TOP, attrParams.topMargin); + outState.putInt(STATE_ATTRIBUTION_MARGIN_RIGHT, attrParams.rightMargin); + outState.putInt(STATE_ATTRIBUTION_MARGIN_BOTTOM, attrParams.bottomMargin); } // Called when we need to clean up @@ -448,7 +950,6 @@ public class MapView extends SurfaceView { // Called when we need to create the GL context // Must be called from Activity onStart public void onStart() { - // Do nothing } // Called when we need to terminate the GL context @@ -463,6 +964,10 @@ public class MapView extends SurfaceView { getContext().unregisterReceiver(mConnectivityReceiver); mConnectivityReceiver = null; + if (mIsMyLocationEnabled) { + toggleGps(false); + }; + mNativeMapView.pause(); } @@ -474,45 +979,57 @@ public class MapView extends SurfaceView { mConnectivityReceiver = new ConnectivityReceiver(); mContext.registerReceiver(mConnectivityReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); + if (mIsMyLocationEnabled) { + toggleGps(true); + } + mNativeMapView.resume(); } - // This class handles SurfaceHolder callbacks - private class Callbacks implements SurfaceHolder.Callback { - - // Called when the native surface buffer has been created - // Must do all EGL/GL ES initialization here - @Override - public void surfaceCreated(SurfaceHolder holder) { - mNativeMapView.createSurface(holder.getSurface()); - } - - // Called when the native surface buffer has been destroyed - // Must do all EGL/GL ES destruction here - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - mNativeMapView.destroySurface(); - } - - // Called when the format or size of the native surface buffer has been - // changed - // Must handle window resizing here. - @Override - public void surfaceChanged(SurfaceHolder holder, int format, int width, - int height) { - mNativeMapView.resize((int) (width / mScreenDensity), (int) (height / mScreenDensity), mScreenDensity, width, height); + public void onSizeChanged(int width, int height, int oldw, int oldh) { + if (!isInEditMode()) { + mNativeMapView.resizeView((int) (width / mScreenDensity), (int) (height / mScreenDensity)); } } - @TargetApi(9) - private class Callbacks2 extends Callbacks implements SurfaceHolder.Callback2 { + // This class handles TextureView callbacks + private class SurfaceTextureListener implements TextureView.SurfaceTextureListener { + + // Called when the native surface texture has been created + // Must do all EGL/GL ES initialization here + @Override + public void onSurfaceTextureAvailable (SurfaceTexture surface, int width, int height) { + mNativeMapView.createSurface(new Surface(surface)); + mNativeMapView.resizeFramebuffer(width, height); + } + + // Called when the native surface texture has been destroyed + // Must do all EGL/GL ES destruction here + @Override + public boolean onSurfaceTextureDestroyed (SurfaceTexture surface) { + mNativeMapView.destroySurface(); + return true; + } + + // Called when the format or size of the native surface texture has been changed + // Must handle window resizing here. + @Override + public void onSurfaceTextureSizeChanged (SurfaceTexture surface, int width, int height) { + mNativeMapView.resizeFramebuffer(width, height); + } // Called when we need to redraw the view // This is called before our view is first visible to prevent an initial // flicker (see Android SDK documentation) - @Override + /*@Override public void surfaceRedrawNeeded(SurfaceHolder holder) { mNativeMapView.update(); + }*/ // TODO call update somewhere? + + // Not used + @Override + public void onSurfaceTextureUpdated (SurfaceTexture surface) { + // Do nothing } } @@ -539,6 +1056,12 @@ public class MapView extends SurfaceView { } } + // Called when the system is running low on memory + // Must be called from Activity onLowMemory + public void onLowMemory() { + mNativeMapView.onLowMemory(); + } + // // Draw events // @@ -565,7 +1088,7 @@ public class MapView extends SurfaceView { } // Called when user touches the screen, all positions are absolute - @Override @TargetApi(14) + @Override public boolean onTouchEvent(@NonNull MotionEvent event) { // Check and ignore non touch or left clicks if ((android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) && (event.getButtonState() != 0) && (event.getButtonState() != MotionEvent.BUTTON_PRIMARY)) { @@ -580,6 +1103,7 @@ public class MapView extends SurfaceView { switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: // First pointer down + mNativeMapView.setGestureInProgress(true); break; case MotionEvent.ACTION_POINTER_DOWN: @@ -596,6 +1120,7 @@ public class MapView extends SurfaceView { long tapInterval = event.getEventTime() - event.getDownTime(); boolean isTap = tapInterval <= ViewConfiguration.getTapTimeout(); boolean inProgress = mRotateGestureDetector.isInProgress() || mScaleGestureDetector.isInProgress(); + mNativeMapView.setGestureInProgress(false); if (mTwoTap && isTap && !inProgress) { PointF focalPoint = TwoFingerGestureDetector.determineFocalPoint(event); @@ -609,6 +1134,7 @@ public class MapView extends SurfaceView { case MotionEvent.ACTION_CANCEL: mTwoTap = false; + mNativeMapView.setGestureInProgress(false); break; } @@ -648,20 +1174,111 @@ public class MapView extends SurfaceView { public boolean onSingleTapUp(MotionEvent e) { // Cancel any animation mNativeMapView.cancelTransitions(); - return true; } - // Called for single taps after a delay @Override public boolean onSingleTapConfirmed(MotionEvent e) { - return false; + // Open / Close InfoWindow + + float x = e.getX(); + float y = e.getY(); + + // flip y direction vertically to match core GL + y = getHeight() - y; + + PointF tapPoint = new PointF(x, y); + + float toleranceWidth = 40 * mScreenDensity; + float toleranceHeight = 60 * mScreenDensity; + + RectF tapRect = new RectF(tapPoint.x - toleranceWidth / 2, tapPoint.y + 2 * toleranceHeight / 3, + tapPoint.x + toleranceWidth / 2, tapPoint.y - 1 * toleranceHeight / 3); + + List corners = Arrays.asList( + fromScreenLocation(new PointF(tapRect.left, tapRect.bottom)), + fromScreenLocation(new PointF(tapRect.left, tapRect.top)), + fromScreenLocation(new PointF(tapRect.right, tapRect.top)), + fromScreenLocation(new PointF(tapRect.right, tapRect.bottom)) + ); + + BoundingBox tapBounds = BoundingBox.fromLatLngs(corners); + + List nearbyAnnotations = getAnnotationsInBounds(tapBounds); + + long newSelectedAnnotationID = -1; + + if (nearbyAnnotations.size() > 0) { + + // there is at least one nearby annotation; select one + // + // first, sort for comparison and iteration + Collections.sort(nearbyAnnotations); + + if (nearbyAnnotations == mAnnotationsNearLastTap) + { + // the selection candidates haven't changed; cycle through them + if (mSelectedAnnotation != null && (mSelectedAnnotation.getId() == mAnnotationsNearLastTap.get(mAnnotationsNearLastTap.size() - 1).getId())) + { + // the selected annotation is the last in the set; cycle back to the first + // note: this could be the selected annotation if only one in set + newSelectedAnnotationID = mAnnotationsNearLastTap.get(0).getId(); + } + else if (mSelectedAnnotation != null) + { + // otherwise increment the selection through the candidates + long currentID = mSelectedAnnotation.getId(); + long result = mAnnotationsNearLastTap.indexOf(mSelectedAnnotation); + newSelectedAnnotationID = mAnnotationsNearLastTap.get((int) result + 1).getId(); + } + else + { + // no current selection; select the first one + newSelectedAnnotationID = mAnnotationsNearLastTap.get(0).getId(); + } + } + else + { + // start tracking a new set of nearby annotations + mAnnotationsNearLastTap = nearbyAnnotations; + + // select the first one + newSelectedAnnotationID = mAnnotationsNearLastTap.get(0).getId(); + } + + } else { + // there are no nearby annotations; deselect if necessary + newSelectedAnnotationID = -1; + } + + if (newSelectedAnnotationID >= 0) { + + for (Annotation annotation : mAnnotations) { + if (annotation instanceof Marker) { + if (annotation.getId() == newSelectedAnnotationID) { + if (mSelectedAnnotation == null || annotation.getId() != mSelectedAnnotation.getId()) { + selectAnnotation(annotation); + } + break; + } + } + } + + } else { + // deselect any selected annotation + if (mSelectedAnnotation != null) { + deselectAnnotation(); + } + } + + return true; } // Called for a long press @Override public void onLongPress(MotionEvent e) { // TODO + performLongClick(); } // Called for flings @@ -689,6 +1306,10 @@ public class MapView extends SurfaceView { mNativeMapView.moveBy(velocityX * duration / 2.0 / mScreenDensity, velocityY * duration / 2.0 / mScreenDensity, (long) (duration * 1000.0f)); + if(onFlingListener != null){ + onFlingListener.onFling(); + } + return true; } @@ -706,6 +1327,11 @@ public class MapView extends SurfaceView { // Scroll the map mNativeMapView.moveBy(-distanceX / mScreenDensity, -distanceY / mScreenDensity); + + if(onScrollListener != null){ + onScrollListener.onScroll(); + } + return true; } } @@ -725,7 +1351,6 @@ public class MapView extends SurfaceView { } mBeginTime = detector.getEventTime(); - return true; } @@ -1080,7 +1705,7 @@ public class MapView extends SurfaceView { // Called for events that don't fit the other handlers // such as mouse scroll events, mouse moves, joystick, trackpad - @Override @TargetApi(12) + @Override public boolean onGenericMotionEvent(MotionEvent event) { // Mouse events //if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) { // this is not available before API 18 @@ -1116,7 +1741,7 @@ public class MapView extends SurfaceView { // Called when the mouse pointer enters or exits the view // or when it fades in or out due to movement - @Override @TargetApi(14) + @Override public boolean onHoverEvent(@NonNull MotionEvent event) { switch (event.getActionMasked()) { case MotionEvent.ACTION_HOVER_ENTER: @@ -1181,26 +1806,45 @@ public class MapView extends SurfaceView { } + /** + * Defines callback for events OnMapChange + */ public interface OnMapChangedListener { - void onMapChanged(); + void onMapChanged(MapChange change); } - private OnMapChangedListener mOnMapChangedListener; + /** + * Add an OnMapChangedListner + * @param listener Listener to add + */ + public void addOnMapChangedListener(@NonNull OnMapChangedListener listener) { + if (listener != null) { + mOnMapChangedListener.add(listener); + } + } - // Adds a listener for onMapChanged - public void setOnMapChangedListener(OnMapChangedListener listener) { - mOnMapChangedListener = listener; + /** + * Remove an OnMapChangedListener + * @param listener Listener to remove + */ + public void removeOnMapChangedListener(@NonNull OnMapChangedListener listener) { + if (listener != null) { + mOnMapChangedListener.remove(listener); + } } // Called when the map view transformation has changed // Called via JNI from NativeMapView // Need to update anything that relies on map state - protected void onMapChanged() { + protected void onMapChanged(int rawChange) { + final MapChange change = MapChange.fromInteger(rawChange); if (mOnMapChangedListener != null) { post(new Runnable() { @Override public void run() { - mOnMapChangedListener.onMapChanged(); + for (OnMapChangedListener listener : mOnMapChangedListener) { + listener.onMapChanged(change); + } } }); } @@ -1229,4 +1873,353 @@ public class MapView extends SurfaceView { }); } } + + // Google Maps Inspired API + + /** + * Gets the status of the my-location layer. + * @return True if the my-location layer is enabled, false otherwise. + */ + public final boolean isMyLocationEnabled () { + return mIsMyLocationEnabled; + } + + /** + * Enables or disables the my-location layer. + * While enabled, the my-location layer continuously draws an indication of a user's current + * location and bearing, and displays UI controls that allow a user to interact with their + * location (for example, to enable or disable camera tracking of their location and bearing). + * @param enabled True to enable; false to disable. + */ + public final void setMyLocationEnabled (boolean enabled) { + mIsMyLocationEnabled = enabled; + toggleGps(enabled); + updateMap(MapChange.MapChangeNullChange); + } + + /** + * Returns the currently displayed user location, or null if there is no location data available. + * @return The currently displayed user location. + */ + public final Location getMyLocation () { + return mGpsLocation; + } + + /** + * Enabled / Disable GPS location updates along with updating the UI + * @param enableGps true if GPS is to be enabled, false if GPS is to be disabled + */ + private void toggleGps(boolean enableGps) { + if (enableGps) { + if (!mLocationClient.isConnected()) { + mGpsLocation = null; + mLocationClient.connect(); + updateLocation(LocationServices.FusedLocationApi.getLastLocation()); + LocationServices.FusedLocationApi.requestLocationUpdates(mLocationRequest, this); + mSensorManager.registerListener(mCompassListener, mSensorAccelerometer, SensorManager.SENSOR_DELAY_UI); + mSensorManager.registerListener(mCompassListener, mSensorMagneticField, SensorManager.SENSOR_DELAY_UI); + } + } else { + if (mLocationClient.isConnected()) { + LocationServices.FusedLocationApi.removeLocationUpdates(this); + mLocationClient.disconnect(); + mGpsLocation = null; + mSensorManager.unregisterListener(mCompassListener, mSensorAccelerometer); + mSensorManager.unregisterListener(mCompassListener, mSensorMagneticField); + } + } + } + + /** + * Gets whether the compass is enabled/disabled. + * @return true if the compass is enabled; false if the compass is disabled. + */ + public boolean isCompassEnabled () { + return mIsCompassEnabled; + } + + /** + * Enables or disables the compass. The compass is an icon on the map that indicates the + * direction of north on the map. If enabled, it is only shown when the camera is tilted or + * rotated away from its default orientation (tilt of 0 and a bearing of 0). When a user clicks + * the compass, the camera orients itself to its default orientation and fades away shortly + * after. If disabled, the compass will never be displayed. + * + * By default, the compass is enabled + * @param compassEnabled true to enable the compass; false to disable the compass. + */ + public void setCompassEnabled (boolean compassEnabled) { + // Set value + this.mIsCompassEnabled = compassEnabled; + + // Toggle UI + if (mIsCompassEnabled) { + mCompassView.setVisibility(View.VISIBLE); + } else { + mCompassView.setVisibility(View.GONE); + } + + // Update Map + updateMap(MapChange.MapChangeNullChange); + } + + public void setCompassGravity(int gravity){ + setWidgetGravity(mCompassView, gravity); + } + + public void setCompassMargins(int left, int top, int right, int bottom){ + setWidgetMargins(mCompassView, left, top, right, bottom); + } + + public void setLogoGravity(int gravity){ + setWidgetGravity(mLogoView, gravity); + } + + public void setLogoMargins(int left, int top, int right, int bottom){ + setWidgetMargins(mLogoView, left, top, right, bottom); + } + + public void setAttributionGravity(int gravity){ + setWidgetGravity(mAttributionsView, gravity); + } + + public void setAttributionMargins(int left, int top, int right, int bottom) { + setWidgetMargins(mAttributionsView, left, top, right, bottom); + } + + private void setWidgetGravity(@NonNull final View view, int gravity){ + LayoutParams layoutParams = (LayoutParams) view.getLayoutParams(); + layoutParams.gravity = gravity; + view.setLayoutParams(layoutParams); + } + + private void setWidgetMargins(@NonNull final View view, int left, int top, int right, int bottom){ + LayoutParams layoutParams = (LayoutParams) view.getLayoutParams(); + layoutParams.setMargins(left,top,right,bottom); + view.setLayoutParams(layoutParams); + } + + private void setWidgetMargins(@NonNull final View view, float leftDp, float topDp, float rightDp, float bottomDp){ + LayoutParams layoutParams = (LayoutParams) view.getLayoutParams(); + layoutParams.setMargins((int)(leftDp*mScreenDensity),(int)(topDp*mScreenDensity),(int)(rightDp*mScreenDensity),(int)(bottomDp*mScreenDensity)); + view.setLayoutParams(layoutParams); + } + + // This class handles sensor updates to calculate compass bearing + private class CompassListener implements SensorEventListener { + + @Override + public void onSensorChanged(SensorEvent event) { + switch (event.sensor.getType()) { + case Sensor.TYPE_ACCELEROMETER: + System.arraycopy(event.values, 0, mValuesAccelerometer, 0, 3); + break; + case Sensor.TYPE_MAGNETIC_FIELD: + System.arraycopy(event.values, 0, mValuesMagneticField, 0, 3); + break; + } + + boolean valid = SensorManager.getRotationMatrix(mMatrixR, mMatrixI, + mValuesAccelerometer, + mValuesMagneticField); + + if (valid) { + SensorManager.getOrientation(mMatrixR, mMatrixValues); + //mAzimuthRadians.putValue(mMatrixValues[0]); + //mAzimuth = Math.toDegrees(mAzimuthRadians.getAverage()); + + Location mGpsLocation = getMyLocation(); + if (mGpsLocation != null) { + GeomagneticField geomagneticField = new GeomagneticField( + (float) mGpsLocation.getLatitude(), + (float) mGpsLocation.getLongitude(), + (float) mGpsLocation.getAltitude(), + System.currentTimeMillis()); + mCompassBearing = (float) Math.toDegrees(mMatrixValues[0]) + geomagneticField.getDeclination(); + mCompassValid = true; + } + } + + updateMap(MapChange.MapChangeNullChange); + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // TODO: ignore unreliable stuff + } + } + + // Called when someone presses the compass + private class CompassOnClickListener implements View.OnClickListener { + + @Override + public void onClick(View view) { + resetNorth(); + } + + } + + private static class AttributionOnClickListener implements View.OnClickListener, DialogInterface.OnClickListener { + + private MapView mMapView; + + public AttributionOnClickListener(MapView mapView) { + mMapView = mapView; + } + + // Called when someone presses the attribution icon + @Override + public void onClick(View v) { + Context context = v.getContext(); + String[] items = context.getResources().getStringArray(R.array.attribution_names); + AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.AttributionAlertDialogStyle); + builder.setTitle(R.string.attributionsDialogTitle); + builder.setAdapter(new ArrayAdapter<>(context, R.layout.attribution_list_item, items), this); + builder.show(); + } + + // Called when someone selects an attribution, 'Improve this map' adds location data to the url + @Override + public void onClick(DialogInterface dialog, int which) { + Context context = ((Dialog) dialog).getContext(); + String url = context.getResources().getStringArray(R.array.attribution_links)[which]; + if (which == ATTRIBUTION_INDEX_IMPROVE_THIS_MAP) { + LatLng latLng = mMapView.getCenterCoordinate(); + url = String.format(url, latLng.getLongitude(), latLng.getLatitude(), (int) mMapView.getZoomLevel()); + } + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(url)); + context.startActivity(intent); + } + } + + /** + * LOST's LocationListener Callback + * @param location New Location + */ + @Override + public void onLocationChanged(Location location) { + updateLocation(location); + } + + // Handles location updates from GPS + private void updateLocation(Location location) { + if (location != null) { + mGpsLocation = location; + updateMap(MapChange.MapChangeNullChange); + } + } + + // Rotates an ImageView - does not work if the ImageView has padding, use margins + private void rotateImageView(ImageView imageView, float angle) { + Matrix matrix = new Matrix(); + matrix.setScale((float) imageView.getWidth() / (float) imageView.getDrawable().getIntrinsicWidth(), (float) imageView.getHeight() / (float) imageView.getDrawable().getIntrinsicHeight()); + matrix.postRotate(angle, (float) imageView.getWidth() / 2.0f, (float) imageView.getHeight() / 2.0f); + imageView.setImageMatrix(matrix); + imageView.setScaleType(ImageView.ScaleType.MATRIX); + } + + // Updates the UI to match the current map's position + private void updateMap(MapChange change) { + // Using direct access to mIsCompassEnabled instead of isCompassEnabled() for + // small performance boost as this method is called rapidly. + if (mIsCompassEnabled) { + rotateImageView(mCompassView, (float) getDirection()); + } + + if (isMyLocationEnabled() && mGpsLocation != null) { + if (mGpsMarker == null) { + // Setup User Location UI + // NOTE: mIsMyLocationEnabled == false to begin with + mGpsMarker = new ImageView(getContext()); + mGpsMarker.setImageResource(R.drawable.location_marker); + addView(mGpsMarker); + } + + mGpsMarker.setVisibility(View.VISIBLE); + LatLng coordinate = new LatLng(mGpsLocation); + PointF screenLocation = toScreenLocation(coordinate); + + float iconSize = 27.0f * mScreenDensity; + // Update Location + FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams((int) iconSize, (int) iconSize); + lp.leftMargin = (int) (screenLocation.x - iconSize / 2.0f); + lp.topMargin = getHeight() - (int) (screenLocation.y + iconSize / 2.0f); + mGpsMarker.setLayoutParams(lp); + rotateImageView(mGpsMarker, 0.0f); + mGpsMarker.requestLayout(); + + // Update direction if tracking mode + if(mUserLocationTrackingMode == UserLocationTrackingMode.FOLLOW_BEARING && mCompassValid){ + // TODO need to do proper filtering (see branch filter-compass) or else map will lock up because of all the compass events + long t = new Date().getTime(); + if((t-t0)>1000){ + t0 = t; + setDirection(-mCompassBearing, true); + } + } + +/* + // TODO - Too much overhead on main thread. Needs to be refactored before it + // can be re-enabled + // Update map position if NOT in NONE mode + if (mUserLocationTrackingMode != UserLocationTrackingMode.NONE) { + setCenterCoordinate(new LatLng(mGpsLocation)); + } +*/ + +/* + // Used For User Location Bearing UI + if (mGpsLocation.hasBearing() || mCompassValid) { + mGpsMarker.setImageResource(R.drawable.direction_arrow); + float iconSize = 54.0f * mScreenDensity; + FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams((int) iconSize, (int) iconSize); + lp.leftMargin = (int) (screenLocation.x - iconSize / 2.0f); + lp.topMargin = mMapFrameLayout.getHeight() - (int) (screenLocation.y + iconSize / 2.0f); + mGpsMarker.setLayoutParams(lp); + float bearing = mGpsLocation.hasBearing() ? mGpsLocation.getBearing() : mCompassBearing; + rotateImageView(mGpsMarker, bearing); + mGpsMarker.requestLayout(); + } +*/ + } else { + if (mGpsMarker != null) { + mGpsMarker.setVisibility(View.INVISIBLE); + } + } + + if (change.equals(MapChange.MapChangeRegionWillChange) || change.equals(MapChange.MapChangeRegionWillChangeAnimated)) { + deselectAnnotation(); + } + + } + + private void selectAnnotation(Annotation annotation) { + + if (annotation == null) { + return; + } + + if (annotation == mSelectedAnnotation) { + return; + } + + if (annotation instanceof Marker) { + // Need to deselect any currently selected annotation first + deselectAnnotation(); + + ((Marker)annotation).showInfoWindow(); + mSelectedAnnotation = annotation; + } + } + + private void deselectAnnotation() { + if (mSelectedAnnotation != null && mSelectedAnnotation instanceof Marker) { + Marker marker = (Marker) mSelectedAnnotation; + if (marker.isInfoWindowShown()) { + marker.hideInfoWindow(); + mSelectedAnnotation = null; + } + } + } } diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/NativeMapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/NativeMapView.java index 6c27d5f2d..3cf56a8de 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/NativeMapView.java +++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/NativeMapView.java @@ -2,12 +2,14 @@ package com.mapbox.mapboxgl.views; import android.graphics.PointF; import android.view.Surface; -import android.util.Log; +import com.mapbox.mapboxgl.annotations.Marker; +import com.mapbox.mapboxgl.annotations.Polygon; +import com.mapbox.mapboxgl.annotations.Polyline; +import com.mapbox.mapboxgl.geometry.BoundingBox; import com.mapbox.mapboxgl.geometry.LatLng; import com.mapbox.mapboxgl.geometry.LatLngZoom; import com.mapbox.mapboxgl.geometry.ProjectedMeters; - import java.util.List; // Class that wraps the native methods for convenience @@ -39,11 +41,19 @@ class NativeMapView { // Constructors // - public NativeMapView(MapView mapView, String cachePath, String dataPath, String apkPath) { + public NativeMapView(MapView mapView, String cachePath, String dataPath, String apkPath, float pixelRatio, int availableProcessors, long totalMemory) { + if (availableProcessors < 0) { + throw new IllegalArgumentException("availableProcessors cannot be negative."); + } + + if (totalMemory < 0) { + throw new IllegalArgumentException("totalMemory cannot be negative."); + } + mMapView = mapView; // Create the NativeMapView - mNativeMapViewPtr = nativeCreate(cachePath, dataPath, apkPath); + mNativeMapViewPtr = nativeCreate(cachePath, dataPath, apkPath, pixelRatio, availableProcessors, totalMemory); } // @@ -90,8 +100,7 @@ class NativeMapView { nativeOnInvalidate(mNativeMapViewPtr); } - public void resize(int width, int height, float ratio, int fbWidth, - int fbHeight) { + public void resizeView(int width, int height) { if (width < 0) { throw new IllegalArgumentException("width cannot be negative."); } @@ -100,6 +109,19 @@ class NativeMapView { throw new IllegalArgumentException("height cannot be negative."); } + if (width > 65535) { + throw new IllegalArgumentException( + "width cannot be greater than 65535."); + } + + if (height > 65535) { + throw new IllegalArgumentException( + "height cannot be greater than 65535."); + } + nativeViewResize(mNativeMapViewPtr, width, height); + } + + public void resizeFramebuffer(int fbWidth, int fbHeight) { if (fbWidth < 0) { throw new IllegalArgumentException("fbWidth cannot be negative."); } @@ -117,7 +139,7 @@ class NativeMapView { throw new IllegalArgumentException( "fbHeight cannot be greater than 65535."); } - nativeResize(mNativeMapViewPtr, width, height, ratio, fbWidth, fbHeight); + nativeFramebufferResize(mNativeMapViewPtr, fbWidth, fbHeight); } public void addClass(String clazz) { @@ -151,7 +173,7 @@ class NativeMapView { public void setDefaultTransitionDuration(long milliseconds) { if (milliseconds < 0) { throw new IllegalArgumentException( - "durationMilliseconds cannot be negative."); + "milliseconds cannot be negative."); } nativeSetDefaultTransitionDuration(mNativeMapViewPtr, @@ -163,7 +185,7 @@ class NativeMapView { } public void setStyleJson(String newStyleJson) { - setStyleJson(newStyleJson, ""); + setStyleJson(newStyleJson, ""); } public void setStyleJson(String newStyleJson, String base) { @@ -186,6 +208,10 @@ class NativeMapView { nativeCancelTransitions(mNativeMapViewPtr); } + public void setGestureInProgress(boolean inProgress) { + nativeSetGestureInProgress(mNativeMapViewPtr, inProgress); + } + public void moveBy(double dx, double dy) { moveBy(dx, dy, 0); } @@ -303,6 +329,42 @@ class NativeMapView { nativeResetNorth(mNativeMapViewPtr); } + public long addMarker(Marker marker) { + return nativeAddMarker(mNativeMapViewPtr, marker); + } + + public long addPolyline(Polyline polyline) { + return nativeAddPolyline(mNativeMapViewPtr, polyline); + } + + public long addPolygon(Polygon polygon) { + return nativeAddPolygon(mNativeMapViewPtr, polygon); + } + + public long[] addPolygons(List polygon) { + return nativeAddPolygons(mNativeMapViewPtr, polygon); + } + + public void removeAnnotation(long id) { + nativeRemoveAnnotation(mNativeMapViewPtr, id); + } + + public void removeAnnotations(long[] ids) { + nativeRemoveAnnotations(mNativeMapViewPtr, ids); + } + + public long[] getAnnotationsInBounds(BoundingBox bbox) { + return nativeGetAnnotationsInBounds(mNativeMapViewPtr, bbox); + } + + public void setSprite(String symbol, int width, int height, float scale, byte[] pixels) { + nativeSetSprite(mNativeMapViewPtr, symbol, width, height, scale, pixels); + } + + public void onLowMemory() { + nativeOnLowMemory(mNativeMapViewPtr); + } + public void setDebug(boolean debug) { nativeSetDebug(mNativeMapViewPtr, debug); } @@ -315,11 +377,27 @@ class NativeMapView { return nativeGetDebug(mNativeMapViewPtr); } + public void setCollisionDebug(boolean debug) { + nativeSetCollisionDebug(mNativeMapViewPtr, debug); + } + + public void toggleCollisionDebug() { + nativeToggleCollisionDebug(mNativeMapViewPtr); + } + + public boolean getCollisionDebug() { + return nativeGetCollisionDebug(mNativeMapViewPtr); + } + + public boolean isFullyLoaded() { + return nativeIsFullyLoaded(mNativeMapViewPtr); + } + public void setReachability(boolean status) { nativeSetReachability(mNativeMapViewPtr, status); } - //public void getWorldBoundsMeters(); + //public void (); //public void getWorldBoundsLatLng(); @@ -343,6 +421,10 @@ class NativeMapView { return nativeLatLngForPixel(mNativeMapViewPtr, pixel); } + public double getTopOffsetPixelsForAnnotationSymbol(String symbolName) { + return nativeGetTopOffsetPixelsForAnnotationSymbol(mNativeMapViewPtr, symbolName); + } + // // Callbacks // @@ -351,8 +433,8 @@ class NativeMapView { mMapView.onInvalidate(); } - protected void onMapChanged() { - mMapView.onMapChanged(); + protected void onMapChanged(int rawChange) { + mMapView.onMapChanged(rawChange); } protected void onFpsChanged(double fps) { @@ -370,7 +452,7 @@ class NativeMapView { super.finalize(); } - private native long nativeCreate(String cachePath, String dataPath, String apkPath); + private native long nativeCreate(String cachePath, String dataPath, String apkPath, float pixelRatio, int availableProcessors, long totalMemory); private native void nativeDestroy(long nativeMapViewPtr); @@ -395,8 +477,9 @@ class NativeMapView { private native void nativeOnInvalidate(long nativeMapViewPtr); - private native void nativeResize(long nativeMapViewPtr, int width, - int height, float ratio, int fbWidth, int fbHeight); + private native void nativeViewResize(long nativeMapViewPtr, int width, int height); + + private native void nativeFramebufferResize(long nativeMapViewPtr, int fbWidth, int fbHeight); private native void nativeAddClass(long nativeMapViewPtr, String clazz); @@ -427,6 +510,8 @@ class NativeMapView { private native void nativeCancelTransitions(long nativeMapViewPtr); + private native void nativeSetGestureInProgress(long nativeMapViewPtr, boolean inProgress); + private native void nativeMoveBy(long nativeMapViewPtr, double dx, double dy, long duration); @@ -474,12 +559,39 @@ class NativeMapView { private native void nativeResetNorth(long nativeMapViewPtr); + private native long nativeAddMarker(long nativeMapViewPtr, Marker marker); + + private native long nativeAddPolyline(long nativeMapViewPtr, Polyline polyline); + + private native long nativeAddPolygon(long mNativeMapViewPtr, Polygon polygon); + + private native long[] nativeAddPolygons(long mNativeMapViewPtr, List polygon); + + private native void nativeRemoveAnnotation(long nativeMapViewPtr, long id); + + private native void nativeRemoveAnnotations(long nativeMapViewPtr, long[] id); + + private native long[] nativeGetAnnotationsInBounds(long mNativeMapViewPtr, BoundingBox bbox); + + private native void nativeSetSprite(long nativeMapViewPtr, String symbol, + int width, int height, float scale, byte[] pixels); + + private native void nativeOnLowMemory(long nativeMapViewPtr); + private native void nativeSetDebug(long nativeMapViewPtr, boolean debug); private native void nativeToggleDebug(long nativeMapViewPtr); private native boolean nativeGetDebug(long nativeMapViewPtr); + private native void nativeSetCollisionDebug(long nativeMapViewPtr, boolean debug); + + private native void nativeToggleCollisionDebug(long nativeMapViewPtr); + + private native boolean nativeGetCollisionDebug(long nativeMapViewPtr); + + private native boolean nativeIsFullyLoaded(long nativeMapViewPtr); + private native void nativeSetReachability(long nativeMapViewPtr, boolean status); //private native void nativeGetWorldBoundsMeters(long nativeMapViewPtr); @@ -495,4 +607,6 @@ class NativeMapView { private native PointF nativePixelForLatLng(long nativeMapViewPtr, LatLng latLng); private native LatLng nativeLatLngForPixel(long nativeMapViewPtr, PointF pixel); + + private native double nativeGetTopOffsetPixelsForAnnotationSymbol(long nativeMapViewPtr, String symbolName); } diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/color/material_bg_selector.xml b/android/java/MapboxGLAndroidSDK/src/main/res/color/material_bg_selector.xml new file mode 100644 index 000000000..0da674ac7 --- /dev/null +++ b/android/java/MapboxGLAndroidSDK/src/main/res/color/material_bg_selector.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/compass.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/compass.png new file mode 100644 index 000000000..6bb20027f Binary files /dev/null and b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/compass.png differ diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/ic_info_blue_normal.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/ic_info_blue_normal.png new file mode 100644 index 000000000..de44e3ddc Binary files /dev/null and b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/ic_info_blue_normal.png differ diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/ic_info_blue_pressed.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/ic_info_blue_pressed.png new file mode 100644 index 000000000..fa2678624 Binary files /dev/null and b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/ic_info_blue_pressed.png differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-hdpi/location_marker.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/location_marker.png similarity index 100% rename from android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-hdpi/location_marker.png rename to android/java/MapboxGLAndroidSDK/src/main/res/drawable-hdpi/location_marker.png diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-ldpi/location_marker.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-ldpi/location_marker.png similarity index 100% rename from android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-ldpi/location_marker.png rename to android/java/MapboxGLAndroidSDK/src/main/res/drawable-ldpi/location_marker.png diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/compass.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/compass.png new file mode 100644 index 000000000..cf15ed187 Binary files /dev/null and b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/compass.png differ diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/ic_info_blue_normal.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/ic_info_blue_normal.png new file mode 100644 index 000000000..dd50478ac Binary files /dev/null and b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/ic_info_blue_normal.png differ diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/ic_info_blue_pressed.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/ic_info_blue_pressed.png new file mode 100644 index 000000000..d377f3cb8 Binary files /dev/null and b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/ic_info_blue_pressed.png differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/location_marker.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/location_marker.png similarity index 100% rename from android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/location_marker.png rename to android/java/MapboxGLAndroidSDK/src/main/res/drawable-mdpi/location_marker.png diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/compass.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/compass.png new file mode 100644 index 000000000..625720936 Binary files /dev/null and b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/compass.png differ diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/ic_info_blue_normal.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/ic_info_blue_normal.png new file mode 100644 index 000000000..df7d7a887 Binary files /dev/null and b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/ic_info_blue_normal.png differ diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/ic_info_blue_pressed.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/ic_info_blue_pressed.png new file mode 100644 index 000000000..f5b183364 Binary files /dev/null and b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/ic_info_blue_pressed.png differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xhdpi/location_marker.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/location_marker.png similarity index 100% rename from android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xhdpi/location_marker.png rename to android/java/MapboxGLAndroidSDK/src/main/res/drawable-xhdpi/location_marker.png diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/compass.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/compass.png new file mode 100644 index 000000000..c59b4c752 Binary files /dev/null and b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/compass.png differ diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/ic_info_blue_normal.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/ic_info_blue_normal.png new file mode 100644 index 000000000..0a04d4eaa Binary files /dev/null and b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/ic_info_blue_normal.png differ diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/ic_info_blue_pressed.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/ic_info_blue_pressed.png new file mode 100644 index 000000000..faaef0625 Binary files /dev/null and b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/ic_info_blue_pressed.png differ diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/ic_logo_mapbox.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/ic_logo_mapbox.png new file mode 100644 index 000000000..d1260a16f Binary files /dev/null and b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/ic_logo_mapbox.png differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxhdpi/location_marker.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/location_marker.png similarity index 100% rename from android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxhdpi/location_marker.png rename to android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxhdpi/location_marker.png diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/ic_info_blue_normal.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/ic_info_blue_normal.png new file mode 100644 index 000000000..bcee96dea Binary files /dev/null and b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/ic_info_blue_normal.png differ diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/ic_info_blue_pressed.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/ic_info_blue_pressed.png new file mode 100644 index 000000000..781263f76 Binary files /dev/null and b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/ic_info_blue_pressed.png differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxxhdpi/location_marker.png b/android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/location_marker.png similarity index 100% rename from android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxxhdpi/location_marker.png rename to android/java/MapboxGLAndroidSDK/src/main/res/drawable-xxxhdpi/location_marker.png diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/drawable/ic_info_selector.xml b/android/java/MapboxGLAndroidSDK/src/main/res/drawable/ic_info_selector.xml new file mode 100644 index 000000000..fdbabbae1 --- /dev/null +++ b/android/java/MapboxGLAndroidSDK/src/main/res/drawable/ic_info_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/layout/attribution_list_item.xml b/android/java/MapboxGLAndroidSDK/src/main/res/layout/attribution_list_item.xml new file mode 100644 index 000000000..f0a912158 --- /dev/null +++ b/android/java/MapboxGLAndroidSDK/src/main/res/layout/attribution_list_item.xml @@ -0,0 +1,11 @@ + + diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/layout/infowindow.xml b/android/java/MapboxGLAndroidSDK/src/main/res/layout/infowindow.xml new file mode 100644 index 000000000..af05e2fd4 --- /dev/null +++ b/android/java/MapboxGLAndroidSDK/src/main/res/layout/infowindow.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/values/arrays.xml b/android/java/MapboxGLAndroidSDK/src/main/res/values/arrays.xml new file mode 100644 index 000000000..8a8e204b7 --- /dev/null +++ b/android/java/MapboxGLAndroidSDK/src/main/res/values/arrays.xml @@ -0,0 +1,13 @@ + + + + © Mapbox + © OpenStreetMap + Improve this map + + + https://www.mapbox.com/about/maps/ + http://www.openstreetmap.org/about/ + https://www.mapbox.com/map-feedback/#/%1$f/%2$f/%3$d + + \ No newline at end of file diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/values/attrs.xml b/android/java/MapboxGLAndroidSDK/src/main/res/values/attrs.xml index 770b70317..901044375 100644 --- a/android/java/MapboxGLAndroidSDK/src/main/res/values/attrs.xml +++ b/android/java/MapboxGLAndroidSDK/src/main/res/values/attrs.xml @@ -1,23 +1,77 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/values/colors.xml b/android/java/MapboxGLAndroidSDK/src/main/res/values/colors.xml new file mode 100644 index 000000000..bb1c326a8 --- /dev/null +++ b/android/java/MapboxGLAndroidSDK/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #FFFFFF + #000000 + #1E8CAB + diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/values/strings.xml b/android/java/MapboxGLAndroidSDK/src/main/res/values/strings.xml new file mode 100644 index 000000000..be9a1d38c --- /dev/null +++ b/android/java/MapboxGLAndroidSDK/src/main/res/values/strings.xml @@ -0,0 +1,10 @@ + + + Map compass. Click to reset the map rotation to North. + Attribution icon. Click to show attribution dialog. + Mapbox Android SDK + Mapbox logo. + Title + Description + Address + diff --git a/android/java/MapboxGLAndroidSDK/src/main/res/values/styles.xml b/android/java/MapboxGLAndroidSDK/src/main/res/values/styles.xml new file mode 100644 index 000000000..a0859a998 --- /dev/null +++ b/android/java/MapboxGLAndroidSDK/src/main/res/values/styles.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/android/java/MapboxGLAndroidSDKTestApp/build.gradle b/android/java/MapboxGLAndroidSDKTestApp/build.gradle index 89b9ac90a..fea7691d2 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/build.gradle +++ b/android/java/MapboxGLAndroidSDKTestApp/build.gradle @@ -1,15 +1,23 @@ buildscript { repositories { mavenCentral() + maven { url 'https://maven.fabric.io/public' } } dependencies { - classpath 'com.android.tools.build:gradle:1.2.3' + classpath 'com.android.tools.build:gradle:1.3.0' classpath 'com.jakewharton.sdkmanager:gradle-plugin:0.12.0' + classpath 'io.fabric.tools:gradle:1.+' } } apply plugin: 'android-sdk-manager' apply plugin: 'com.android.application' +apply plugin: 'io.fabric' + +repositories { + maven { url 'https://maven.fabric.io/public' } +} + apply plugin: 'checkstyle' task accessToken { @@ -30,8 +38,8 @@ gradle.projectsEvaluated { } android { - compileSdkVersion 22 - buildToolsVersion "22.0.1" + compileSdkVersion 23 + buildToolsVersion "23.0.1" repositories { mavenCentral() @@ -39,8 +47,8 @@ android { defaultConfig { applicationId "com.mapbox.mapboxgl.testapp" - minSdkVersion 8 - targetSdkVersion 22 + minSdkVersion 15 + targetSdkVersion 23 versionCode 4 versionName "0.1.3" @@ -69,7 +77,7 @@ android { buildTypes { release { minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } @@ -78,10 +86,13 @@ dependencies { compile(project(':MapboxGLAndroidSDK')) { transitive = true } - compile 'com.android.support:support-annotations:22.2.0' - compile 'com.android.support:support-v4:22.2.0' - compile 'com.android.support:appcompat-v7:22.2.0' - compile 'com.mapzen.android:lost:1.0.0' + compile 'com.android.support:support-annotations:23.0.1' + compile 'com.android.support:support-v4:23.0.1' + compile 'com.android.support:appcompat-v7:23.0.1' + compile 'com.android.support:design:23.0.1' + compile('com.crashlytics.sdk.android:crashlytics:2.5.1@aar') { + transitive = true; + } // Testing dependencies androidTestCompile 'com.android.support.test:runner:0.3' diff --git a/android/java/MapboxGLAndroidSDKTestApp/proguard-rules.pro b/android/java/MapboxGLAndroidSDKTestApp/proguard-rules.pro new file mode 100644 index 000000000..578d94692 --- /dev/null +++ b/android/java/MapboxGLAndroidSDKTestApp/proguard-rules.pro @@ -0,0 +1 @@ +# Proguard configuration is handeld in the SDK \ No newline at end of file diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml index 901a63f57..85be72bb7 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml @@ -2,15 +2,12 @@ - - - + + + + diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/assets/small_line.geojson b/android/java/MapboxGLAndroidSDKTestApp/src/main/assets/small_line.geojson new file mode 100644 index 000000000..ef76eaf56 --- /dev/null +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/assets/small_line.geojson @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"type":"LineString","coordinates":[[-121.3714599609375,38.64583568648869],[-121.48681640624999,38.64047263931154],[-121.53076171875,38.61579745317875],[-121.56509399414061,38.57823196583313],[-121.75186157226561,38.533127435052776],[-121.97021484374999,38.35350340353833],[-122.08007812499999,38.24680876017446],[-122.16796875,38.19718009396176],[-122.23800659179686,38.09457899232253],[-122.27371215820312,38.01455819225337],[-122.33551025390625,37.94094845586459],[-122.27371215820312,37.81846319511331],[-122.4151611328125,37.7652868250379]]}}]} \ No newline at end of file diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/assets/small_poly.geojson b/android/java/MapboxGLAndroidSDKTestApp/src/main/assets/small_poly.geojson new file mode 100644 index 000000000..ec61303c7 --- /dev/null +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/assets/small_poly.geojson @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-122.28813171386719,38.617406963286136],[-122.26959228515624,38.6833657775237],[-122.18238830566406,38.55568323796419],[-122.10617065429688,38.51378825951165],[-122.11509704589845,38.50465406475561],[-122.18307495117188,38.542795073979015],[-122.19955444335938,38.496593518947556],[-122.28813171386719,38.617406963286136]]]}}]} \ No newline at end of file diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java index 65cb17462..ed9b7ef06 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MainActivity.java @@ -1,81 +1,73 @@ package com.mapbox.mapboxgl.testapp; -import android.content.Context; -import android.graphics.Matrix; +import android.Manifest; +import android.content.pm.PackageManager; +import android.graphics.Color; import android.graphics.PointF; -import android.hardware.GeomagneticField; -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; -import android.hardware.SensorManager; import android.location.Location; import android.os.Bundle; -import android.support.v7.app.ActionBarActivity; +import android.support.annotation.NonNull; +import android.support.design.widget.FloatingActionButton; +import android.support.design.widget.NavigationView; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; +import android.support.v4.view.GravityCompat; +import android.support.v4.widget.DrawerLayout; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; -import android.view.Menu; -import android.view.MenuInflater; +import android.util.Log; +import android.view.GestureDetector; import android.view.MenuItem; +import android.view.MotionEvent; import android.view.View; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.Spinner; import android.widget.TextView; - +import android.widget.Toast; +import com.crashlytics.android.Crashlytics; +import com.mapbox.mapboxgl.annotations.Marker; +import com.mapbox.mapboxgl.annotations.MarkerOptions; +import com.mapbox.mapboxgl.annotations.PolygonOptions; +import com.mapbox.mapboxgl.annotations.PolylineOptions; import com.mapbox.mapboxgl.geometry.LatLng; import com.mapbox.mapboxgl.views.MapView; -import com.mapzen.android.lost.api.LocationListener; -import com.mapzen.android.lost.api.LocationRequest; -import com.mapzen.android.lost.api.LocationServices; -import com.mapzen.android.lost.api.LostApiClient; - +import io.fabric.sdk.android.Fabric; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.text.DecimalFormat; import java.util.ArrayList; -public class MainActivity extends ActionBarActivity { +public class MainActivity extends AppCompatActivity { + + private static final String TAG = "MainActivity"; // // Static members // // Used for saving instance state - private static final String STATE_IS_GPS_ON = "isGpsOn"; + private static final String STATE_IS_ANNOTATIONS_ON = "isAnnotationsOn"; + private static final String STATE_SELECTED_STYLE = "selectedStyle"; + + // Used for permissions requests + private static final int PERMISSIONS_LOCATION = 0; // // Instance members // // Used for the UI - private MapFragment mMapFragment; + private DrawerLayout mDrawerLayout; + private MapView mMapView; private TextView mFpsTextView; - private ImageView mCompassView; - private FrameLayout mMapFrameLayout; - private float mDensity; - private Spinner mClassSpinner; - private ArrayAdapter mOutdoorsClassAdapter; - private ArrayAdapter mSatelliteClassAdapter; + private int mSelectedStyle = R.id.actionStyleMapboxStreets; + NavigationView mNavigationView; // Used for GPS - private boolean mIsGpsOn = false; - private LostApiClient mLocationClient; - private GpsListener mGpsListener; - private LocationRequest mLocationRequest; - private ImageView mGpsMarker; - private Location mGpsLocation; - private MenuItem mGpsMenuItem; + private FloatingActionButton mLocationFAB; - // Used for compass - private SensorManager mSensorManager; - private Sensor mSensorAccelerometer; - private Sensor mSensorMagneticField; - private CompassListener mCompassListener; - private float[] mValuesAccelerometer = new float[3]; - private float[] mValuesMagneticField = new float[3]; - private float[] mMatrixR = new float[9]; - private float[] mMatrixI = new float[9]; - private float[] mMatrixValues = new float[3]; - private float mCompassBearing; - private boolean mCompassValid = false; + // Used for Annotations + private boolean mIsAnnotationsOn = false; // // Lifecycle events @@ -85,62 +77,112 @@ public class MainActivity extends ActionBarActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - if (savedInstanceState != null) { - mIsGpsOn = savedInstanceState.getBoolean(STATE_IS_GPS_ON, false); - } - - mDensity = getResources().getDisplayMetrics().density; + Fabric.with(this, new Crashlytics()); // Load the layout setContentView(R.layout.activity_main); - mMapFragment = (MapFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_map); - mMapFragment.getMap().setOnFpsChangedListener(new MyOnFpsChangedListener()); - mMapFragment.getMap().setOnMapChangedListener(new MyOnMapChangedListener()); + + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + + final ActionBar ab = getSupportActionBar(); + if (ab != null) { + ab.setHomeAsUpIndicator(R.drawable.ic_menu); + ab.setDisplayHomeAsUpEnabled(true); + } + + mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); + + mNavigationView = (NavigationView) findViewById(R.id.nav_view); + if (mNavigationView != null) { + setupDrawerContent(mNavigationView); + } + + mMapView = (MapView) findViewById(R.id.mainMapView); + // Load the access token + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(getResources().openRawResource(R.raw.token))); + String line = reader.readLine(); + mMapView.setAccessToken(line); + } catch (IOException e) { + Log.e(TAG, "Error loading access token from token.txt: " + e.toString()); + } + + mMapView.onCreate(savedInstanceState); + mMapView.setOnFpsChangedListener(new MyOnFpsChangedListener()); + + final GestureDetector gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() { + public void onLongPress(final MotionEvent e) { + float x = e.getX(); + float y = e.getY(); + + // flip y direction vertically to match core GL + y = mMapView.getHeight() - y; + + LatLng position = mMapView.fromScreenLocation(new PointF(x, y)); + + mMapView.addMarker(new MarkerOptions() + .position(position) + .title("Dropped Pin") + .snippet(new DecimalFormat("#.#####").format(position.getLatitude()) + ", " + + new DecimalFormat("#.#####").format(position.getLongitude())) + .sprite("default_marker")); + } + }); + + mMapView.setOnTouchListener(new View.OnTouchListener() { + public boolean onTouch(View v, MotionEvent event) { + return gestureDetector.onTouchEvent(event); + } + }); mFpsTextView = (TextView) findViewById(R.id.view_fps); mFpsTextView.setText(""); - mCompassView = (ImageView) findViewById(R.id.view_compass); - mCompassView.setOnClickListener(new CompassOnClickListener()); + mLocationFAB = (FloatingActionButton)findViewById(R.id.locationFAB); + mLocationFAB.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + // Toggle GPS position updates + toggleGps(!mMapView.isMyLocationEnabled()); + if (mMapView.isMyLocationEnabled()) { + Location location = mMapView.getMyLocation(); + if (location != null) { + mMapView.setZoomLevel(8); + mMapView.setCenterCoordinate(new LatLng(location)); + } + } + } + }); - mGpsMarker = new ImageView(getApplicationContext()); - mGpsMarker.setVisibility(View.INVISIBLE); - mGpsMarker.setImageResource(R.drawable.location_marker); - mGpsMarker.setLayoutParams(new FrameLayout.LayoutParams((int) (27.0f * mDensity), (int) (27.0f * mDensity))); - mGpsMarker.requestLayout(); + // Restore saved state + if (savedInstanceState != null) { + mIsAnnotationsOn = savedInstanceState.getBoolean(STATE_IS_ANNOTATIONS_ON); + mSelectedStyle = savedInstanceState.getInt(STATE_SELECTED_STYLE); + } - mMapFrameLayout = (FrameLayout) findViewById(R.id.layout_map); - mMapFrameLayout.addView(mGpsMarker); - // Add a toolbar as the action bar - Toolbar mainToolbar = (Toolbar) findViewById(R.id.toolbar_main); - setSupportActionBar(mainToolbar); - getSupportActionBar().setDisplayShowTitleEnabled(false); + // Set default UI state + mNavigationView.getMenu().findItem(R.id.action_compass).setChecked(mMapView.isCompassEnabled()); + mNavigationView.getMenu().findItem(R.id.action_debug).setChecked(mMapView.isDebugActive()); + mNavigationView.getMenu().findItem(R.id.action_markers).setChecked(mIsAnnotationsOn); + changeMapStyle(mSelectedStyle); + toggleGps(mMapView.isMyLocationEnabled()); + } - // Add the spinner to select map styles - Spinner styleSpinner = (Spinner) findViewById(R.id.spinner_style); - ArrayAdapter styleAdapter = ArrayAdapter.createFromResource(getSupportActionBar().getThemedContext(), - R.array.style_list, android.R.layout.simple_spinner_dropdown_item); - styleSpinner.setAdapter(styleAdapter); - styleSpinner.setOnItemSelectedListener(new StyleSpinnerListener()); + /** + * Dispatch onStart() to all fragments. Ensure any created loaders are + * now started. + */ + @Override + protected void onStart() { + super.onStart(); + mMapView.onStart(); + } - // Add the spinner to select class styles - mClassSpinner = (Spinner) findViewById(R.id.spinner_class); - mOutdoorsClassAdapter = ArrayAdapter.createFromResource(getSupportActionBar().getThemedContext(), - R.array.outdoors_class_list, android.R.layout.simple_spinner_dropdown_item); - - // Prepare for GPS - mLocationClient = new LostApiClient.Builder(this).build(); - mLocationRequest = LocationRequest.create() - .setInterval(1000) - .setSmallestDisplacement(1) - .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); - mGpsListener = new GpsListener(); - - mSensorManager = (SensorManager) getApplicationContext().getSystemService(Context.SENSOR_SERVICE); - mSensorAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); - mSensorMagneticField = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD); - mCompassListener = new CompassListener(); + @Override + protected void onStop() { + super.onStop(); + mMapView.onStop(); } // Called when our app goes into the background @@ -148,16 +190,21 @@ public class MainActivity extends ActionBarActivity { public void onPause() { super.onPause(); - // Cancel GPS - toggleGps(false); + mMapView.onPause(); } // Called when our app comes into the foreground @Override public void onResume() { super.onResume(); - // Restart GPS - toggleGps(true); + + mMapView.onResume(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mMapView.onDestroy(); } // Called when we need to save instance state @@ -165,53 +212,139 @@ public class MainActivity extends ActionBarActivity { protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - outState.putBoolean(STATE_IS_GPS_ON, mIsGpsOn); + mMapView.onSaveInstanceState(outState); + outState.putBoolean(STATE_IS_ANNOTATIONS_ON, mIsAnnotationsOn); + outState.putInt(STATE_SELECTED_STYLE, mSelectedStyle); } + // Called when the system is running low on memory + @Override + public void onLowMemory() { + super.onLowMemory(); + + mMapView.onLowMemory(); + } // // Other events // - // Adds items to the action bar menu - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.menu_main, menu); - mGpsMenuItem = menu.findItem(R.id.action_gps); - if (mIsGpsOn) { - mGpsMenuItem.setIcon(R.drawable.ic_action_location_found); - } else { - mGpsMenuItem.setIcon(R.drawable.ic_action_location_searching); - } - return super.onCreateOptionsMenu(menu); - } - // Called when pressing action bar items @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case R.id.action_gps: - // Toggle GPS position updates - toggleGps(!mIsGpsOn); - updateMap(); + case android.R.id.home: + mDrawerLayout.openDrawer(GravityCompat.START); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { + switch (requestCode) { + case PERMISSIONS_LOCATION: { + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + enableGps(); + } + } + } + } + + private void setupDrawerContent(NavigationView navigationView) { + navigationView.setNavigationItemSelectedListener( + new NavigationView.OnNavigationItemSelectedListener() { + @Override + public boolean onNavigationItemSelected(MenuItem menuItem) { + mDrawerLayout.closeDrawers(); + + // Respond To Selection + switch (menuItem.getItemId()) { + + case R.id.action_debug: + // Toggle debug mode + mMapView.toggleDebug(); + toggleFpsCounter(mMapView.isDebugActive()); + menuItem.setChecked(mMapView.isDebugActive()); + return true; + + case R.id.action_markers: + // Toggle markers + toggleAnnotations(!mIsAnnotationsOn); + menuItem.setChecked(mIsAnnotationsOn); + return true; + + case R.id.action_compass: + // Toggle compass + mMapView.setCompassEnabled(!mMapView.isCompassEnabled()); + menuItem.setChecked(mMapView.isCompassEnabled()); + return true; + +/* + case R.id.followNone: + mMapView.setUserLocationTrackingMode(MapView.UserLocationTrackingMode.NONE); + return true; + + case R.id.followFollow: + mMapView.setUserLocationTrackingMode(MapView.UserLocationTrackingMode.FOLLOW); + return true; + + case R.id.followBearing: + mMapView.setUserLocationTrackingMode(MapView.UserLocationTrackingMode.FOLLOW_BEARING); + return true; +*/ + default: + return changeMapStyle(menuItem.getItemId()); + } + } + }); + } + + private void toggleFpsCounter(boolean enableFps) { + // Show the FPS counter + if (enableFps) { + mFpsTextView.setVisibility(View.VISIBLE); + mFpsTextView.setText(getResources().getString(R.string.label_fps)); + } else { + mFpsTextView.setVisibility(View.INVISIBLE); + } + } + + private boolean changeMapStyle(int id) { + switch (id) { + case R.id.actionStyleMapboxStreets: + mMapView.setStyleUrl(getString(R.string.styleURLMapboxStreets)); + mNavigationView.getMenu().findItem(id).setChecked(true); + mSelectedStyle = id; return true; - case R.id.action_debug: - // Toggle debug mode - mMapFragment.getMap().toggleDebug(); + case R.id.actionStyleEmerald: + mMapView.setStyleUrl(getString(R.string.styleURLEmerald)); + mNavigationView.getMenu().findItem(id).setChecked(true); + mSelectedStyle = id; + return true; - // Show the FPS counter - if (mMapFragment.getMap().isDebugActive()) { - mFpsTextView.setVisibility(View.VISIBLE); - mFpsTextView.setText(getResources().getString(R.string.label_fps)); - } else { - mFpsTextView.setVisibility(View.INVISIBLE); - } + case R.id.actionStyleLight: + mMapView.setStyleUrl(getString(R.string.styleURLLight)); + mNavigationView.getMenu().findItem(id).setChecked(true); + mSelectedStyle = id; + return true; + + case R.id.actionStyleDark: + mMapView.setStyleUrl(getString(R.string.styleURLDark)); + mNavigationView.getMenu().findItem(id).setChecked(true); + mSelectedStyle = id; + return true; + + case R.id.actionStyleSatellite: + mMapView.setStyleUrl(getString(R.string.styleURLSatellite)); + mNavigationView.getMenu().findItem(id).setChecked(true); + mSelectedStyle = id; return true; default: - return super.onOptionsItemSelected(item); + return false; } } @@ -220,228 +353,105 @@ public class MainActivity extends ActionBarActivity { * @param enableGps true if GPS is to be enabled, false if GPS is to be disabled */ private void toggleGps(boolean enableGps) { - if (enableGps) { - if (!mIsGpsOn) { - mIsGpsOn = true; - if (mGpsMenuItem != null) { - mGpsMenuItem.setIcon(R.drawable.ic_action_location_found); - } - mGpsLocation = null; - mLocationClient.connect(); - updateLocation(LocationServices.FusedLocationApi.getLastLocation()); - LocationServices.FusedLocationApi.requestLocationUpdates(mLocationRequest, new GpsListener()); - mSensorManager.registerListener(mCompassListener, mSensorAccelerometer, SensorManager.SENSOR_DELAY_UI); - mSensorManager.registerListener(mCompassListener, mSensorMagneticField, SensorManager.SENSOR_DELAY_UI); + if ((ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) || + (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED)) + { + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSIONS_LOCATION); + } else { + enableGps(); } } else { - if (mIsGpsOn) { - mIsGpsOn = false; - if (mGpsMenuItem != null) { - mGpsMenuItem.setIcon(R.drawable.ic_action_location_searching); - } - LocationServices.FusedLocationApi.removeLocationUpdates(mGpsListener); - mLocationClient.disconnect(); - mSensorManager.unregisterListener(mCompassListener, mSensorAccelerometer); - mSensorManager.unregisterListener(mCompassListener, mSensorMagneticField); - mGpsLocation = null; + mMapView.setMyLocationEnabled(false); + mLocationFAB.setColorFilter(Color.TRANSPARENT); + } + } + + private void enableGps() { + mMapView.setMyLocationEnabled(true); + mLocationFAB.setColorFilter(ContextCompat.getColor(this, R.color.primary)); + } + + /** + * Enable / Disable Annotations. + * + * @param enableAnnotations True to display, False to hide + */ + private void toggleAnnotations(boolean enableAnnotations) { + if (enableAnnotations) { + if (!mIsAnnotationsOn) { + mIsAnnotationsOn = true; + addMarkers(); + addPolyline(); + addPolygon(); + mMapView.setZoomLevel(7); + mMapView.setCenterCoordinate(new LatLng(38.11727, -122.22839)); + } + } else { + if (mIsAnnotationsOn) { + mIsAnnotationsOn = false; + removeAnnotations(); } } } - // This class forwards location updates to updateLocation() - private class GpsListener implements LocationListener { + private void addMarkers() { + LatLng backLot = new LatLng(38.649441, -121.369064); + final Marker marker = mMapView.addMarker(new MarkerOptions() + .position(backLot) + .title("Back Lot") + .snippet("The back lot behind my house")); + marker.setInfoWindowOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + Toast.makeText(getApplicationContext(), "Custom Info Touch Listener!!", Toast.LENGTH_SHORT).show(); + marker.hideInfoWindow(); + return true; + } + }); - @Override - public void onLocationChanged(Location location) { - updateLocation(location); + LatLng cheeseRoom = new LatLng(38.531577,-122.010646); + mMapView.addMarker(new MarkerOptions() + .position(cheeseRoom) + .sprite("dog-park-15") + .title("Cheese Room") + .snippet("The only air conditioned room on the property!")); + } + + private void addPolyline() { + try { + String geojsonStr = Util.loadStringFromAssets(this, "small_line.geojson"); + LatLng[] latLngs = Util.parseGeoJSONCoordinates(geojsonStr); + MapView map = mMapView; + map.addPolyline(new PolylineOptions() + .add(latLngs) + .width(2) + .color(Color.RED)); + } catch (Exception e) { + Log.e(TAG, "Error adding Polyline: "+ e); + e.printStackTrace(); } } - // This class handles sensor updates to calculate compass bearing - private class CompassListener implements SensorEventListener { - - @Override - public void onSensorChanged(SensorEvent event) { - switch (event.sensor.getType()) { - case Sensor.TYPE_ACCELEROMETER: - System.arraycopy(event.values, 0, mValuesAccelerometer, 0, 3); - break; - case Sensor.TYPE_MAGNETIC_FIELD: - System.arraycopy(event.values, 0, mValuesMagneticField, 0, 3); - break; - } - - boolean valid = SensorManager.getRotationMatrix(mMatrixR, mMatrixI, - mValuesAccelerometer, - mValuesMagneticField); - - if (valid) { - SensorManager.getOrientation(mMatrixR, mMatrixValues); - //mAzimuthRadians.putValue(mMatrixValues[0]); - //mAzimuth = Math.toDegrees(mAzimuthRadians.getAverage()); - - if (mGpsLocation != null) { - GeomagneticField geomagneticField = new GeomagneticField( - (float) mGpsLocation.getLatitude(), - (float) mGpsLocation.getLongitude(), - (float) mGpsLocation.getAltitude(), - System.currentTimeMillis()); - mCompassBearing = (float) Math.toDegrees(mMatrixValues[0]) + geomagneticField.getDeclination(); - mCompassValid = true; - } - } - - updateMap(); - } - - @Override - public void onAccuracyChanged(Sensor sensor, int accuracy) { - // TODO: ignore unreliable stuff + private void addPolygon() { + try { + String geojsonStr = Util.loadStringFromAssets(this, "small_poly.geojson"); + LatLng[] latLngs = Util.parseGeoJSONCoordinates(geojsonStr); + MapView map = mMapView; + ArrayList opts = new ArrayList<>(); + opts.add(new PolygonOptions() + .add(latLngs) + .strokeColor(Color.MAGENTA) + .fillColor(Color.BLUE)); + map.addPolygons(opts).get(0); + } catch (Exception e) { + Log.e(TAG, "Error adding Polygon: "+ e); + e.printStackTrace(); } } - // Handles location updates from GPS - private void updateLocation(Location location) { - if (location != null) { - mGpsLocation = location; - } - - updateMap(); - } - - // This class handles style change events - private class StyleSpinnerListener implements AdapterView.OnItemSelectedListener { - - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - switch (position) { - - // Mapbox Streets - case 0: - mMapFragment.getMap().setStyleUrl("asset://styles/mapbox-streets-v7.json"); - mMapFragment.getMap().removeAllClasses(); - mClassSpinner.setVisibility(View.INVISIBLE); - mClassSpinner.setAdapter(null); - mClassSpinner.setOnItemSelectedListener(null); - break; - - // Emerald - case 1: - mMapFragment.getMap().setStyleUrl("asset://styles/emerald-v7.json"); - mMapFragment.getMap().removeAllClasses(); - mClassSpinner.setVisibility(View.INVISIBLE); - mClassSpinner.setAdapter(null); - mClassSpinner.setOnItemSelectedListener(null); - break; - - // Light - case 2: - mMapFragment.getMap().setStyleUrl("asset://styles/light-v7.json"); - mMapFragment.getMap().removeAllClasses(); - mClassSpinner.setVisibility(View.INVISIBLE); - mClassSpinner.setAdapter(null); - mClassSpinner.setOnItemSelectedListener(null); - break; - - // Dark - case 3: - mMapFragment.getMap().setStyleUrl("asset://styles/dark-v7.json"); - mMapFragment.getMap().removeAllClasses(); - mClassSpinner.setVisibility(View.INVISIBLE); - mClassSpinner.setAdapter(null); - mClassSpinner.setOnItemSelectedListener(null); - break; - - // Outdoors - case 4: - mMapFragment.getMap().setStyleUrl("asset://styles/outdoors-v7.json"); - mMapFragment.getMap().removeAllClasses(); - mClassSpinner.setVisibility(View.VISIBLE); - mClassSpinner.setAdapter(mOutdoorsClassAdapter); - mClassSpinner.setOnItemSelectedListener(new OutdoorClassSpinnerListener()); - break; - - default: - onNothingSelected(parent); - break; - } - } - - @Override - public void onNothingSelected(AdapterView parent) { - mMapFragment.getMap().setStyleUrl(""); - } - } - - // This class handles outdoor class change events - private class OutdoorClassSpinnerListener implements AdapterView.OnItemSelectedListener { - - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - ArrayList classes = new ArrayList<>(1); - - switch (position) { - // Day - case 0: - classes.add("day"); - mMapFragment.getMap().setClasses(classes); - break; - - // Night - case 1: - classes.add("night"); - mMapFragment.getMap().setClasses(classes); - break; - - default: - onNothingSelected(parent); - break; - } - } - - @Override - public void onNothingSelected(AdapterView parent) { - mMapFragment.getMap().removeAllClasses(); - } - } - - // This class handles satellite class change events - private class SatelliteClassSpinnerListener implements AdapterView.OnItemSelectedListener { - - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - ArrayList classes = new ArrayList<>(2); - - switch (position) { - // Labels + Contours - case 0: - classes.add("labels"); - classes.add("contours"); - mMapFragment.getMap().setClasses(classes); - break; - - // Labels Only - case 1: - classes.add("labels"); - mMapFragment.getMap().setClasses(classes); - break; - - // No Labels - case 2: - mMapFragment.getMap().setClasses(classes); - break; - - default: - onNothingSelected(parent); - break; - } - } - - @Override - public void onNothingSelected(AdapterView parent) { - mMapFragment.getMap().removeAllClasses(); - } + private void removeAnnotations() { + mMapView.removeAnnotations(); } // Called when FPS changes @@ -453,62 +463,4 @@ public class MainActivity extends ActionBarActivity { } } - // Rotates an ImageView - does not work if the ImageView has padding, use margins - private void rotateImageView(ImageView imageView, float angle) { - Matrix matrix = new Matrix(); - matrix.setScale((float) imageView.getWidth() / (float) imageView.getDrawable().getIntrinsicWidth(), (float) imageView.getHeight() / (float) imageView.getDrawable().getIntrinsicHeight()); - matrix.postRotate(angle, (float) imageView.getWidth() / 2.0f, (float) imageView.getHeight() / 2.0f); - imageView.setImageMatrix(matrix); - imageView.setScaleType(ImageView.ScaleType.MATRIX); - } - - // Updates the UI to match the current map's position - private void updateMap() { - rotateImageView(mCompassView, (float) mMapFragment.getMap().getDirection()); - - if (mGpsLocation != null) { - mGpsMarker.setVisibility(View.VISIBLE); - LatLng coordinate = new LatLng(mGpsLocation); - PointF screenLocation = mMapFragment.getMap().toScreenLocation(coordinate); - - if (mGpsLocation.hasBearing() || mCompassValid) { - mGpsMarker.setImageResource(R.drawable.direction_arrow); - FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams((int) (54.0f * mDensity), (int) (54.0f * mDensity)); - lp.leftMargin = (int) ((screenLocation.x - 54.0f / 2.0f) * mDensity); - lp.topMargin = mMapFrameLayout.getHeight() - (int) ((screenLocation.y + 54.0f / 2.0f) * mDensity); - mGpsMarker.setLayoutParams(lp); - float bearing = mGpsLocation.hasBearing() ? mGpsLocation.getBearing() : mCompassBearing; - rotateImageView(mGpsMarker, bearing); - mGpsMarker.requestLayout(); - } else { - mGpsMarker.setImageResource(R.drawable.location_marker); - FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams((int) (27.0f * mDensity), (int) (27.0f * mDensity)); - lp.leftMargin = (int) ((screenLocation.x - 27.0f / 2.0f) * mDensity); - lp.topMargin = mMapFrameLayout.getHeight() - (int) ((screenLocation.y + 27.0f / 2.0f) * mDensity); - mGpsMarker.setLayoutParams(lp); - rotateImageView(mGpsMarker, 0.0f); - mGpsMarker.requestLayout(); - } - } else { - mGpsMarker.setVisibility(View.INVISIBLE); - } - } - - // Called when map state changes - private class MyOnMapChangedListener implements MapView.OnMapChangedListener { - - @Override - public void onMapChanged() { - updateMap(); - } - } - - // Called when someone presses the compass - private class CompassOnClickListener implements View.OnClickListener { - - @Override - public void onClick(View view) { - mMapFragment.getMap().resetNorth(); - } - } } diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MapFragment.java b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MapFragment.java index b78926b9e..7883dbae4 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MapFragment.java +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/MapFragment.java @@ -9,10 +9,6 @@ import android.view.ViewGroup; import com.mapbox.mapboxgl.views.MapView; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; - public class MapFragment extends Fragment { // @@ -42,15 +38,6 @@ public class MapFragment extends Fragment { // Create the map mMap = (MapView) inflater.inflate(R.layout.fragment_main, container, true); - // Load the access token - try { - BufferedReader reader = new BufferedReader(new InputStreamReader(getActivity().getApplicationContext().getResources().openRawResource(R.raw.token))); - String line = reader.readLine(); - mMap.setAccessToken(line); - } catch (IOException e) { - return null; - } - // Need to pass on any saved state to the map mMap.onCreate(savedInstanceState); diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/Util.java b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/Util.java new file mode 100644 index 000000000..74b909cec --- /dev/null +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxgl/testapp/Util.java @@ -0,0 +1,61 @@ +package com.mapbox.mapboxgl.testapp; + +import android.content.Context; +import android.text.TextUtils; + +import com.mapbox.mapboxgl.geometry.LatLng; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.Charset; + +public class Util { + + public static String loadStringFromAssets(final Context context, final String fileName) throws IOException { + if (TextUtils.isEmpty(fileName)) { + throw new NullPointerException("No GeoJSON File Name passed in."); + } + InputStream is = context.getAssets().open(fileName); + BufferedReader rd = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8"))); + return readAll(rd); + } + + public static LatLng[] parseGeoJSONCoordinates(String geojsonStr) throws JSONException { + JSONObject jsonObject = new JSONObject(geojsonStr); + JSONArray features = jsonObject.getJSONArray("features"); + JSONObject feature = features.getJSONObject(0); + JSONObject geometry = feature.getJSONObject("geometry"); + String type = geometry.getString("type"); + JSONArray coordinates; + if (type.equals("Polygon")) { + coordinates = geometry.getJSONArray("coordinates").getJSONArray(0); + } else { + coordinates = geometry.getJSONArray("coordinates"); + } + int len = coordinates.length(); + LatLng[] latLngs = new LatLng[coordinates.length()]; + for (int i = 0; i < len; ++i) { + JSONArray coord = coordinates.getJSONArray(i); + double lng = coord.getDouble(0); + double lat = coord.getDouble(1); + latLngs[i] = new LatLng(lat, lng); + } + return latLngs; + } + + private static String readAll(Reader rd) throws IOException { + StringBuilder sb = new StringBuilder(); + int cp; + while ((cp = rd.read()) != -1) { + sb.append((char) cp); + } + return sb.toString(); + } +} diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-hdpi/direction_arrow.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-hdpi/direction_arrow.png deleted file mode 100644 index 379ac4fb1..000000000 Binary files a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-hdpi/direction_arrow.png and /dev/null differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-hdpi/ic_action_about.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-hdpi/ic_action_about.png deleted file mode 100644 index 077dcec80..000000000 Binary files a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-hdpi/ic_action_about.png and /dev/null differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-hdpi/ic_action_location_found.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-hdpi/ic_action_location_found.png deleted file mode 100644 index e1bf5807b..000000000 Binary files a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-hdpi/ic_action_location_found.png and /dev/null differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-hdpi/ic_action_location_searching.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-hdpi/ic_action_location_searching.png deleted file mode 100644 index 48f4aec5b..000000000 Binary files a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-hdpi/ic_action_location_searching.png and /dev/null differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-hdpi/ic_menu.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-hdpi/ic_menu.png new file mode 100644 index 000000000..8962cbd91 Binary files /dev/null and b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-hdpi/ic_menu.png differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-hdpi/ic_my_location_black_24dp.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-hdpi/ic_my_location_black_24dp.png new file mode 100644 index 000000000..85e38726d Binary files /dev/null and b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-hdpi/ic_my_location_black_24dp.png differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-ldpi/direction_arrow.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-ldpi/direction_arrow.png deleted file mode 100644 index 292d2c5df..000000000 Binary files a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-ldpi/direction_arrow.png and /dev/null differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/compass.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/compass.png deleted file mode 100644 index 58e7e08d2..000000000 Binary files a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/compass.png and /dev/null differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/direction_arrow.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/direction_arrow.png deleted file mode 100644 index c0101b8a6..000000000 Binary files a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/direction_arrow.png and /dev/null differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/ic_action_about.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/ic_action_about.png deleted file mode 100644 index 624e745b6..000000000 Binary files a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/ic_action_about.png and /dev/null differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/ic_action_location_found.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/ic_action_location_found.png deleted file mode 100644 index 33f660f2d..000000000 Binary files a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/ic_action_location_found.png and /dev/null differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/ic_action_location_searching.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/ic_action_location_searching.png deleted file mode 100644 index fcb72b882..000000000 Binary files a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/ic_action_location_searching.png and /dev/null differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/ic_menu.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/ic_menu.png new file mode 100644 index 000000000..c62db8c86 Binary files /dev/null and b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/ic_menu.png differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/ic_my_location_black_24dp.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/ic_my_location_black_24dp.png new file mode 100644 index 000000000..5684aa7dc Binary files /dev/null and b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/ic_my_location_black_24dp.png differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xhdpi/direction_arrow.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xhdpi/direction_arrow.png deleted file mode 100644 index 5227caf44..000000000 Binary files a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xhdpi/direction_arrow.png and /dev/null differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xhdpi/ic_action_about.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xhdpi/ic_action_about.png deleted file mode 100644 index 3be315270..000000000 Binary files a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xhdpi/ic_action_about.png and /dev/null differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xhdpi/ic_action_location_found.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xhdpi/ic_action_location_found.png deleted file mode 100644 index 4c06441f9..000000000 Binary files a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xhdpi/ic_action_location_found.png and /dev/null differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xhdpi/ic_action_location_searching.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xhdpi/ic_action_location_searching.png deleted file mode 100644 index 7a3d4984f..000000000 Binary files a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xhdpi/ic_action_location_searching.png and /dev/null differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xhdpi/ic_menu.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xhdpi/ic_menu.png new file mode 100644 index 000000000..36859f094 Binary files /dev/null and b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xhdpi/ic_menu.png differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xhdpi/ic_my_location_black_24dp.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xhdpi/ic_my_location_black_24dp.png new file mode 100644 index 000000000..7faa3455f Binary files /dev/null and b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xhdpi/ic_my_location_black_24dp.png differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxhdpi/direction_arrow.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxhdpi/direction_arrow.png deleted file mode 100644 index 760edd439..000000000 Binary files a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxhdpi/direction_arrow.png and /dev/null differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxhdpi/ic_action_about.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxhdpi/ic_action_about.png deleted file mode 100644 index 0fe809b90..000000000 Binary files a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxhdpi/ic_action_about.png and /dev/null differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxhdpi/ic_action_location_found.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxhdpi/ic_action_location_found.png deleted file mode 100644 index 43f18ea01..000000000 Binary files a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxhdpi/ic_action_location_found.png and /dev/null differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxhdpi/ic_action_location_searching.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxhdpi/ic_action_location_searching.png deleted file mode 100644 index aff020a6b..000000000 Binary files a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxhdpi/ic_action_location_searching.png and /dev/null differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxhdpi/ic_menu.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxhdpi/ic_menu.png new file mode 100644 index 000000000..1a5109828 Binary files /dev/null and b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxhdpi/ic_menu.png differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxhdpi/ic_my_location_black_24dp.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxhdpi/ic_my_location_black_24dp.png new file mode 100644 index 000000000..d3a1ab08c Binary files /dev/null and b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxhdpi/ic_my_location_black_24dp.png differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxxhdpi/direction_arrow.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxxhdpi/direction_arrow.png deleted file mode 100644 index 8e3c59819..000000000 Binary files a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxxhdpi/direction_arrow.png and /dev/null differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxxhdpi/ic_menu.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxxhdpi/ic_menu.png new file mode 100644 index 000000000..c15d63467 Binary files /dev/null and b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxxhdpi/ic_menu.png differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxxhdpi/ic_my_location_black_24dp.png b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxxhdpi/ic_my_location_black_24dp.png new file mode 100644 index 000000000..0812b0e31 Binary files /dev/null and b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/drawable-xxxhdpi/ic_my_location_black_24dp.png differ diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml index 6a9bf8266..0e569b770 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_main.xml @@ -1,58 +1,71 @@ - + android:fitsSystemWindows="true"> - + android:layout_height="match_parent"> - - - - - - - - - - - - + android:background="@color/primary" + android:minHeight="?attr/actionBarSize" + android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/> - - + + + + + + + + + + + + + + + + + + + diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/fragment_main.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/fragment_main.xml index f303a02f5..642a5e7d3 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/fragment_main.xml +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/layout/fragment_main.xml @@ -1,4 +1,6 @@ + xmlns:mapbox="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + /> diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_drawer.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_drawer.xml new file mode 100644 index 000000000..a374ab18d --- /dev/null +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_drawer.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_main.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_main.xml index bf6dbbf36..014fa25d6 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_main.xml +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/menu/menu_main.xml @@ -1,11 +1,3 @@ - - diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/colors.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/colors.xml new file mode 100644 index 000000000..ac3b4fc30 --- /dev/null +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/colors.xml @@ -0,0 +1,7 @@ + + + #1E8CAB + #166B83 + #E55E5E + #FFFFFF + diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/dimens.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/dimens.xml new file mode 100644 index 000000000..f76eef07c --- /dev/null +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/dimens.xml @@ -0,0 +1,4 @@ + + + 16dp + diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml index 901e23192..403da277c 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml @@ -4,19 +4,26 @@ Mapbox GL Toggle GPS location - + Disable tracking + Use follow tracking mode + Use bearing tracking mode tracking + Toggle compass Toggle debug mode + Toggle point annotations FPS: - Map compass. Click to reset the map rotation to North. + Mapbox Streets + Emerald + Light + Dark + Satellite - - Mapbox Streets - Emerald - Light - Dark - + asset://styles/streets-v8.json + asset://styles/emerald-v8.json + asset://styles/light-v8.json + asset://styles/dark-v8.json + asset://styles/satellite-v8.json Day diff --git a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/styles.xml b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/styles.xml index 2399c78be..9248bc7d0 100644 --- a/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/styles.xml +++ b/android/java/MapboxGLAndroidSDKTestApp/src/main/res/values/styles.xml @@ -1,5 +1,12 @@ - + diff --git a/android/java/build.gradle b/android/java/build.gradle index 909007098..b1f4c8d08 100644 --- a/android/java/build.gradle +++ b/android/java/build.gradle @@ -1,4 +1,4 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. task wrapper(type: Wrapper) { - gradleVersion = '2.2.1' + gradleVersion = '2.5' } diff --git a/android/java/gradle/wrapper/gradle-wrapper.jar b/android/java/gradle/wrapper/gradle-wrapper.jar index 8c0fb64a8..30d399d8d 100644 Binary files a/android/java/gradle/wrapper/gradle-wrapper.jar and b/android/java/gradle/wrapper/gradle-wrapper.jar differ diff --git a/android/java/gradle/wrapper/gradle-wrapper.properties b/android/java/gradle/wrapper/gradle-wrapper.properties index 5adf98860..3212d53fb 100644 --- a/android/java/gradle/wrapper/gradle-wrapper.properties +++ b/android/java/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat Dec 06 12:43:10 EST 2014 +#Fri Aug 07 10:13:04 AEST 2015 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip diff --git a/android/mapboxgl-app.gyp b/android/mapboxgl-app.gypi similarity index 72% rename from android/mapboxgl-app.gyp rename to android/mapboxgl-app.gypi index d16d1dd4a..e3db1269d 100644 --- a/android/mapboxgl-app.gyp +++ b/android/mapboxgl-app.gypi @@ -25,14 +25,12 @@ '<@(boost_cflags)', ], 'libraries': [ - '<@(openssl_static_libs)', - '<@(curl_static_libs)', - '<@(png_static_libs)', + '<@(libpng_static_libs)', '<@(jpeg_static_libs)', - '<@(sqlite3_static_libs)', - '<@(uv_static_libs)', - '<@(nu_static_libs)', - '<@(zip_static_libs)', + '<@(sqlite_static_libs)', + '<@(libuv_static_libs)', + '<@(nunicode_static_libs)', + '<@(libzip_static_libs)', ], 'variables': { 'ldflags': [ @@ -42,13 +40,11 @@ '-lGLESv2', '-lstdc++', '-latomic', - '<@(png_ldflags)', + '<@(libpng_ldflags)', '<@(jpeg_ldflags)', - '<@(sqlite3_ldflags)', - '<@(openssl_ldflags)', - '<@(curl_ldflags)', + '<@(sqlite_ldflags)', '<@(zlib_ldflags)', - '<@(zip_ldflags)', + '<@(libzip_ldflags)', ], }, 'conditions': [ @@ -74,10 +70,9 @@ 'copies': [ { 'files': [ - '../common/ca-bundle.crt', '../styles/styles' ], - 'destination': '<(pwd)/java/MapboxGLAndroidSDK/src/main/assets' + 'destination': '<(pwd)/../android/java/MapboxGLAndroidSDK/src/main/assets' }, ], @@ -85,7 +80,7 @@ { 'action_name': 'Strip dynamic library', 'inputs': [ '<(PRODUCT_DIR)/lib.target/libmapbox-gl.so' ], - 'outputs': [ '<(pwd)/java/MapboxGLAndroidSDK/src/main/jniLibs/$(JNIDIR)/libmapbox-gl.so' ], + 'outputs': [ '<(pwd)/../android/java/MapboxGLAndroidSDK/src/main/jniLibs/$(JNIDIR)/libmapbox-gl.so' ], 'action': [ '$(STRIP)', '<@(_inputs)', '-o', '<@(_outputs)' ] }, ], diff --git a/bin/render.cpp b/bin/render.cpp index 5ae83c1d4..3da0c9358 100644 --- a/bin/render.cpp +++ b/bin/render.cpp @@ -85,13 +85,12 @@ int main(int argc, char *argv[]) { fileSource.setAccessToken(std::string(token)); } - HeadlessView view; + HeadlessView view(pixelRatio, width, height); Map map(view, fileSource, MapMode::Still); map.setStyleJSON(style, "."); map.setClasses(classes); - map.resize(width, height, pixelRatio); map.setLatLngZoom({ lat, lon }, zoom); map.setBearing(bearing); diff --git a/bin/render.gypi b/bin/render.gypi index 2e1dbd8f7..45c9ebb0e 100644 --- a/bin/render.gypi +++ b/bin/render.gypi @@ -27,18 +27,18 @@ 'variables' : { 'cflags_cc': [ - '<@(glfw3_cflags)', - '<@(uv_cflags)', + '<@(glfw_cflags)', + '<@(libuv_cflags)', '<@(boost_cflags)', ], 'ldflags': [ - '<@(glfw3_ldflags)', - '<@(uv_ldflags)', + '<@(glfw_ldflags)', + '<@(libuv_ldflags)', ], 'libraries': [ - '<@(glfw3_static_libs)', - '<@(uv_static_libs)', - '<@(boost_program_options_static_libs)' + '<@(glfw_static_libs)', + '<@(libuv_static_libs)', + '<@(boost_libprogram_options_static_libs)' ], }, diff --git a/binding.gyp b/binding.gyp new file mode 100644 index 000000000..e9f0d6edc --- /dev/null +++ b/binding.gyp @@ -0,0 +1,40 @@ +{ + 'includes': [ + 'gyp/common.gypi', + ], + 'targets': [ + { 'target_name': '<(module_name)', + 'dependencies': [ + 'mbgl.gyp:core', + 'mbgl.gyp:platform-<(platform_lib)', + 'mbgl.gyp:headless-<(headless_lib)', + ], + + 'include_dirs': [ "&2 echo -en "\033[0m"; -} -trap finish EXIT - -# Install mason -git submodule update --init .mason -export PATH="`pwd`/.mason:${PATH}" MASON_DIR="`pwd`/.mason" - -case ${MASON_PLATFORM} in - 'ios') - SQLITE_VERSION=system - LIBUV_VERSION=0.10.28 - ZLIB_VERSION=system - BOOST_VERSION=system - ;; - 'android') - SQLITE_VERSION=3.8.8.1 - LIBPNG_VERSION=1.6.16 - LIBJPEG_VERSION=v9a - OPENSSL_VERSION=1.0.1l - LIBCURL_VERSION=7.40.0 - LIBUV_VERSION=1.4.0 - ZLIB_VERSION=system - BOOST_VERSION=1.57.0 - NUNICODE_VERSION=1.5.1 - LIBZIP_VERSION=0.11.2 - ;; - *) - GLFW_VERSION=3.1 - SQLITE_VERSION=3.8.8.1 - LIBPNG_VERSION=1.6.16 - LIBJPEG_VERSION=v9a - LIBCURL_VERSION=system - LIBUV_VERSION=0.10.28 - ZLIB_VERSION=system - BOOST_VERSION=1.57.0 - BOOST_PROGRAM_OPTIONS_VERSION=1.57.0 - NUNICODE_VERSION=1.5.1 - LIBZIP_VERSION=0.11.2 - ;; -esac - -if [ ${MASON_PLATFORM} == 'linux' ] ; then - MESA_VERSION=10.4.3 - mason install mesa ${MESA_VERSION} - export PKG_CONFIG_PATH=`mason prefix mesa ${MESA_VERSION}`/lib/pkgconfig -fi - -function abort { >&2 echo -e "\033[1m\033[31m$1\033[0m"; exit 1; } - if [ -z ${CONFIG_FILE} ]; then abort 'You need to specify an output path for the configuration file' fi -if [[ ${MASON_PLATFORM} = 'android' && -z ${MASON_ANDROID_ABI} ]]; then - abort 'You need to specify an ABI for android platforms' -fi +# Make sure we clear all colors +trap '>&2 echo -en "\033[0m"' TERM INT -echo -n "MASON_PLATFORM=${MASON_PLATFORM} " -if [ ${MASON_PLATFORM} = 'android' ]; then echo -n "MASON_ANDROID_ABI=${MASON_ANDROID_ABI} " ; fi -echo $0 $@ +function abort { >&2 echo -e "\033[1m\033[31m$1\033[0m"; exit 1; } +function info { >&2 echo -e "\033[1m\033[33m$1\033[0m"; } +function warn { >&2 echo -e "\033[1m\033[33m$1\033[0m"; } + +# Install mason +export PATH="`pwd`/.mason:${PATH}" MASON_DIR="`pwd`/.mason" + +# You can override the function for a particular set of flags by defining a +# print_XXX_flags function in your dependencies.sh file +function print_flags { + local NAME=$1; shift + if [ "$(type -t print_${NAME}_flags)" = 'function' ]; then + print_${NAME}_flags + else + local VERSION=`echo "${NAME}_VERSION" | tr "[:lower:]" "[:upper:]"` + if [ ! -z ${!VERSION:-} ] ; then + mason install ${NAME} ${!VERSION} + for FLAGS in "$@" ; do + CONFIG+=" '${NAME}_${FLAGS}%': $(quote_flags $(mason ${FLAGS} ${NAME} ${!VERSION})),"$LN + done + else + warn "* Not using ${NAME}" + fi + fi +} + +function print_default_flags { + : +} + +function print_opengl_flags { + CONFIG+=" 'opengl_cflags%': [],"$LN + CONFIG+=" 'opengl_ldflags%': [],"$LN +} + +# Load dependencies +source scripts/${MASON_PLATFORM}/configure.sh +if [ -e scripts/${MASON_PLATFORM}/${MASON_PLATFORM_VERSION}/configure.sh ]; then + source scripts/${MASON_PLATFORM}/${MASON_PLATFORM_VERSION}/configure.sh +fi PYTHON=`which python || abort 'Cannot find python'` @@ -78,12 +58,6 @@ PYTHON=`which python || abort 'Cannot find python'` >&2 ${PYTHON} --version >&2 echo -en "\033[0m"; -PKG_CONFIG=`which pkg-config || abort 'Cannot find pkg-config'` - ->&2 echo -en "\033[1m\033[32m* Using pkg-config " ->&2 ${PKG_CONFIG} --version ->&2 echo -en "\033[0m"; - function quote_flags { ${PYTHON} -c "import sys, re; print filter(None, re.split('(? ${CONFIG_FILE} cat ${CONFIG_FILE} diff --git a/gyp/android.gyp b/gyp/android.gyp new file mode 100644 index 000000000..0a09edb6c --- /dev/null +++ b/gyp/android.gyp @@ -0,0 +1,5 @@ +{ + 'includes': [ + '../android/mapboxgl-app.gypi', + ], +} diff --git a/gyp/asset-fs.gypi b/gyp/asset-fs.gypi index f7fbea1c7..e84fabb62 100644 --- a/gyp/asset-fs.gypi +++ b/gyp/asset-fs.gypi @@ -17,14 +17,14 @@ 'variables': { 'cflags_cc': [ - '<@(uv_cflags)', + '<@(libuv_cflags)', '<@(boost_cflags)', ], 'ldflags': [ - '<@(uv_ldflags)', + '<@(libuv_ldflags)', ], 'libraries': [ - '<@(uv_static_libs)', + '<@(libuv_static_libs)', ], 'defines': [ '-DMBGL_ASSET_FS' diff --git a/gyp/asset-zip.gypi b/gyp/asset-zip.gypi index 8a195649f..e8854734e 100644 --- a/gyp/asset-zip.gypi +++ b/gyp/asset-zip.gypi @@ -18,21 +18,21 @@ 'variables': { 'cflags': [ - '<@(uv_cflags)', - '<@(zip_cflags)', + '<@(libuv_cflags)', + '<@(libzip_cflags)', ], 'cflags_cc': [ - '<@(uv_cflags)', - '<@(zip_cflags)', + '<@(libuv_cflags)', + '<@(libzip_cflags)', '<@(boost_cflags)', ], 'ldflags': [ - '<@(uv_ldflags)', - '<@(zip_ldflags)', + '<@(libuv_ldflags)', + '<@(libzip_ldflags)', ], 'libraries': [ - '<@(uv_static_libs)', - '<@(zip_static_libs)', + '<@(libuv_static_libs)', + '<@(libzip_static_libs)', ], 'defines': [ '-DMBGL_ASSET_ZIP' diff --git a/gyp/cache-sqlite.gypi b/gyp/cache-sqlite.gypi index a9d21924c..704dc490e 100644 --- a/gyp/cache-sqlite.gypi +++ b/gyp/cache-sqlite.gypi @@ -19,17 +19,17 @@ 'variables': { 'cflags_cc': [ - '<@(uv_cflags)', - '<@(sqlite3_cflags)', + '<@(libuv_cflags)', + '<@(sqlite_cflags)', ], 'ldflags': [ - '<@(uv_ldflags)', - '<@(sqlite3_ldflags)', + '<@(libuv_ldflags)', + '<@(sqlite_ldflags)', '<@(zlib_ldflags)', ], 'libraries': [ - '<@(uv_static_libs)', - '<@(sqlite3_static_libs)', + '<@(libuv_static_libs)', + '<@(sqlite_static_libs)', '<@(zlib_static_libs)', ], }, diff --git a/gyp/common.gypi b/gyp/common.gypi index d7d21f761..b44f69868 100644 --- a/gyp/common.gypi +++ b/gyp/common.gypi @@ -20,6 +20,7 @@ '-Wno-variadic-macros', '-frtti', '-fexceptions', + '${CFLAGS}', ], 'GCC_WARN_PEDANTIC': 'YES', 'GCC_WARN_UNINITIALIZED_AUTOS': 'YES_AGGRESSIVE', @@ -37,6 +38,7 @@ '-Wno-c++1y-extensions', '-frtti', '-fexceptions', + '${CFLAGS}', ], }], ['OS=="linux"', { diff --git a/gyp/core.gypi b/gyp/core.gypi index 246419371..f9b361032 100644 --- a/gyp/core.gypi +++ b/gyp/core.gypi @@ -28,21 +28,21 @@ 'variables': { 'cflags_cc': [ - '<@(uv_cflags)', + '<@(libuv_cflags)', '<@(opengl_cflags)', '<@(boost_cflags)', ], 'cflags': [ - '<@(uv_cflags)', + '<@(libuv_cflags)', '<@(opengl_cflags)', '-fPIC' ], 'ldflags': [ - '<@(uv_ldflags)', + '<@(libuv_ldflags)', '<@(opengl_ldflags)', ], 'libraries': [ - '<@(uv_static_libs)', + '<@(libuv_static_libs)', ], }, diff --git a/gyp/http-android.gypi b/gyp/http-android.gypi new file mode 100644 index 000000000..c210db0fb --- /dev/null +++ b/gyp/http-android.gypi @@ -0,0 +1,70 @@ +{ + 'targets': [ + { 'target_name': 'http-android', + 'product_name': 'mbgl-http-android', + 'type': 'static_library', + 'standalone_static_library': 1, + 'hard_dependency': 1, + + 'sources': [ + '../platform/android/http_request_android.cpp', + ], + + 'include_dirs': [ + '../include', + '../src', + ], + + 'variables': { + 'cflags_cc': [ + '<@(libuv_cflags)', + '<@(boost_cflags)', + ], + 'ldflags': [ + '<@(libuv_ldflags)', + ], + 'libraries': [ + '<@(libuv_static_libs)', + ], + 'defines': [ + '-DMBGL_HTTP_ANDROID' + ], + }, + + 'conditions': [ + ['OS == "mac"', { + 'xcode_settings': { + 'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ], + }, + }, { + 'cflags_cc': [ '<@(cflags_cc)' ], + }], + ], + + 'direct_dependent_settings': { + 'conditions': [ + ['OS == "mac"', { + 'xcode_settings': { + 'OTHER_CFLAGS': [ '<@(defines)' ], + 'OTHER_CPLUSPLUSFLAGS': [ '<@(defines)' ], + } + }, { + 'cflags': [ '<@(defines)' ], + 'cflags_cc': [ '<@(defines)' ], + }] + ], + }, + + 'link_settings': { + 'conditions': [ + ['OS == "mac"', { + 'libraries': [ '<@(libraries)' ], + 'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(ldflags)' ] } + }, { + 'libraries': [ '<@(libraries)', '<@(ldflags)' ], + }] + ], + }, + }, + ], +} diff --git a/gyp/http-curl.gypi b/gyp/http-curl.gypi index b7c583235..7ae5b1b75 100644 --- a/gyp/http-curl.gypi +++ b/gyp/http-curl.gypi @@ -17,17 +17,17 @@ 'variables': { 'cflags_cc': [ - '<@(uv_cflags)', - '<@(curl_cflags)', + '<@(libuv_cflags)', + '<@(libcurl_cflags)', '<@(boost_cflags)', ], 'ldflags': [ - '<@(uv_ldflags)', - '<@(curl_ldflags)', + '<@(libuv_ldflags)', + '<@(libcurl_ldflags)', ], 'libraries': [ - '<@(uv_static_libs)', - '<@(curl_static_libs)', + '<@(libuv_static_libs)', + '<@(libcurl_static_libs)', ], 'defines': [ '-DMBGL_HTTP_CURL' @@ -38,9 +38,9 @@ ['host == "android"', { 'variables': { # Android uses libzip and openssl to set CURL's CA bundle. - 'cflags_cc': [ '<@(zip_cflags)', '<@(openssl_cflags)' ], - 'ldflags': [ '<@(zip_ldflags)', ], - 'libraries': [ '<@(zip_static_libs)', ], + 'cflags_cc': [ '<@(libzip_cflags)', '<@(openssl_cflags)' ], + 'ldflags': [ '<@(libzip_ldflags)', ], + 'libraries': [ '<@(libzip_static_libs)', ], }, }], ['OS == "mac"', { diff --git a/gyp/http-nsurl.gypi b/gyp/http-nsurl.gypi index a64c8508e..efd2c18a7 100644 --- a/gyp/http-nsurl.gypi +++ b/gyp/http-nsurl.gypi @@ -17,14 +17,14 @@ 'variables': { 'cflags_cc': [ - '<@(uv_cflags)', + '<@(libuv_cflags)', ], 'ldflags': [ '-framework Foundation', # For NSURLRequest - '<@(uv_ldflags)', + '<@(libuv_ldflags)', ], 'libraries': [ - '<@(uv_static_libs)', + '<@(libuv_static_libs)', ], 'defines': [ '-DMBGL_HTTP_NSURL' diff --git a/gyp/linux.gyp b/gyp/linux.gyp index 0f7126934..6cc168ba8 100644 --- a/gyp/linux.gyp +++ b/gyp/linux.gyp @@ -1,7 +1,10 @@ { 'includes': [ '../linux/mapboxgl-app.gypi', - '../test/test.gypi', - '../bin/render.gypi', + ], + + 'conditions': [ + ['test', { 'includes': [ '../test/test.gypi' ] } ], + ['render', { 'includes': [ '../bin/render.gypi' ] } ], ], } diff --git a/gyp/platform-android.gypi b/gyp/platform-android.gypi index 7701393bb..41a5733ac 100644 --- a/gyp/platform-android.gypi +++ b/gyp/platform-android.gypi @@ -22,23 +22,23 @@ 'variables': { 'cflags_cc': [ - '<@(png_cflags)', + '<@(libpng_cflags)', '<@(jpeg_cflags)', - '<@(uv_cflags)', - '<@(nu_cflags)', + '<@(libuv_cflags)', + '<@(nunicode_cflags)', '<@(boost_cflags)', ], 'ldflags': [ - '<@(png_ldflags)', + '<@(libpng_ldflags)', '<@(jpeg_ldflags)', - '<@(uv_ldflags)', - '<@(nu_ldflags)', + '<@(libuv_ldflags)', + '<@(nunicode_ldflags)', ], 'libraries': [ - '<@(png_static_libs)', + '<@(libpng_static_libs)', '<@(jpeg_static_libs)', - '<@(uv_static_libs)', - '<@(nu_static_libs)', + '<@(libuv_static_libs)', + '<@(nunicode_static_libs)', ], }, diff --git a/gyp/platform-ios.gypi b/gyp/platform-ios.gypi index 52a66ea45..6234f4d88 100644 --- a/gyp/platform-ios.gypi +++ b/gyp/platform-ios.gypi @@ -17,9 +17,11 @@ '../platform/darwin/image.mm', '../platform/darwin/nsthread.mm', '../platform/darwin/reachability.m', - '../include/mbgl/ios/MapboxGL.h', + '../include/mbgl/ios/Mapbox.h', '../platform/ios/MGLMapboxEvents.h', '../platform/ios/MGLMapboxEvents.m', + '../include/mbgl/ios/MGLMapCamera.h', + '../platform/ios/MGLMapCamera.mm', '../include/mbgl/ios/MGLMapView.h', '../include/mbgl/ios/MGLMapView+IBAdditions.h', '../platform/ios/MGLMapView.mm', @@ -50,6 +52,8 @@ '../platform/ios/MGLPolygon.m', '../include/mbgl/ios/MGLShape.h', '../platform/ios/MGLShape.m', + '../include/mbgl/ios/MGLAnnotationImage.h', + '../platform/ios/MGLAnnotationImage.m', '../platform/ios/NSBundle+MGLAdditions.h', '../platform/ios/NSBundle+MGLAdditions.m', '../platform/ios/NSException+MGLAdditions.h', @@ -59,15 +63,16 @@ '../platform/ios/NSString+MGLAdditions.m', '../platform/ios/vendor/SMCalloutView/SMCalloutView.h', '../platform/ios/vendor/SMCalloutView/SMCalloutView.m', + '../platform/ios/resources/', ], 'variables': { 'cflags_cc': [ - '<@(uv_cflags)', + '<@(libuv_cflags)', '<@(boost_cflags)', ], 'libraries': [ - '<@(uv_static_libs)', + '<@(libuv_static_libs)', ], 'ldflags': [ '-framework CoreLocation', @@ -103,7 +108,7 @@ '../include', ], 'mac_bundle_resources': [ - ' +#include // Forward definition of JNI types typedef class _jclass* jclass; +typedef class _jstring* jstring; +typedef class _jobject* jobject; +typedef class _jlongArray* jlongArray; typedef struct _jmethodID* jmethodID; typedef struct _jfieldID* jfieldID; +struct _JavaVM; +typedef _JavaVM JavaVM; + +struct _JNIEnv; +typedef _JNIEnv JNIEnv; + namespace mbgl { namespace android { +extern JavaVM* theJVM; + extern std::string cachePath; extern std::string dataPath; extern std::string apkPath; @@ -31,6 +43,36 @@ extern jfieldID latLngZoomLatitudeId; extern jfieldID latLngZoomLongitudeId; extern jfieldID latLngZoomZoomId; +extern jclass bboxClass; +extern jmethodID bboxConstructorId; +extern jfieldID bboxLatNorthId; +extern jfieldID bboxLatSouthId; +extern jfieldID bboxLonEastId; +extern jfieldID bboxLonWestId; + +extern jclass markerClass; +extern jmethodID markerConstructorId; +extern jfieldID markerPositionId; +extern jfieldID markerSpriteId; + +extern jclass polylineClass; +extern jmethodID polylineConstructorId; +extern jfieldID polylineAlphaId; +extern jfieldID polylineVisibleId; +extern jfieldID polylineColorId; +extern jfieldID polylineWidthId; +extern jfieldID polylinePointsId; + +extern jclass polygonClass; +extern jmethodID polygonConstructorId; +extern jfieldID polygonAlphaId; +extern jfieldID polygonVisibleId; +extern jfieldID polygonFillColorId; +extern jfieldID polygonStrokeColorId; +extern jfieldID polygonStrokeWidthId; +extern jfieldID polygonPointsId; +extern jfieldID polygonHolesId; + extern jclass runtimeExceptionClass; extern jclass nullPointerExceptionClass; @@ -50,6 +92,23 @@ extern jmethodID pointFConstructorId; extern jfieldID pointFXId; extern jfieldID pointFYId; +extern jclass httpContextClass; +extern jmethodID httpContextGetInstanceId; +extern jmethodID httpContextCreateRequestId; + +extern jclass httpRequestClass; +extern jmethodID httpRequestStartId; +extern jmethodID httpRequestCancelId; + +extern bool throw_jni_error(JNIEnv *env, const char *msg); +extern bool attach_jni_thread(JavaVM* vm, JNIEnv** env, std::string threadName); +extern void detach_jni_thread(JavaVM* vm, JNIEnv** env, bool detach); +extern std::string std_string_from_jstring(JNIEnv *env, jstring jstr); +extern jstring std_string_to_jstring(JNIEnv *env, std::string str); +extern std::vector std_vector_string_from_jobject(JNIEnv *env, jobject jlist); +extern jobject std_vector_string_to_jobject(JNIEnv *env, std::vector vector); +extern jlongArray std_vector_uint_to_jobject(JNIEnv *env, std::vector vector); + } } diff --git a/include/mbgl/android/native_map_view.hpp b/include/mbgl/android/native_map_view.hpp index af8f76bd7..d5606ae37 100644 --- a/include/mbgl/android/native_map_view.hpp +++ b/include/mbgl/android/native_map_view.hpp @@ -17,14 +17,18 @@ namespace android { class NativeMapView : public mbgl::View, private mbgl::util::noncopyable { public: - NativeMapView(JNIEnv *env, jobject obj); + NativeMapView(JNIEnv *env, jobject obj, float pixelRatio, int availableProcessors, size_t totalMemory); virtual ~NativeMapView(); + float getPixelRatio() const override; + std::array getSize() const override; + std::array getFramebufferSize() const override; void activate() override; void deactivate() override; void notify() override; void invalidate() override; - void swap() override; + void beforeRender() override; + void afterRender() override; void notifyMapChange(mbgl::MapChange) override; @@ -48,6 +52,9 @@ public: void onInvalidate(); + void resizeView(int width, int height); + void resizeFramebuffer(int width, int height); + private: EGLConfig chooseConfig(const EGLConfig configs[], EGLint numConfigs); @@ -73,6 +80,15 @@ private: bool fpsEnabled = false; double fps = 0.0; + int width = 0; + int height = 0; + int fbWidth = 0; + int fbHeight = 0; + const float pixelRatio; + + int availableProcessors = 0; + size_t totalMemory = 0; + // Ensure these are initialised last mbgl::SQLiteCache fileCache; mbgl::DefaultFileSource fileSource; diff --git a/include/mbgl/annotation/sprite_image.hpp b/include/mbgl/annotation/sprite_image.hpp new file mode 100644 index 000000000..7d8ea0501 --- /dev/null +++ b/include/mbgl/annotation/sprite_image.hpp @@ -0,0 +1,40 @@ +#ifndef MBGL_ANNOTATIONS_SPRITE_IMAGE +#define MBGL_ANNOTATIONS_SPRITE_IMAGE + +#include +#include + +#include +#include +#include + +namespace mbgl { + +class SpriteImage : private util::noncopyable { +public: + SpriteImage( + uint16_t width, uint16_t height, float pixelRatio, std::string&& data, bool sdf = false); + + // Logical dimensions of the sprite image. + const uint16_t width; + const uint16_t height; + + // Pixel ratio of the sprite image. + const float pixelRatio; + + // Physical dimensions of the sprite image. + const uint16_t pixelWidth; + const uint16_t pixelHeight; + + // A string of an RGBA8 representation of the sprite. It must have exactly + // (width * ratio) * (height * ratio) * 4 (RGBA) bytes. The scan lines may + // not have gaps between them (i.e. stride == 0). + const std::string data; + + // Whether this image should be interpreted as a signed distance field icon. + const bool sdf; +}; + +} + +#endif diff --git a/include/mbgl/ios/MGLAnnotationImage.h b/include/mbgl/ios/MGLAnnotationImage.h new file mode 100644 index 000000000..9707fca8f --- /dev/null +++ b/include/mbgl/ios/MGLAnnotationImage.h @@ -0,0 +1,16 @@ +#import + +#import "MGLTypes.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MGLAnnotationImage : NSObject + +@property (nonatomic, readonly) UIImage *image; +@property (nonatomic, readonly) NSString *reuseIdentifier; + ++ (instancetype)annotationImageWithImage:(UIImage *)image reuseIdentifier:(NSString *)reuseIdentifier; + +@end + +NS_ASSUME_NONNULL_END diff --git a/include/mbgl/ios/MGLGeometry.h b/include/mbgl/ios/MGLGeometry.h index 6226af1d0..2dee7c76c 100644 --- a/include/mbgl/ios/MGLGeometry.h +++ b/include/mbgl/ios/MGLGeometry.h @@ -3,6 +3,7 @@ #import "MGLTypes.h" #import +#import NS_ASSUME_NONNULL_BEGIN @@ -82,4 +83,14 @@ NS_INLINE NSString *MGLStringFromCoordinateBounds(MGLCoordinateBounds bounds) { bounds.ne.latitude, bounds.ne.longitude]; } +NS_INLINE CGFloat MGLRadiansFromDegrees(CLLocationDegrees degrees) +{ + return degrees * M_PI / 180; +} + +NS_INLINE CLLocationDegrees MGLDegreesFromRadians(CGFloat radians) +{ + return radians * 180 / M_PI; +} + NS_ASSUME_NONNULL_END diff --git a/include/mbgl/ios/MGLMapCamera.h b/include/mbgl/ios/MGLMapCamera.h new file mode 100644 index 000000000..68f3923fd --- /dev/null +++ b/include/mbgl/ios/MGLMapCamera.h @@ -0,0 +1,36 @@ +#import "Mapbox.h" + +#pragma once + +NS_ASSUME_NONNULL_BEGIN + +/** An `MGLMapCamera` object represents a viewpoint from which the user observes some point on an `MGLMapView`. */ +@interface MGLMapCamera : NSObject + +/** Coordinate at the center of the map view. */ +@property (nonatomic) CLLocationCoordinate2D centerCoordinate; + +/** Heading measured in degrees clockwise from true north. */ +@property (nonatomic) CLLocationDirection heading; + +/** Pitch toward the horizon measured in degrees, with 0 degrees resulting in a two-dimensional map. */ +@property (nonatomic) CGFloat pitch; + +/** Meters above ground level. */ +@property (nonatomic) CLLocationDistance altitude; + +/** Returns a new camera with all properties set to 0. */ ++ (instancetype)camera; + ++ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate + fromEyeCoordinate:(CLLocationCoordinate2D)eyeCoordinate + eyeAltitude:(CLLocationDistance)eyeAltitude; + ++ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate + fromDistance:(CLLocationDistance)distance + pitch:(CGFloat)pitch + heading:(CLLocationDirection)heading; + +@end + +NS_ASSUME_NONNULL_END diff --git a/include/mbgl/ios/MGLMapView.h b/include/mbgl/ios/MGLMapView.h index 918f521a8..da500f1ef 100644 --- a/include/mbgl/ios/MGLMapView.h +++ b/include/mbgl/ios/MGLMapView.h @@ -1,10 +1,13 @@ #import "MGLGeometry.h" +#import "MGLMapCamera.h" #import #import NS_ASSUME_NONNULL_BEGIN +@class MGLAnnotationImage; +@class MGLMapCamera; @class MGLUserLocation; @class MGLPolyline; @class MGLPolygon; @@ -12,6 +15,7 @@ NS_ASSUME_NONNULL_BEGIN @protocol MGLMapViewDelegate; @protocol MGLAnnotation; +@protocol MGLOverlay; /** An MGLMapView object provides an embeddable map interface, similar to the one provided by Apple's MapKit. You use this class to display map information and to manipulate the map contents from your application. You can center the map on a given coordinate, specify the size of the area you want to display, and style the features of the map to fit your application's use case. * @@ -73,6 +77,22 @@ IB_DESIGNABLE * The default value of this property is `YES`. */ @property(nonatomic, getter=isRotateEnabled) BOOL rotateEnabled; +/** A Boolean value that determines whether the user may change the pitch (tilt) of the map. + * + * This property controls only user interactions with the map. If you set the value of this property to `NO`, you may still change the pitch of the map programmatically. + * + * The default value of this property is `YES`. */ +@property(nonatomic, getter=isPitchEnabled) BOOL pitchEnabled; + +/** The compass image view shown in the upper-right when the map is rotated. */ +@property (nonatomic, readonly) UIImageView *compassView; + +/** The Mapbox logo image view shown in the lower-left of the map. */ +@property (nonatomic, readonly) UIImageView *logoView; + +/** The button shown in the lower-right of the map which when pressed displays the map attribution information. */ +@property (nonatomic, readonly) UIButton *attributionButton; + #pragma mark - Accessing the Delegate /** @name Accessing the Delegate */ @@ -118,6 +138,8 @@ IB_DESIGNABLE * @param animated Specify `YES` if you want the map view to animate scrolling and zooming to the new location or `NO` if you want the map to display the new location immediately. */ - (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel animated:(BOOL)animated; +- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel direction:(CLLocationDirection)direction animated:(BOOL)animated; + /** The coordinate bounds visible in the receiver’s viewport. * * Changing the value of this property updates the receiver immediately. If you want to animate the change, call `setVisibleCoordinateBounds:animated:` instead. */ @@ -141,6 +163,13 @@ IB_DESIGNABLE * @param animated Specify `YES` to animate the change by smoothly scrolling and zooming or `NO` to immediately display the given bounds. */ - (void)setVisibleCoordinates:(CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated; +/** Sets the visible region so that the map displays the specified annotations. +* +* Calling this method updates the value in the visibleCoordinateBounds property and potentially other properties to reflect the new map region. +* @param annotations The annotations that you want to be visible in the map. +* @param animated `YES` if you want the map region change to be animated, or `NO` if you want the map to display the new region immediately without animations. */ +- (void)showAnnotations:(NS_ARRAY_OF(id ) *)annotations animated:(BOOL)animated; + /** The heading of the map (measured in degrees) relative to true north. * * The value `0` means that the top edge of the map view corresponds to true north. The value `90` means the top of the map is pointing due east. The value `180` means the top of the map points due south, and so on. */ @@ -156,6 +185,20 @@ IB_DESIGNABLE /** Resets the map rotation to a northern heading. */ - (IBAction)resetNorth; +/** A camera representing the current viewpoint of the map. */ +@property (nonatomic, copy) MGLMapCamera *camera; + +/** Moves the viewpoint to a different location with respect to the map with an optional transition animation. +* @param camera The new viewpoint. +* @param animated Specify `YES` if you want the map view to animate the change to the new viewpoint or `NO` if you want the map to display the new viewpoint immediately. */ +- (void)setCamera:(MGLMapCamera *)camera animated:(BOOL)animated; + +/** Moves the viewpoint to a different location with respect to the map with an optional transition duration and timing function. +* @param camera The new viewpoint. +* @param duration The amount of time, measured in seconds, that the transition animation should take. Specify `0` to jump to the new viewpoint instantaneously. +* @param function A timing function used for the animation. Set this parameter to `nil` for a transition that matches most system animations. If the duration is `0`, this parameter is ignored. */ +- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function; + #pragma mark - Converting Map Coordinates /** @name Converting Map Coordinates */ @@ -249,6 +292,18 @@ IB_DESIGNABLE * @param annotations The array of annotations to remove. Objects in the array must conform to the MGLAnnotation protocol. */ - (void)removeAnnotations:(NS_ARRAY_OF(id ) *)annotations; +/** Returns a reusable annotation image object located by its identifier. +* +* For performance reasons, you should generally reuse MGLAnnotationImage objects for annotations in your map views. Dequeueing saves time and memory during performance-critical operations such as scrolling. +* +* @param identifier A string identifying the annotation image to be reused. This string is the same one you specify when initially returning the annotation image object using the mapView:imageForAnnotation: method. +* @return An annotation image object with the specified identifier, or `nil` if no such object exists in the reuse queue. */ +- (nullable MGLAnnotationImage *)dequeueReusableAnnotationImageWithIdentifier:(NSString *)identifier; + +#pragma mark - Managing Annotation Selections + +/** @name Managing Annotation Selections */ + /** The annotations that are currently selected. * * Assigning a new array to this property selects only the first annotation in the array. */ @@ -267,6 +322,38 @@ IB_DESIGNABLE * @param animated If `YES`, the callout view is animated offscreen. */ - (void)deselectAnnotation:(id )annotation animated:(BOOL)animated; +#pragma mark - Adding Overlays + +/** @name Adding Overlays */ + +/** Adds a single overlay object to the map. +* +* To remove an overlay from a map, use the removeOverlay: method. +* @param overlay The overlay object to add. This object must conform to the MGLOverlay protocol. */ +- (void)addOverlay:(id )overlay; + +/** Adds an array of overlay objects to the map. +* +* To remove multiple overlays from a map, use the removeOverlays: method. +* @param overlays An array of objects, each of which must conform to the MGLOverlay protocol. */ +- (void)addOverlays:(NS_ARRAY_OF(id ) *)overlays; + +#pragma mark - Removing Overlays + +/** @name Removing Overlays */ + +/** Removes a single overlay object from the map. +* +* If the specified overlay is not currently associated with the map view, this method does nothing. +* @param overlay The overlay object to remove. */ +- (void)removeOverlay:(id )overlay; + +/** Removes one or more overlay objects from the map. +* +* If a given overlay object is not associated with the map view, it is ignored. +* @param overlays An array of objects, each of which conforms to the MGLOverlay protocol. */ +- (void)removeOverlays:(NS_ARRAY_OF(id ) *)overlays; + #pragma mark - Displaying the User's Location /** @name Displaying the User's Location */ @@ -323,11 +410,13 @@ IB_DESIGNABLE /** @name Managing the Display of Annotations */ -/** Returns the style's symbol name to use for the marker for the specified point annotation object. -* @param mapView The map view that requested the annotation symbol name. -* @param annotation The object representing the annotation that is about to be displayed. -* @return The marker symbol to display for the specified annotation or `nil` if you want to display the default symbol. */ -- (nullable NSString *)mapView:(MGLMapView *)mapView symbolNameForAnnotation:(id )annotation; +- (nullable NSString *)mapView:(MGLMapView *)mapView symbolNameForAnnotation:(id )annotation __attribute__((unavailable("Use -mapView:imageForAnnotation:."))); + +/** Returns an image object to use for the marker for the specified point annotation object. +* @param mapView The map view that requested the annotation image. +* @param annotation The object representing the annotation that is about to be displayed. +* @return The image object to display for the specified annotation or `nil` if you want to display the default marker image. */ +- (nullable MGLAnnotationImage *)mapView:(MGLMapView *)mapView imageForAnnotation:(id )annotation; /** Returns the alpha value to use when rendering a shape annotation. Defaults to `1.0`. * @param mapView The map view rendering the shape annotation. @@ -390,13 +479,24 @@ IB_DESIGNABLE // Responding to Map Position Changes -// TODO +/** Tells the delegate that the region displayed by the map view is about to change. + * + * This method is called whenever the currently displayed map region will start changing. + * @param mapView The map view whose visible region will change. + * @param animated Whether the change will cause an animated effect on the map. */ - (void)mapView:(MGLMapView *)mapView regionWillChangeAnimated:(BOOL)animated; -// TODO +/** Tells the delegate that the region displayed by the map view is changing. + * + * This method is called whenever the currently displayed map region changes. During movement, this method may be called many times to report updates to the map position. Therefore, your implementation of this method should be as lightweight as possible to avoid affecting performance. + * @param mapView The map view whose visible region is changing. */ - (void)mapViewRegionIsChanging:(MGLMapView *)mapView; -// TODO +/** Tells the delegate that the region displayed by the map view just changed. + * + * This method is called whenever the currently displayed map region has finished changing. + * @param mapView The map view whose visible region changed. + * @param animated Whether the change caused an animated effect on the map. */ - (void)mapView:(MGLMapView *)mapView regionDidChangeAnimated:(BOOL)animated; #pragma mark - Loading the Map Data @@ -444,7 +544,7 @@ IB_DESIGNABLE /** Tells the delegate that the location of the user was updated. * -* While the showsUserLocation property is set to `YES`, this method is called whenever a new location update is received by the map view. This method is also called if the map view’s user tracking mode is set to MGLUserTrackingModeFollowWithHeading and the heading changes. +* While the showsUserLocation property is set to `YES`, this method is called whenever a new location update is received by the map view. This method is also called if the map view’s user tracking mode is set to MGLUserTrackingModeFollowWithHeading and the heading changes, or if it is set to MGLUserTrackingModeFollowWithCourse and the course changes. * * This method is not called if the application is currently running in the background. If you want to receive location updates while running in the background, you must use the Core Location framework. * diff --git a/include/mbgl/ios/MGLOverlay.h b/include/mbgl/ios/MGLOverlay.h index 104a5e608..eee362d57 100644 --- a/include/mbgl/ios/MGLOverlay.h +++ b/include/mbgl/ios/MGLOverlay.h @@ -8,7 +8,7 @@ NS_ASSUME_NONNULL_BEGIN /** The `MGLOverlay` protocol defines a specific type of annotation that represents both a point and an area on a map. Overlay objects are essentially data objects that contain the geographic data needed to represent the map area. For example, overlays can take the form of common shapes such as rectangles and circles. They can also describe polygons and other complex shapes. * -* You use overlays to layer more sophisticated content on top of a map view. For example, you could use an overlay to show the boundaries of a national park or trace a bus route along city streets. Mapbox GL defines several concrete classes that conform to this protocol and define standard shapes. +* You use overlays to layer more sophisticated content on top of a map view. For example, you could use an overlay to show the boundaries of a national park or trace a bus route along city streets. The Mapbox iOS SDK defines several concrete classes that conform to this protocol and define standard shapes. * * Because overlays are also annotations, they have similar usage pattern to annotations. When added to a map view using the `addOverlay:` method, that view detects whenever the overlay’s defined region intersects the visible portion of the map. At that point, the map view asks its delegate to provide a special overlay view to draw the visual representation of the overlay. If you add an overlay to a map view as an annotation instead, it is treated as an annotation with a single point. */ @protocol MGLOverlay diff --git a/include/mbgl/ios/MGLPolyline.h b/include/mbgl/ios/MGLPolyline.h index 7cbbadeca..07c5286c8 100644 --- a/include/mbgl/ios/MGLPolyline.h +++ b/include/mbgl/ios/MGLPolyline.h @@ -9,7 +9,7 @@ NS_ASSUME_NONNULL_BEGIN /** The `MGLPolyline` class represents a shape consisting of one or more points that define connecting line segments. The points are connected end-to-end in the order they are provided. The first and last points are not connected to each other. */ @interface MGLPolyline : MGLMultiPoint -/** Creates and returns an `MGLPolygon` object from the specified set of coordinates. +/** Creates and returns an `MGLPolyline` object from the specified set of coordinates. * @param coords The array of coordinates defining the shape. The data in this array is copied to the new object. * @param count The number of items in the _`coords`_ array. * @return A new polyline object. */ diff --git a/include/mbgl/ios/MGLTypes.h b/include/mbgl/ios/MGLTypes.h index 75a0d4619..f36fc3f44 100644 --- a/include/mbgl/ios/MGLTypes.h +++ b/include/mbgl/ios/MGLTypes.h @@ -21,7 +21,9 @@ typedef NS_ENUM(NSUInteger, MGLUserTrackingMode) { /** The map follows the user location. */ MGLUserTrackingModeFollow, /** The map follows the user location and rotates when the heading changes. */ - MGLUserTrackingModeFollowWithHeading + MGLUserTrackingModeFollowWithHeading, + /** The map follows the user location and rotates when the course changes. */ + MGLUserTrackingModeFollowWithCourse, }; NS_ASSUME_NONNULL_END diff --git a/include/mbgl/ios/MapboxGL.h b/include/mbgl/ios/Mapbox.h similarity index 84% rename from include/mbgl/ios/MapboxGL.h rename to include/mbgl/ios/Mapbox.h index bcdc5359b..f05f0c842 100644 --- a/include/mbgl/ios/MapboxGL.h +++ b/include/mbgl/ios/Mapbox.h @@ -1,5 +1,7 @@ #import "MGLAccountManager.h" #import "MGLAnnotation.h" +#import "MGLAnnotationImage.h" +#import "MGLMapCamera.h" #import "MGLGeometry.h" #import "MGLMapView.h" #import "MGLMultiPoint.h" diff --git a/include/mbgl/map/camera.hpp b/include/mbgl/map/camera.hpp new file mode 100644 index 000000000..bd0b353ba --- /dev/null +++ b/include/mbgl/map/camera.hpp @@ -0,0 +1,22 @@ +#ifndef MBGL_MAP_CAMERA +#define MBGL_MAP_CAMERA + +#include +#include +#include +#include + +namespace mbgl { + +struct CameraOptions { + mapbox::util::optional center; + mapbox::util::optional zoom; + mapbox::util::optional angle; + mapbox::util::optional pitch; + mapbox::util::optional duration; + mapbox::util::optional easing; +}; + +} + +#endif /* MBGL_MAP_CAMERA */ diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index e1dd4671b..4cd9293c9 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -2,6 +2,7 @@ #define MBGL_MAP_MAP #include +#include #include #include #include @@ -21,6 +22,7 @@ class View; class MapData; class MapContext; class StillImage; +class SpriteImage; class Transform; class PointAnnotation; class ShapeAnnotation; @@ -35,14 +37,17 @@ enum class AnnotationType : uint8_t { Shape = 1 << 1, }; +struct EdgeInsets { + double top = 0; + double left = 0; + double bottom = 0; + double right = 0; +}; + using AnnotationIDs = std::vector; using AnnotationSegment = std::vector; using AnnotationSegments = std::vector; -using EdgeInsets = struct { - double top, left, bottom, right; -}; - class Map : private util::noncopyable { friend class View; @@ -62,11 +67,11 @@ public: using StillImageCallback = std::function)>; void renderStill(StillImageCallback callback); - // Triggers a synchronous or asynchronous render. + // Triggers a synchronous render. void renderSync(); // Notifies the Map thread that the state has changed and an update might be necessary. - void update(Update update = Update::Nothing); + void update(Update update); // Styling void addClass(const std::string&); @@ -75,8 +80,12 @@ public: void setClasses(const std::vector&); std::vector getClasses() const; - void setDefaultTransitionDuration(Duration = Duration::zero()); - Duration getDefaultTransitionDuration(); + void setDefaultTransitionDuration(const Duration& = Duration::zero()); + Duration getDefaultTransitionDuration() const; + + void setDefaultTransitionDelay(const Duration& = Duration::zero()); + Duration getDefaultTransitionDelay() const; + void setStyleURL(const std::string& url); void setStyleJSON(const std::string& json, const std::string& base = ""); std::string getStyleURL() const; @@ -86,34 +95,41 @@ public: void cancelTransitions(); void setGestureInProgress(bool); + // Camera + void jumpTo(CameraOptions options); + void easeTo(CameraOptions options); + // Position - void moveBy(double dx, double dy, Duration = Duration::zero()); - void setLatLng(LatLng latLng, Duration = Duration::zero()); + void moveBy(double dx, double dy, const Duration& = Duration::zero()); + void setLatLng(LatLng latLng, const Duration& = Duration::zero()); LatLng getLatLng() const; void resetPosition(); // Scale - void scaleBy(double ds, double cx = -1, double cy = -1, Duration = Duration::zero()); - void setScale(double scale, double cx = -1, double cy = -1, Duration = Duration::zero()); + void scaleBy(double ds, double cx = -1, double cy = -1, const Duration& = Duration::zero()); + void setScale(double scale, double cx = -1, double cy = -1, const Duration& = Duration::zero()); double getScale() const; - void setZoom(double zoom, Duration = Duration::zero()); + void setZoom(double zoom, const Duration& = Duration::zero()); double getZoom() const; - void setLatLngZoom(LatLng latLng, double zoom, Duration = Duration::zero()); - void fitBounds(LatLngBounds bounds, EdgeInsets padding, Duration duration = Duration::zero()); - void fitBounds(AnnotationSegment segment, EdgeInsets padding, Duration duration = Duration::zero()); + void setLatLngZoom(LatLng latLng, double zoom, const Duration& = Duration::zero()); + CameraOptions cameraForLatLngBounds(LatLngBounds bounds, EdgeInsets padding); + CameraOptions cameraForLatLngs(std::vector latLngs, EdgeInsets padding); void resetZoom(); double getMinZoom() const; double getMaxZoom() const; // Rotation - void rotateBy(double sx, double sy, double ex, double ey, Duration = Duration::zero()); - void setBearing(double degrees, Duration = Duration::zero()); + void rotateBy(double sx, double sy, double ex, double ey, const Duration& = Duration::zero()); + void setBearing(double degrees, const Duration& = Duration::zero()); void setBearing(double degrees, double cx, double cy); double getBearing() const; void resetNorth(); + // Pitch + void setPitch(double pitch, const Duration& = Duration::zero()); + double getPitch() const; + // Size - void resize(uint16_t width, uint16_t height, float ratio = 1); uint16_t getWidth() const; uint16_t getHeight() const; @@ -142,6 +158,10 @@ public: AnnotationIDs getAnnotationsInBounds(const LatLngBounds&, const AnnotationType& = AnnotationType::Any); LatLngBounds getBoundsForAnnotations(const AnnotationIDs&); + // Sprites + void setSprite(const std::string&, std::shared_ptr); + void removeSprite(const std::string&); + // Memory void setSourceTileCacheSize(size_t); void onLowMemory(); diff --git a/include/mbgl/map/update.hpp b/include/mbgl/map/update.hpp index b4c6b5f13..fe9250efc 100644 --- a/include/mbgl/map/update.hpp +++ b/include/mbgl/map/update.hpp @@ -5,16 +5,29 @@ namespace mbgl { -using UpdateType = uint32_t; - -enum class Update : UpdateType { +enum class Update : uint32_t { Nothing = 0, - DefaultTransitionDuration = 1 << 2, + Dimensions = 1 << 1, + DefaultTransition = 1 << 2, Classes = 1 << 3, Zoom = 1 << 4, RenderStill = 1 << 5, + Repaint = 1 << 6, }; +inline Update operator| (const Update& lhs, const Update& rhs) { + return Update(static_cast(lhs) | static_cast(rhs)); } -#endif +inline Update& operator|=(Update& lhs, const Update& rhs) { + lhs = lhs | rhs; + return lhs; +} + +inline bool operator& (const Update& lhs, const Update& rhs) { + return static_cast(lhs) & static_cast(rhs); +} + +} // namespace mbgl + +#endif // MBGL_MAP_UPDATE diff --git a/include/mbgl/map/view.hpp b/include/mbgl/map/view.hpp index 575d0df01..fadd035e4 100644 --- a/include/mbgl/map/view.hpp +++ b/include/mbgl/map/view.hpp @@ -30,6 +30,21 @@ enum MapChange : uint8_t { class View { public: + // Called from the main thread directly after initialization. Must always return the same value, + // i.e. it may not change over time. + virtual float getPixelRatio() const = 0; + + // Called from the main thread when the View signaled a dimension change. Must return the + // logical dimension of this map in pixels. + virtual std::array getSize() const = 0; + + // Called from the main thread for every frame that is being rendered. Must return the absolute + // dimensions of the current framebuffer. Typically, this is the logical width scaled by the + // pixel ratio, but in case the view was moved to display with a different pixel ratio, it can + // also be different from that rule. + virtual std::array getFramebufferSize() const = 0; + + // Called from the main thread when this View is associated with a Map object. virtual void initialize(Map *map_); // Called from the render thread. Makes the GL context active in the current @@ -43,15 +58,15 @@ public: virtual void notify() = 0; - // Called from the render thread. The implementation should resize the framebuffer. - virtual void resize(uint16_t width, uint16_t height, float pixelRatio); - // Called from the render thread. The implementation must trigger a rerender. // (map->renderSync() from the main thread must be called as a result of this) virtual void invalidate() = 0; + // Called from the render thread before the render begins. + virtual void beforeRender() = 0; + // Called from the render thread after the render is complete. - virtual void swap() = 0; + virtual void afterRender() = 0; // Reads the pixel data from the current framebuffer. If your View implementation // doesn't support reading from the framebuffer, return a null pointer. diff --git a/include/mbgl/platform/darwin/settings_nsuserdefaults.hpp b/include/mbgl/platform/darwin/settings_nsuserdefaults.hpp index 6c91fd302..615320ee5 100644 --- a/include/mbgl/platform/darwin/settings_nsuserdefaults.hpp +++ b/include/mbgl/platform/darwin/settings_nsuserdefaults.hpp @@ -17,6 +17,7 @@ public: double latitude = 0; double zoom = 0; double bearing = 0; + double pitch = 0; MGLUserTrackingMode userTrackingMode = MGLUserTrackingModeNone; bool showsUserLocation = false; diff --git a/include/mbgl/platform/default/glfw_view.hpp b/include/mbgl/platform/default/glfw_view.hpp index 6e03953cb..17050d64d 100644 --- a/include/mbgl/platform/default/glfw_view.hpp +++ b/include/mbgl/platform/default/glfw_view.hpp @@ -3,7 +3,7 @@ #include -#ifdef NVIDIA +#ifdef MBGL_USE_GLES2 #define GLFW_INCLUDE_ES2 #endif #include @@ -12,19 +12,25 @@ class GLFWView : public mbgl::View { public: - GLFWView(bool fullscreen = false); + GLFWView(bool fullscreen = false, bool benchmark = false); ~GLFWView(); + float getPixelRatio() const override; + std::array getSize() const override; + std::array getFramebufferSize() const override; + void initialize(mbgl::Map *map) override; void activate() override; void deactivate() override; void notify() override; void invalidate() override; - void swap() override; + void beforeRender() override; + void afterRender() override; static void onKey(GLFWwindow *window, int key, int scancode, int action, int mods); static void onScroll(GLFWwindow *window, double xoffset, double yoffset); - static void onResize(GLFWwindow *window, int width, int height); + static void onWindowResize(GLFWwindow *window, int width, int height); + static void onFramebufferResize(GLFWwindow *window, int width, int height); static void onMouseClick(GLFWwindow *window, int button, int action, int modifiers); static void onMouseMove(GLFWwindow *window, double x, double y); @@ -37,26 +43,39 @@ public: void setWindowTitle(const std::string&); void run(); - void fps(); + void report(float duration); private: mbgl::LatLng makeRandomPoint() const; + static std::shared_ptr + makeSpriteImage(int width, int height, float pixelRatio); void addRandomPointAnnotations(int count); void addRandomShapeAnnotations(int count); + void addRandomCustomPointAnnotations(int count); void clearAnnotations(); void popAnnotation(); mbgl::AnnotationIDs annotationIDs; + std::vector spriteIDs; private: bool fullscreen = false; + const bool benchmark = false; bool tracking = false; bool rotating = false; + // Frame timer + int frames = 0; + float frameTime = 0; + double lastReported = 0; + int width = 1024; int height = 768; + int fbWidth; + int fbHeight; + float pixelRatio; double lastX = 0, lastY = 0; diff --git a/include/mbgl/platform/default/headless_view.hpp b/include/mbgl/platform/default/headless_view.hpp index 61e60ebaf..50edc4842 100644 --- a/include/mbgl/platform/default/headless_view.hpp +++ b/include/mbgl/platform/default/headless_view.hpp @@ -25,18 +25,25 @@ class HeadlessDisplay; class HeadlessView : public View { public: - HeadlessView(uint16_t width = 256, uint16_t height = 256, float pixelRatio = 1); - HeadlessView(std::shared_ptr display, uint16_t width = 256, uint16_t height = 256, float pixelRatio = 1); + HeadlessView(float pixelRatio, uint16_t width = 256, uint16_t height = 256); + HeadlessView(std::shared_ptr display, float pixelRatio, uint16_t width = 256, uint16_t height = 256); ~HeadlessView(); + float getPixelRatio() const override; + std::array getSize() const override; + std::array getFramebufferSize() const override; + void activate() override; void deactivate() override; void notify() override; - void resize(uint16_t width, uint16_t height, float pixelRatio) override; void invalidate() override; - void swap() override; + void beforeRender() override; + void afterRender() override; std::unique_ptr readStillImage() override; + void resizeFramebuffer(); + void resize(uint16_t width, uint16_t height); + private: void createContext(); void loadExtensions(); @@ -45,18 +52,9 @@ private: private: std::shared_ptr display; - - struct Dimensions { - inline Dimensions(uint16_t width = 0, uint16_t height = 0, float pixelRatio = 0); - inline uint16_t pixelWidth() const { return width * pixelRatio; } - inline uint16_t pixelHeight() const { return height * pixelRatio; } - - uint16_t width = 0; - uint16_t height = 0; - float pixelRatio = 0; - }; - - Dimensions dimensions; + const float pixelRatio; + std::array dimensions; + bool needsResize; #if MBGL_USE_CGL CGLContextObj glContext = nullptr; diff --git a/include/mbgl/platform/gl.hpp b/include/mbgl/platform/gl.hpp index 73f0801fa..6bcb85fee 100644 --- a/include/mbgl/platform/gl.hpp +++ b/include/mbgl/platform/gl.hpp @@ -19,7 +19,7 @@ #else #error Unsupported Apple platform #endif -#elif __ANDROID__ +#elif __ANDROID__ || MBGL_USE_GLES2 #define GL_GLEXT_PROTOTYPES #include #include diff --git a/include/mbgl/storage/file_cache.hpp b/include/mbgl/storage/file_cache.hpp index f815d5b8c..a9071b1b6 100644 --- a/include/mbgl/storage/file_cache.hpp +++ b/include/mbgl/storage/file_cache.hpp @@ -10,6 +10,7 @@ namespace mbgl { struct Resource; class Response; +class WorkRequest; class FileCache : private util::noncopyable { public: @@ -18,7 +19,7 @@ public: enum class Hint : uint8_t { Full, Refresh, No }; using Callback = std::function)>; - virtual void get(const Resource &resource, Callback callback) = 0; + virtual std::unique_ptr get(const Resource &resource, Callback callback) = 0; virtual void put(const Resource &resource, std::shared_ptr response, Hint hint) = 0; }; diff --git a/include/mbgl/storage/resource.hpp b/include/mbgl/storage/resource.hpp index 2fcb3b1b7..0c2a27e9d 100644 --- a/include/mbgl/storage/resource.hpp +++ b/include/mbgl/storage/resource.hpp @@ -13,8 +13,8 @@ struct Resource { Source, Tile, Glyphs, - JSON, - Image + SpriteImage, + SpriteJSON }; const Kind kind; diff --git a/include/mbgl/storage/sqlite_cache.hpp b/include/mbgl/storage/sqlite_cache.hpp index fd67d48bb..65328652f 100644 --- a/include/mbgl/storage/sqlite_cache.hpp +++ b/include/mbgl/storage/sqlite_cache.hpp @@ -17,7 +17,7 @@ public: ~SQLiteCache() override; // FileCache API - void get(const Resource &resource, Callback callback) override; + std::unique_ptr get(const Resource &resource, Callback callback) override; void put(const Resource &resource, std::shared_ptr response, Hint hint) override; class Impl; diff --git a/include/mbgl/style/style_properties.hpp b/include/mbgl/style/style_properties.hpp index 9ab528586..23354b1a2 100644 --- a/include/mbgl/style/style_properties.hpp +++ b/include/mbgl/style/style_properties.hpp @@ -45,6 +45,20 @@ struct LineProperties { } }; +struct CircleProperties { + inline CircleProperties() {} + float radius = 5.0f; + Color color = {{ 0, 0, 0, 1 }}; + float opacity = 1.0f; + std::array translate = {{ 0, 0 }}; + TranslateAnchorType translateAnchor = TranslateAnchorType::Map; + float blur = 0; + + inline bool isVisible() const { + return radius > 0 && color[3] > 0 && opacity > 0; + } +}; + struct SymbolProperties { inline SymbolProperties() {} @@ -100,6 +114,7 @@ struct BackgroundProperties { typedef mapbox::util::variant< FillProperties, LineProperties, + CircleProperties, SymbolProperties, RasterProperties, BackgroundProperties, diff --git a/include/mbgl/style/types.hpp b/include/mbgl/style/types.hpp index f6ffcd686..adc13b386 100644 --- a/include/mbgl/style/types.hpp +++ b/include/mbgl/style/types.hpp @@ -27,6 +27,7 @@ enum class StyleLayerType : uint8_t { Unknown, Fill, Line, + Circle, Symbol, Raster, Background @@ -36,6 +37,7 @@ MBGL_DEFINE_ENUM_CLASS(StyleLayerTypeClass, StyleLayerType, { { StyleLayerType::Unknown, "unknown" }, { StyleLayerType::Fill, "fill" }, { StyleLayerType::Line, "line" }, + { StyleLayerType::Circle, "circle" }, { StyleLayerType::Symbol, "symbol" }, { StyleLayerType::Raster, "raster" }, { StyleLayerType::Background, "background" }, @@ -92,6 +94,8 @@ enum class JoinType : uint8_t { Miter, Bevel, Round, + // the following two types are for internal use only + FakeRound, FlipBevel }; @@ -99,6 +103,7 @@ MBGL_DEFINE_ENUM_CLASS(JoinTypeClass, JoinType, { { JoinType::Miter, "miter" }, { JoinType::Bevel, "bevel" }, { JoinType::Round, "round" }, + { JoinType::FakeRound, "fakeround" }, { JoinType::FlipBevel, "flipbevel" }, }); diff --git a/include/mbgl/util/exception.hpp b/include/mbgl/util/exception.hpp index da61aa482..31fa693f8 100644 --- a/include/mbgl/util/exception.hpp +++ b/include/mbgl/util/exception.hpp @@ -11,6 +11,11 @@ struct Exception : std::runtime_error { inline Exception(const std::string &msg) : std::runtime_error(msg) {} }; +struct SpriteImageException : Exception { + inline SpriteImageException(const char *msg) : Exception(msg) {} + inline SpriteImageException(const std::string &msg) : Exception(msg) {} +}; + struct GlyphRangeLoadingException : Exception { inline GlyphRangeLoadingException(const char *msg) : Exception(msg) {} inline GlyphRangeLoadingException(const std::string &msg) : Exception(msg) {} diff --git a/include/mbgl/util/gl_helper.hpp b/include/mbgl/util/gl_helper.hpp index cf007e8a6..288fc2c68 100644 --- a/include/mbgl/util/gl_helper.hpp +++ b/include/mbgl/util/gl_helper.hpp @@ -25,21 +25,21 @@ inline void setBlend(const bool& enabled) { enabled ? MBGL_CHECK_ERROR(glEnable(GL_BLEND)) : MBGL_CHECK_ERROR(glDisable(GL_BLEND)); } -inline std::array getClearColor() { - std::array color; - MBGL_CHECK_ERROR(glGetDoublev(GL_COLOR_CLEAR_VALUE, color.data())); +inline std::array getClearColor() { + std::array color; + MBGL_CHECK_ERROR(glGetFloatv(GL_COLOR_CLEAR_VALUE, color.data())); return color; } -inline void setClearColor(const std::array& color) { +inline void setClearColor(const std::array& color) { MBGL_CHECK_ERROR(glClearColor(color[0], color[1], color[2], color[3])); } inline std::array getBlendFunc() { GLint func[2]; - glGetIntegerv(GL_BLEND_SRC, &func[0]); - glGetIntegerv(GL_BLEND_DST, &func[1]); + glGetIntegerv(GL_BLEND_SRC_ALPHA, &func[0]); + glGetIntegerv(GL_BLEND_DST_ALPHA, &func[1]); return {{ static_cast(func[0]), static_cast(func[1]) }}; } @@ -47,7 +47,7 @@ inline void setBlendFunc(const std::array& func) { MBGL_CHECK_ERROR(glBlendFunc(func[0], func[1])); } - +#ifndef GL_ES_VERSION_2_0 inline std::array getPixelZoom() { std::array zoom; glGetDoublev(GL_ZOOM_X, &zoom[0]); @@ -69,17 +69,20 @@ inline std::array getRasterPos() { inline void setRasterPos(const std::array& pos) { MBGL_CHECK_ERROR(glRasterPos4d(pos[0], pos[1], pos[2], pos[3])); } - +#endif } // end anonymous namespace namespace mbgl { namespace gl { using PreserveBlend = Preserve; -using PreserveClearColor = Preserve, getClearColor, setClearColor>; +using PreserveClearColor = Preserve, getClearColor, setClearColor>; using PreserveBlendFunc = Preserve, getBlendFunc, setBlendFunc>; + +#ifndef GL_ES_VERSION_2_0 using PreservePixelZoom = Preserve, getPixelZoom, setPixelZoom>; using PreserveRasterPos = Preserve, getRasterPos, setRasterPos>; +#endif } } diff --git a/include/mbgl/util/image.hpp b/include/mbgl/util/image.hpp index b2f70e144..f58c2c098 100644 --- a/include/mbgl/util/image.hpp +++ b/include/mbgl/util/image.hpp @@ -7,12 +7,12 @@ namespace mbgl { namespace util { -std::string compress_png(int width, int height, void *rgba); +std::string compress_png(int width, int height, const void *rgba); class Image { public: - Image(const std::string &img); + explicit Image(const std::string &img); inline const char *getData() const { return img.get(); } inline uint32_t getWidth() const { return width; } diff --git a/include/mbgl/util/mat4.hpp b/include/mbgl/util/mat4.hpp index 4279113d2..9c3433240 100644 --- a/include/mbgl/util/mat4.hpp +++ b/include/mbgl/util/mat4.hpp @@ -27,16 +27,19 @@ namespace mbgl { -typedef std::array mat4; +typedef std::array mat4; namespace matrix { void identity(mat4& out); -void ortho(mat4& out, float left, float right, float bottom, float top, float near, float far); +bool invert(mat4& out, mat4& a); +void ortho(mat4& out, double left, double right, double bottom, double top, double near, double far); +void perspective(mat4& out, double fovy, double aspect, double near, double far); void copy(mat4& out, const mat4& a); -void translate(mat4& out, const mat4& a, float x, float y, float z); -void rotate_z(mat4& out, const mat4& a, float rad); -void scale(mat4& out, const mat4& a, float x, float y, float z); +void translate(mat4& out, const mat4& a, double x, double y, double z); +void rotate_x(mat4& out, const mat4& a, double rad); +void rotate_z(mat4& out, const mat4& a, double rad); +void scale(mat4& out, const mat4& a, double x, double y, double z); void multiply(mat4& out, const mat4& a, const mat4& b); } diff --git a/include/mbgl/util/math.hpp b/include/mbgl/util/math.hpp index 5fcf3dce8..37b1dd343 100644 --- a/include/mbgl/util/math.hpp +++ b/include/mbgl/util/math.hpp @@ -112,10 +112,12 @@ T clamp(T value, T min, T max) { return value < min ? min : (value > max ? max : value); } +// Constrains n to the given range (including min, excluding max) via modular +// arithmetic. template T wrap(T value, T min, T max) { T d = max - min; - return value == max ? value : std::fmod((std::fmod((value - min), d) + d), d) + min; + return std::fmod((std::fmod((value - min), d) + d), d) + min; } template diff --git a/src/mbgl/util/optional.hpp b/include/mbgl/util/optional.hpp similarity index 100% rename from src/mbgl/util/optional.hpp rename to include/mbgl/util/optional.hpp diff --git a/include/mbgl/util/projection.hpp b/include/mbgl/util/projection.hpp index f301d9300..956390085 100644 --- a/include/mbgl/util/projection.hpp +++ b/include/mbgl/util/projection.hpp @@ -33,16 +33,16 @@ public: static inline double getMetersPerPixelAtLatitude(const double lat, const double zoom) { const double mapPixelWidthAtZoom = std::pow(2.0, zoom) * util::tileSize; - const double constrainedLatitude = std::fmin(std::fmax(lat, -util::LATITUDE_MAX), util::LATITUDE_MAX); + const double constrainedLatitude = ::fmin(::fmax(lat, -util::LATITUDE_MAX), util::LATITUDE_MAX); return std::cos(constrainedLatitude * util::DEG2RAD) * util::M2PI * util::EARTH_RADIUS_M / mapPixelWidthAtZoom; } static inline const ProjectedMeters projectedMetersForLatLng(const LatLng latLng) { - const double constrainedLatitude = std::fmin(std::fmax(latLng.latitude, -util::LATITUDE_MAX), util::LATITUDE_MAX); + const double constrainedLatitude = ::fmin(::fmax(latLng.latitude, -util::LATITUDE_MAX), util::LATITUDE_MAX); const double m = 1 - 1e-15; - const double f = std::fmin(std::fmax(std::sin(util::DEG2RAD * constrainedLatitude), -m), m); + const double f = ::fmin(::fmax(std::sin(util::DEG2RAD * constrainedLatitude), -m), m); const double easting = util::EARTH_RADIUS_M * latLng.longitude * util::DEG2RAD; const double northing = 0.5 * util::EARTH_RADIUS_M * std::log((1 + f) / (1 - f)); @@ -54,7 +54,7 @@ public: double latitude = (2 * std::atan(std::exp(projectedMeters.northing / util::EARTH_RADIUS_M)) - (M_PI / 2)) * util::RAD2DEG; double longitude = projectedMeters.easting * util::RAD2DEG / util::EARTH_RADIUS_M; - latitude = std::fmin(std::fmax(latitude, -util::LATITUDE_MAX), util::LATITUDE_MAX); + latitude = ::fmin(::fmax(latitude, -util::LATITUDE_MAX), util::LATITUDE_MAX); return LatLng(latitude, longitude); } diff --git a/src/mbgl/util/unitbezier.hpp b/include/mbgl/util/unitbezier.hpp similarity index 100% rename from src/mbgl/util/unitbezier.hpp rename to include/mbgl/util/unitbezier.hpp diff --git a/include/mbgl/util/uv.hpp b/include/mbgl/util/uv.hpp index 19c6c5b4d..9198b0fd1 100644 --- a/include/mbgl/util/uv.hpp +++ b/include/mbgl/util/uv.hpp @@ -12,8 +12,6 @@ typedef struct uv_fs_s uv_fs_t; namespace uv { -std::string cwd(); - class rwlock; class loop; class async; diff --git a/ios/FAQ.md b/ios/FAQ.md deleted file mode 100644 index 19ae74fdb..000000000 --- a/ios/FAQ.md +++ /dev/null @@ -1,49 +0,0 @@ -# Mapbox GL for iOS FAQ - -Mapbox GL is a completely new renderer technology which will eventually replace and/or merge Mapbox's existing iOS toolsets, the [Mapbox iOS SDK](http://www.mapbox.com/mapbox-ios-sdk/) and [MBXMapKit](https://www.mapbox.com/mbxmapkit/). This FAQ shares our current plans for that migration. The plans are subject to change as Mapbox GL matures, but we wanted to both clarify our thinking as well as set expectations for where things are headed. - -### When will Mapbox GL be released? - -The library is [open source](https://github.com/mapbox/mapbox-gl-native) right now, but an official, production-recommended release will come later in 2015. - -### What iOS versions will be supported? - -Mapbox GL currently supports iOS 7 and later. This will continue to be the case. MBXMapKit also supports iOS 7 and greater, but this does mean that moving from the iOS SDK will leave behind iOS 5 and 6. - -### Will the API be similar to the Mapbox iOS SDK/MBXMapKit/MapKit? - -Yes. We are shooting for bringing the Mapbox GL API in line with Apple's MapKit for the easiest transition ability. - -MBXMapKit is already an add-on to MapKit, so Apple's `MKMapView` API is used directly. - -The Mapbox iOS SDK is "workalike", since it descends from an [upstream open source project](https://github.com/Alpstein/route-me) that predates Apple's own MapKit. It uses similar concepts like annotations (with the difference that the map view delegate provides `CALayer` instances instead of `UIView`, the intention being that Mapbox GL will support `UIView`), similar API for managing the map view center, bounds, and zoom levels, and an `RMUserLocation` API that is very much like `MKUserLocation`. But the Mapbox iOS SDK also features unique APIs like extensible tile sources, offline caching, UTFGrid interactivity, and point annotation clustering. - -### Will the iOS SDK's extra APIs make it over to Mapbox GL? - -The intention is yes. This includes: - -#### Tile sources - -The SDK uses the `RMTileSource` protocol to allow for extensible remote and local raster tile sources, as well as custom on-the-fly raster tile generation. We are planning to build a transitional API in Mapbox GL that acts as a wrapper to `RMTileSource` so that existing sources can be used to plug into the main Mapbox GL source API. - -#### Offline caching - -Both the Mapbox iOS SDK and MBXMapKit feature robust support for offline caching of raster tiles and the SDK features support for [MBTiles](http://mbtiles.org) tile sources. - -We're likely going to take a similar approach to both performance caching and offline maps as is done with the `0.3.0` release of MBXMapKit. That is, `NSURLCache` will be used for performance caching and specific, separate offline map database management will be used for fetching areas of map tiles. This is a cleaner design than exists in the SDK, which combines performance and offline caches and has a non-intuitive configuration API. - -We will likely also port over MBTiles support to Mapbox GL. - -#### UTFGrid interactivity - -[UTFGrid](https://github.com/mapbox/utfgrid-spec) is a technology for enabling interactive features in raster tiles. Support for the technology is provided in the Mapbox iOS SDK with categories in `RMInteractiveSource` in combination with either `RMMapboxSource` or `RMMBTilesSource`. - -Mapbox GL obsoletes this sort of pixel-based interactivity since the source data used for rendering can be queried on-device, so we are unlikely to port UTFGrid over to GL. - -#### Annotation clustering - -We're planning on adding support for point clustering to Mapbox GL and it will likely have a similar API to the SDK's. Clustering will happen on an individual style layer level. The performance for clustering, as well as annotations in general, will hopefully be better than in the SDK, since it will be done with direct OpenGL rendering instead of Core Animation layers. - -### What will the migration path look like? - -Ideally, the migration will be pretty lightweight because of the APIs supported above. There may be slight syntax changes, but they likely won't be more than would be expected from something like Mapbox iOS SDK version `1.x` to a hypothetical `2.x`. diff --git a/ios/MapboxGL.podspec b/ios/Mapbox-iOS-SDK.podspec similarity index 67% rename from ios/MapboxGL.podspec rename to ios/Mapbox-iOS-SDK.podspec index da1960b72..099a84c8a 100644 --- a/ios/MapboxGL.podspec +++ b/ios/Mapbox-iOS-SDK.podspec @@ -1,19 +1,19 @@ Pod::Spec.new do |m| - m.name = 'MapboxGL' - m.version = '0.4.2-symbols' + m.name = 'Mapbox-iOS-SDK' + m.version = '2.0.0-symbols' m.summary = 'Open source vector map solution for iOS with full styling capabilities.' m.description = 'Open source OpenGL-based vector map solution for iOS with full styling capabilities and Cocoa bindings.' - m.homepage = 'https://www.mapbox.com/mapbox-gl-ios/' + m.homepage = 'https://www.mapbox.com/ios-sdk/' m.license = 'BSD' m.author = { 'Mapbox' => 'mobile@mapbox.com' } m.screenshot = 'https://raw.githubusercontent.com/mapbox/mapbox-gl-native/master/ios/screenshot.png' m.social_media_url = 'https://twitter.com/mapbox' - m.documentation_url = 'https://www.mapbox.com/mapbox-gl-ios' + m.documentation_url = 'https://www.mapbox.com/ios-sdk/' m.source = { - :http => "http://mapbox.s3.amazonaws.com/mapbox-gl-native/ios/builds/mapbox-gl-ios-#{m.version.to_s}.zip", + :http => "http://mapbox.s3.amazonaws.com/mapbox-gl-native/ios/builds/mapbox-ios-sdk-#{m.version.to_s}.zip", :flatten => true } @@ -24,11 +24,15 @@ Pod::Spec.new do |m| m.preserve_paths = '**' m.source_files = 'Headers/*.h', 'MGLDummy.m' - m.resource_bundle = { 'MapboxGL' => 'MapboxGL.bundle/*' } - m.vendored_library = 'libMapboxGL.a' + m.resource_bundle = { 'Mapbox' => 'Mapbox.bundle/*' } + m.vendored_library = 'libMapbox.a' + m.module_name = 'Mapbox' m.frameworks = 'CoreLocation', 'CoreTelephony', 'GLKit', 'ImageIO', 'MobileCoreServices', 'QuartzCore', 'SystemConfiguration' m.libraries = 'c++', 'sqlite3', 'z' - m.xcconfig = { 'OTHER_CPLUSPLUSFLAGS' => '-std=gnu++11 -stdlib=libc++' } + m.xcconfig = { + 'OTHER_CPLUSPLUSFLAGS' => '-std=gnu++11 -stdlib=libc++', + 'OTHER_LDFLAGS' => '-ObjC', + } end diff --git a/ios/app/MBXAppDelegate.m b/ios/app/MBXAppDelegate.m index b09235a27..bca039739 100644 --- a/ios/app/MBXAppDelegate.m +++ b/ios/app/MBXAppDelegate.m @@ -1,6 +1,6 @@ #import "MBXAppDelegate.h" #import "MBXViewController.h" -#import +#import @implementation MBXAppDelegate diff --git a/ios/app/MBXViewController.mm b/ios/app/MBXViewController.mm index 00245828e..d94e41b5f 100644 --- a/ios/app/MBXViewController.mm +++ b/ios/app/MBXViewController.mm @@ -1,25 +1,20 @@ #import "MBXViewController.h" -#import - -#import +#import #import static UIColor *const kTintColor = [UIColor colorWithRed:0.120 green:0.550 blue:0.670 alpha:1.000]; static NSArray *const kStyleNames = @[ - @"Mapbox Streets", + @"Streets", @"Emerald", @"Light", @"Dark", - @"Bright", - @"Basic", - @"Outdoors", @"Satellite", ]; -static NSString *const kStyleVersion = @"7"; +static NSUInteger const kStyleVersion = 8; @interface MBXViewController () @@ -29,10 +24,20 @@ static NSString *const kStyleVersion = @"7"; @implementation MBXViewController -mbgl::Settings_NSUserDefaults *settings = nullptr; - #pragma mark - Setup ++ (void)initialize +{ + if (self == [MBXViewController class]) + { + [[NSUserDefaults standardUserDefaults] registerDefaults:@{ + @"userTrackingMode": @(MGLUserTrackingModeNone), + @"showsUserLocation": @NO, + @"debug": @NO, + }]; + } +} + - (id)init { self = [super init]; @@ -41,6 +46,7 @@ mbgl::Settings_NSUserDefaults *settings = nullptr; { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveState:) name:UIApplicationDidEnterBackgroundNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(restoreState:) name:UIApplicationWillEnterForegroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveState:) name:UIApplicationWillTerminateNotification object:nil]; } return self; @@ -52,7 +58,6 @@ mbgl::Settings_NSUserDefaults *settings = nullptr; self.mapView = [[MGLMapView alloc] initWithFrame:self.view.bounds]; self.mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - self.mapView.showsUserLocation = YES; self.mapView.delegate = self; [self.view addSubview:self.mapView]; @@ -77,34 +82,42 @@ mbgl::Settings_NSUserDefaults *settings = nullptr; target:self action:@selector(locateUser)]; - settings = new mbgl::Settings_NSUserDefaults(); [self restoreState:nil]; } - (void)saveState:(__unused NSNotification *)notification { - if (self.mapView && settings) + if (self.mapView) { - settings->longitude = self.mapView.centerCoordinate.longitude; - settings->latitude = self.mapView.centerCoordinate.latitude; - settings->zoom = self.mapView.zoomLevel; - settings->bearing = self.mapView.direction; - settings->debug = self.mapView.isDebugActive; - settings->userTrackingMode = self.mapView.userTrackingMode; - settings->showsUserLocation = self.mapView.showsUserLocation; - settings->save(); + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSData *archivedCamera = [NSKeyedArchiver archivedDataWithRootObject:self.mapView.camera]; + [defaults setObject:archivedCamera forKey:@"camera"]; + [defaults setInteger:self.mapView.userTrackingMode forKey:@"userTrackingMode"]; + [defaults setBool:self.mapView.showsUserLocation forKey:@"showsUserLocation"]; + [defaults setBool:self.mapView.debugActive forKey:@"debug"]; + [defaults synchronize]; } } - (void)restoreState:(__unused NSNotification *)notification { - if (self.mapView && settings) { - settings->load(); - [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(settings->latitude, settings->longitude) zoomLevel:settings->zoom animated:NO]; - self.mapView.direction = settings->bearing; - self.mapView.userTrackingMode = settings->userTrackingMode; - self.mapView.showsUserLocation = settings->showsUserLocation; - [self.mapView setDebugActive:settings->debug]; + if (self.mapView) { + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSData *archivedCamera = [defaults objectForKey:@"camera"]; + MGLMapCamera *camera = archivedCamera ? [NSKeyedUnarchiver unarchiveObjectWithData:archivedCamera] : nil; + if (camera) + { + self.mapView.camera = camera; + } + NSInteger uncheckedTrackingMode = [defaults integerForKey:@"userTrackingMode"]; + if (uncheckedTrackingMode >= 0 && + (NSUInteger)uncheckedTrackingMode >= MGLUserTrackingModeNone && + (NSUInteger)uncheckedTrackingMode <= MGLUserTrackingModeFollowWithCourse) + { + self.mapView.userTrackingMode = (MGLUserTrackingMode)uncheckedTrackingMode; + } + self.mapView.showsUserLocation = [defaults boolForKey:@"showsUserLocation"]; + self.mapView.debugActive = [defaults boolForKey:@"debug"]; } } @@ -301,27 +314,31 @@ mbgl::Settings_NSUserDefaults *settings = nullptr; } self.mapView.styleURL = [NSURL URLWithString: - [NSString stringWithFormat:@"asset://styles/%@-v%@.json", - [[styleName lowercaseString] stringByReplacingOccurrencesOfString:@" " withString:@"-"], - kStyleVersion]]; + [NSString stringWithFormat:@"asset://styles/%@-v%lu.json", + [styleName lowercaseString], + (unsigned long)kStyleVersion]]; [titleButton setTitle:styleName forState:UIControlStateNormal]; } - (void)locateUser { - if (self.mapView.userTrackingMode == MGLUserTrackingModeNone) - { - self.mapView.userTrackingMode = MGLUserTrackingModeFollow; - } - else if (self.mapView.userTrackingMode == MGLUserTrackingModeFollow) - { - self.mapView.userTrackingMode = MGLUserTrackingModeFollowWithHeading; - } - else - { - self.mapView.userTrackingMode = MGLUserTrackingModeNone; + MGLUserTrackingMode nextMode; + switch (self.mapView.userTrackingMode) { + case MGLUserTrackingModeNone: + nextMode = MGLUserTrackingModeFollow; + break; + case MGLUserTrackingModeFollow: + nextMode = MGLUserTrackingModeFollowWithHeading; + break; + case MGLUserTrackingModeFollowWithHeading: + nextMode = MGLUserTrackingModeFollowWithCourse; + break; + case MGLUserTrackingModeFollowWithCourse: + nextMode = MGLUserTrackingModeNone; + break; } + self.mapView.userTrackingMode = nextMode; } #pragma mark - Destruction @@ -330,16 +347,50 @@ mbgl::Settings_NSUserDefaults *settings = nullptr; { [[NSNotificationCenter defaultCenter] removeObserver:self]; - if (settings) - { - [self saveState:nil]; - delete settings; - settings = nullptr; - } + [self saveState:nil]; } #pragma mark - MGLMapViewDelegate +- (MGLAnnotationImage *)mapView:(MGLMapView * __nonnull)mapView imageForAnnotation:(id __nonnull)annotation +{ + NSString *title = [(MGLPointAnnotation *)annotation title]; + NSString *lastTwoCharacters = [title substringFromIndex:title.length - 2]; + + MGLAnnotationImage *image = [mapView dequeueReusableAnnotationImageWithIdentifier:lastTwoCharacters]; + + if ( ! image) + { + CGRect rect = CGRectMake(0, 0, 20, 15); + + UIGraphicsBeginImageContextWithOptions(rect.size, NO, [[UIScreen mainScreen] scale]); + + CGContextRef ctx = UIGraphicsGetCurrentContext(); + + CGContextSetFillColorWithColor(ctx, [[[UIColor redColor] colorWithAlphaComponent:0.75] CGColor]); + CGContextFillRect(ctx, rect); + + CGContextSetStrokeColorWithColor(ctx, [[UIColor blackColor] CGColor]); + CGContextStrokeRectWithWidth(ctx, rect, 2); + + NSAttributedString *drawString = [[NSAttributedString alloc] initWithString:lastTwoCharacters attributes:@{ + NSFontAttributeName: [UIFont fontWithName:@"Arial-BoldMT" size:12], + NSForegroundColorAttributeName: [UIColor whiteColor] }]; + CGSize stringSize = drawString.size; + CGRect stringRect = CGRectMake((rect.size.width - stringSize.width) / 2, + (rect.size.height - stringSize.height) / 2, + stringSize.width, + stringSize.height); + [drawString drawInRect:stringRect]; + + image = [MGLAnnotationImage annotationImageWithImage:UIGraphicsGetImageFromCurrentImageContext() reuseIdentifier:lastTwoCharacters]; + + UIGraphicsEndImageContext(); + } + + return image; +} + - (BOOL)mapView:(__unused MGLMapView *)mapView annotationCanShowCallout:(__unused id )annotation { return YES; @@ -363,6 +414,7 @@ mbgl::Settings_NSUserDefaults *settings = nullptr; - (void)mapView:(__unused MGLMapView *)mapView didChangeUserTrackingMode:(MGLUserTrackingMode)mode animated:(__unused BOOL)animated { UIImage *newButtonImage; + NSString *newButtonTitle; switch (mode) { case MGLUserTrackingModeNone: @@ -376,8 +428,13 @@ mbgl::Settings_NSUserDefaults *settings = nullptr; case MGLUserTrackingModeFollowWithHeading: newButtonImage = [UIImage imageNamed:@"TrackingHeadingMask.png"]; break; + case MGLUserTrackingModeFollowWithCourse: + newButtonImage = nil; + newButtonTitle = @"Course"; + break; } + self.navigationItem.rightBarButtonItem.title = newButtonTitle; [UIView animateWithDuration:0.25 animations:^{ self.navigationItem.rightBarButtonItem.image = newButtonImage; }]; diff --git a/ios/app/README.md b/ios/app/README.md deleted file mode 100644 index 325670f1e..000000000 --- a/ios/app/README.md +++ /dev/null @@ -1,3 +0,0 @@ -This is the app for use in the development of Mapbox GL for iOS. - -If you want to run this app, head up to [`mapbox-gl-native`](https://github.com/mapbox/mapbox-gl-native) and follow the iOS installation instructions there. \ No newline at end of file diff --git a/ios/app/app-info.plist b/ios/app/app-info.plist index 85afe3e7a..665324693 100644 --- a/ios/app/app-info.plist +++ b/ios/app/app-info.plist @@ -24,10 +24,23 @@ 0.0.3 LSRequiresIPhoneOS + MGLMetricsTestServerURL + https://cloudfront-staging.tilestream.net + NSAppTransportSecurity + + NSExceptionDomains + + cloudfront-staging.tilestream.net + + NSExceptionRequiresForwardSecrecy + + + + NSHumanReadableCopyright (c) 2014 Mapbox NSLocationWhenInUseUsageDescription - The map will be centered on the user's location. + The map will display the user's location. UILaunchStoryboardName Default UIRequiredDeviceCapabilities @@ -47,7 +60,5 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - MGLMetricsTestServerURL - https://cloudfront-staging.tilestream.net diff --git a/ios/app/mapboxgl-app.gypi b/ios/app/mapboxgl-app.gypi index 0a39c7f7d..8407ea7da 100644 --- a/ios/app/mapboxgl-app.gypi +++ b/ios/app/mapboxgl-app.gypi @@ -13,7 +13,8 @@ './points.geojson', './polyline.geojson', './threestates.geojson', - './Settings.bundle/' + './Settings.bundle/', + './app-info.plist' ], 'dependencies': [ @@ -31,7 +32,6 @@ './MBXAppDelegate.m', './MBXViewController.h', './MBXViewController.mm', - '../../platform/darwin/settings_nsuserdefaults.mm', ], 'xcode_settings': { diff --git a/ios/benchmark/MBXBenchViewController.mm b/ios/benchmark/MBXBenchViewController.mm index 86b4ec9cb..0305ab853 100644 --- a/ios/benchmark/MBXBenchViewController.mm +++ b/ios/benchmark/MBXBenchViewController.mm @@ -32,7 +32,7 @@ { [super viewDidLoad]; - NSURL* url = [[NSURL alloc] initWithString:@"asset://styles/mapbox-streets-v7.json"]; + NSURL* url = [[NSURL alloc] initWithString:@"asset://styles/streets-v8.json"]; self.mapView = [[MGLMapView alloc] initWithFrame:self.view.bounds styleURL:url]; self.mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; self.mapView.delegate = self; diff --git a/ios/benchmark/fo/MBXBenchViewController.mm b/ios/benchmark/fo/MBXBenchViewController.mm index f1079d9e0..b4eb4ec3f 100644 --- a/ios/benchmark/fo/MBXBenchViewController.mm +++ b/ios/benchmark/fo/MBXBenchViewController.mm @@ -21,7 +21,7 @@ { [super viewDidLoad]; - NSURL* url = [[NSURL alloc] initWithString:@"asset://styles/mapbox-streets-v7.json"]; + NSURL* url = [[NSURL alloc] initWithString:@"asset://styles/streets-v8.json"]; self.mapView = [[MGLMapView alloc] initWithFrame:self.view.bounds accessToken:nil styleURL:url]; self.mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; self.mapView.delegate = self; diff --git a/ios/docs/install_docs.sh b/ios/docs/install_docs.sh index 1a5f97a8d..b6270e777 100755 --- a/ios/docs/install_docs.sh +++ b/ios/docs/install_docs.sh @@ -14,7 +14,7 @@ echo rm -rf /tmp/mbgl mkdir -p /tmp/mbgl/ README=/tmp/mbgl/GL-README.md -cat ../README.md > ${README} +cat ./pod-README.md > ${README} echo >> ${README} echo -n "#" >> ${README} cat ../../CHANGELOG.md >> ${README} @@ -26,7 +26,7 @@ perl \ /tmp/mbgl/ios/*.h appledoc \ --output ${OUTPUT} \ - --project-name "Mapbox GL for iOS ${VERSION}" \ + --project-name "Mapbox iOS SDK ${VERSION}" \ --project-company Mapbox \ --create-docset \ --company-id com.mapbox \ diff --git a/ios/README.md b/ios/docs/pod-README.md similarity index 93% rename from ios/README.md rename to ios/docs/pod-README.md index 545cd5108..7ffbf3b2c 100644 --- a/ios/README.md +++ b/ios/docs/pod-README.md @@ -1,4 +1,4 @@ -# Mapbox GL for iOS +# Mapbox iOS SDK An open source OpenGL-based vector map solution for iOS with full styling capabilities and Cocoa bindings. diff --git a/linux/main.cpp b/linux/main.cpp index 9145b180f..6bd2f7c43 100644 --- a/linux/main.cpp +++ b/linux/main.cpp @@ -12,6 +12,8 @@ #include #include #include +#include +#include namespace { @@ -29,18 +31,27 @@ void quit_handler(int) { } int main(int argc, char *argv[]) { - int fullscreen_flag = 0; + bool fullscreen = false; + bool benchmark = false; std::string style; + double latitude = 0, longitude = 0; + double bearing = 0, zoom = 1; + bool skipConfig = false; const struct option long_options[] = { - {"fullscreen", no_argument, &fullscreen_flag, 'f'}, + {"fullscreen", no_argument, 0, 'f'}, + {"benchmark", no_argument, 0, 'b'}, {"style", required_argument, 0, 's'}, + {"lon", required_argument, 0, 'x'}, + {"lat", required_argument, 0, 'y'}, + {"zoom", required_argument, 0, 'z'}, + {"bearing", required_argument, 0, 'r'}, {0, 0, 0, 0} }; while (true) { int option_index = 0; - int opt = getopt_long(argc, argv, "fs:", long_options, &option_index); + int opt = getopt_long(argc, argv, "fbs:", long_options, &option_index); if (opt == -1) break; switch (opt) { @@ -48,10 +59,30 @@ int main(int argc, char *argv[]) { if (long_options[option_index].flag != 0) break; case 'f': - // handle fullscreen_flag + fullscreen = true; + break; + case 'b': + benchmark = true; break; case 's': style = std::string("asset://") + std::string(optarg); + break; + case 'x': + longitude = atof(optarg); + skipConfig = true; + break; + case 'y': + latitude = atof(optarg); + skipConfig = true; + break; + case 'z': + zoom = atof(optarg); + skipConfig = true; + break; + case 'r': + bearing = atof(optarg); + skipConfig = true; + break; default: break; } @@ -65,7 +96,11 @@ int main(int argc, char *argv[]) { sigIntHandler.sa_flags = 0; sigaction(SIGINT, &sigIntHandler, NULL); - view = std::make_unique(); + if (benchmark) { + mbgl::Log::Info(mbgl::Event::General, "BENCHMARK MODE: Some optimizations are disabled."); + } + + view = std::make_unique(fullscreen, benchmark); mbgl::SQLiteCache cache("/tmp/mbgl-cache.db"); mbgl::DefaultFileSource fileSource(&cache); @@ -82,9 +117,16 @@ int main(int argc, char *argv[]) { // Load settings mbgl::Settings_JSON settings; - map.setLatLngZoom(mbgl::LatLng(settings.latitude, settings.longitude), settings.zoom); - map.setBearing(settings.bearing); - map.setDebug(settings.debug); + + if (skipConfig) { + map.setLatLngZoom(mbgl::LatLng(latitude, longitude), zoom); + map.setBearing(bearing); + mbgl::Log::Info(mbgl::Event::General, "Location: %f/%f (z%.2f, %.2f deg)", latitude, longitude, zoom, bearing); + } else { + map.setLatLngZoom(mbgl::LatLng(settings.latitude, settings.longitude), settings.zoom); + map.setBearing(settings.bearing); + map.setDebug(settings.debug); + } view->setChangeStyleCallback([&map] () { static uint8_t currentStyleIndex; @@ -118,7 +160,12 @@ int main(int argc, char *argv[]) { settings.zoom = map.getZoom(); settings.bearing = map.getBearing(); settings.debug = map.getDebug(); - settings.save(); + if (!skipConfig) { + settings.save(); + } + mbgl::Log::Info(mbgl::Event::General, + "Exit location: --lat=\"%f\" --lon=\"%f\" --zoom=\"%f\" --bearing \"%f\"", + settings.latitude, settings.longitude, settings.zoom, settings.bearing); return 0; } diff --git a/linux/mapboxgl-app.gypi b/linux/mapboxgl-app.gypi index 35ca322e2..8457c2ce7 100644 --- a/linux/mapboxgl-app.gypi +++ b/linux/mapboxgl-app.gypi @@ -29,13 +29,15 @@ 'variables' : { 'cflags_cc': [ - '<@(glfw3_cflags)', + '<@(opengl_cflags)', + '<@(boost_cflags)', + '<@(glfw_cflags)', ], 'ldflags': [ - '<@(glfw3_ldflags)', + '<@(glfw_ldflags)', ], 'libraries': [ - '<@(glfw3_static_libs)', + '<@(glfw_static_libs)', ], }, diff --git a/macosx/main.mm b/macosx/main.mm index 4a786f577..24251e6d4 100644 --- a/macosx/main.mm +++ b/macosx/main.mm @@ -12,6 +12,19 @@ #import +#pragma GCC diagnostic push +#ifndef __clang__ +#pragma GCC diagnostic ignored "-Wunused-local-typedefs" +#pragma GCC diagnostic ignored "-Wshadow" +#endif +#include +#pragma GCC diagnostic pop + +#include + +namespace po = boost::program_options; + + @interface URLHandler : NSObject @property (nonatomic) mbgl::Map *map; @@ -101,8 +114,33 @@ const std::string &defaultCacheDatabase() { return path; } -int main() { - GLFWView view; +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); @@ -146,10 +184,15 @@ int main() { mbgl::Log::Info(mbgl::Event::Setup, std::string("Changed style to: ") + newStyle.first); }); + // Load style - const auto& newStyle = mbgl::util::defaultStyles.front(); - map.setStyleURL(newStyle.first); - view.setWindowTitle(newStyle.second); + if (style.empty()) { + const auto& newStyle = mbgl::util::defaultStyles.front(); + style = newStyle.first; + view.setWindowTitle(newStyle.second); + } + + map.setStyleURL(style); view.run(); diff --git a/macosx/mapboxgl-app.gypi b/macosx/mapboxgl-app.gypi index c56bca659..e1d9476e1 100644 --- a/macosx/mapboxgl-app.gypi +++ b/macosx/mapboxgl-app.gypi @@ -34,14 +34,16 @@ 'variables' : { 'cflags_cc': [ - '<@(glfw3_cflags)', + '<@(boost_cflags)', + '<@(glfw_cflags)', ], 'ldflags': [ '-framework SystemConfiguration', # For NSUserDefaults and Reachability - '<@(glfw3_ldflags)', + '<@(glfw_ldflags)', ], 'libraries': [ - '<@(glfw3_static_libs)', + '<@(glfw_static_libs)', + '<@(boost_libprogram_options_static_libs)' ], }, diff --git a/mbgl.gyp b/mbgl.gyp index c5ede2823..959a9729d 100644 --- a/mbgl.gyp +++ b/mbgl.gyp @@ -18,6 +18,7 @@ ['platform_lib == "android" and host == "android"', { 'includes': [ './gyp/platform-android.gypi' ] } ], ['http_lib == "curl"', { 'includes': [ './gyp/http-curl.gypi' ] } ], ['http_lib == "nsurl" and (host == "osx" or host == "ios")', { 'includes': [ './gyp/http-nsurl.gypi' ] } ], + ['http_lib == "android" and host == "android"', { 'includes': [ './gyp/http-android.gypi' ] } ], ['asset_lib == "fs"', { 'includes': [ './gyp/asset-fs.gypi' ] } ], ['asset_lib == "zip"', { 'includes': [ './gyp/asset-zip.gypi' ] } ], ['cache_lib == "sqlite"', { 'includes': [ './gyp/cache-sqlite.gypi' ] } ], diff --git a/package.json b/package.json new file mode 100644 index 000000000..33ffb3288 --- /dev/null +++ b/package.json @@ -0,0 +1,46 @@ +{ + "name": "mapbox-gl-native", + "version": "2.0.0-pre.4", + "description": "Renders map tiles with Mapbox GL", + "keywords": [ + "mapbox", + "gl" + ], + "main": "lib/mapbox-gl-native.node", + "repository": { + "type": "git", + "url": "git://github.com/mapbox/mapbox-gl-native.git" + }, + "licenses": [ + { + "type": "BSD", + "url": "https://github.com/mapbox/mapbox-gl-native/blob/master/LICENSE.md" + } + ], + "dependencies": { + "nan": "^2.0.9", + "node-pre-gyp": "^0.6.9" + }, + "bundledDependencies": [ + "node-pre-gyp" + ], + "devDependencies": { + "aws-sdk": "^2.1.47", + "mapbox-gl-test-suite": "git+https://github.com/mapbox/mapbox-gl-test-suite.git#6cfd7eb20de77018a7f10770f56df9ce1f8a76ac", + "request": "^2.61.0", + "tape": "^4.2.0" + }, + "scripts": { + "install": "node-pre-gyp install --fallback-to-build=false || make node", + "test": "tape platform/node/test/js/**/*.test.js", + "test-suite": "node platform/node/test/render.test.js" + }, + "gypfile": true, + "binary": { + "module_name": "mapbox-gl-native", + "module_path": "./lib/", + "host": "https://mapbox-node-binary.s3.amazonaws.com", + "remote_path": "./{name}/v{version}", + "package_name": "{node_abi}-{platform}-{arch}.tar.gz" + } +} diff --git a/platform/android/http_request_android.cpp b/platform/android/http_request_android.cpp new file mode 100644 index 000000000..249cb731e --- /dev/null +++ b/platform/android/http_request_android.cpp @@ -0,0 +1,370 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace mbgl { + +void JNICALL nativeOnFailure(JNIEnv *env, jobject obj, jlong nativePtr, jint type, jstring message); +void JNICALL nativeOnResponse(JNIEnv *env, jobject obj, jlong nativePtr, jint code, jstring message, jstring etag, jstring modified, jstring cacheControl, jstring expires, jbyteArray body); + +class HTTPAndroidRequest; + +class HTTPAndroidContext : public HTTPContextBase { +public: + explicit HTTPAndroidContext(uv_loop_t *loop); + ~HTTPAndroidContext(); + + HTTPRequestBase* createRequest(const Resource&, + RequestBase::Callback, + uv_loop_t*, + std::shared_ptr) final; + + uv_loop_t *loop = nullptr; + + JavaVM *vm = nullptr; + jobject obj = nullptr; +}; + +class HTTPAndroidRequest : public HTTPRequestBase { +public: + HTTPAndroidRequest(HTTPAndroidContext*, + const Resource&, + Callback, + uv_loop_t*, + std::shared_ptr); + ~HTTPAndroidRequest(); + + void cancel() final; + void retry() final; + + void onFailure(int type, std::string message); + void onResponse(int code, std::string message, std::string etag, std::string modified, std::string cacheControl, std::string expires, std::string body); + +private: + void retry(uint64_t timeout) final; + void finish(); + void start(); + + HTTPAndroidContext *context = nullptr; + + bool cancelled = false; + + std::unique_ptr response; + const std::shared_ptr existingResponse; + ResponseStatus status = ResponseStatus::PermanentError; + + jobject obj = nullptr; + + uv::async async; + uv::timer timer; + enum : bool { PreemptImmediately, ExponentialBackoff } strategy = PreemptImmediately; + int attempts = 0; + + static const int maxAttempts = 4; + + static const int connectionError = 0; + static const int temporaryError = 1; + static const int permanentError = 1; +}; + +// ------------------------------------------------------------------------------------------------- + +HTTPAndroidContext::HTTPAndroidContext(uv_loop_t *loop_) + : HTTPContextBase(loop_), + loop(loop_), + vm(mbgl::android::theJVM) { + + JNIEnv *env = nullptr; + bool detach = mbgl::android::attach_jni_thread(vm, &env, "HTTPAndroidContext::HTTPAndroidContext()"); + + const std::vector methods = { + {"nativeOnFailure", "(JILjava/lang/String;)V", reinterpret_cast(&nativeOnFailure)}, + {"nativeOnResponse", + "(JILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[B)V", + reinterpret_cast(&nativeOnResponse)} + }; + + if (env->RegisterNatives(mbgl::android::httpRequestClass, methods.data(), methods.size()) < 0) { + env->ExceptionDescribe(); + } + + obj = env->CallStaticObjectMethod(mbgl::android::httpContextClass, mbgl::android::httpContextGetInstanceId); + if (env->ExceptionCheck() || (obj == nullptr)) { + env->ExceptionDescribe(); + } + + obj = env->NewGlobalRef(obj); + if (obj == nullptr) { + env->ExceptionDescribe(); + } + + mbgl::android::detach_jni_thread(vm, &env, detach); +} + +HTTPAndroidContext::~HTTPAndroidContext() { + JNIEnv *env = nullptr; + bool detach = mbgl::android::attach_jni_thread(vm, &env, "HTTPAndroidContext::~HTTPAndroidContext()"); + + env->DeleteGlobalRef(obj); + obj = nullptr; + + mbgl::android::detach_jni_thread(vm, &env, detach); + + vm = nullptr; +} + +HTTPRequestBase* HTTPAndroidContext::createRequest(const Resource& resource, + RequestBase::Callback callback, + uv_loop_t* loop_, + std::shared_ptr response) { + return new HTTPAndroidRequest(this, resource, callback, loop_, response); +} + +HTTPAndroidRequest::HTTPAndroidRequest(HTTPAndroidContext* context_, const Resource& resource_, Callback callback_, uv_loop_t* loop, std::shared_ptr response_) + : HTTPRequestBase(resource_, callback_), + context(context_), + existingResponse(response_), + async(loop, [this] { finish(); }), + timer(loop) { + + std::string etagStr; + std::string modifiedStr; + if (existingResponse) { + if (!existingResponse->etag.empty()) { + etagStr = existingResponse->etag; + } else if (existingResponse->modified) { + modifiedStr = util::rfc1123(existingResponse->modified); + } + } + + JNIEnv *env = nullptr; + bool detach = mbgl::android::attach_jni_thread(context->vm, &env, "HTTPAndroidContext::HTTPAndroidRequest()"); + + jstring resourceUrl = mbgl::android::std_string_to_jstring(env, resource.url); + jstring userAgent = mbgl::android::std_string_to_jstring(env, "MapboxGL/1.0"); + jstring etag = mbgl::android::std_string_to_jstring(env, etagStr); + jstring modified = mbgl::android::std_string_to_jstring(env, modifiedStr); + obj = env->CallObjectMethod(context->obj, mbgl::android::httpContextCreateRequestId, reinterpret_cast(this), resourceUrl, userAgent, etag, modified); + if (env->ExceptionCheck() || (obj == nullptr)) { + env->ExceptionDescribe(); + } + + obj = env->NewGlobalRef(obj); + if (obj == nullptr) { + env->ExceptionDescribe(); + } + + mbgl::android::detach_jni_thread(context->vm, &env, detach); + + context->addRequest(this); + start(); +} + +HTTPAndroidRequest::~HTTPAndroidRequest() { + context->removeRequest(this); + + JNIEnv *env = nullptr; + bool detach = mbgl::android::attach_jni_thread(context->vm, &env, "HTTPAndroidContext::~HTTPAndroidRequest()"); + + env->DeleteGlobalRef(obj); + obj = nullptr; + + mbgl::android::detach_jni_thread(context->vm, &env, detach); + + timer.stop(); +} + +void HTTPAndroidRequest::cancel() { + cancelled = true; + + JNIEnv *env = nullptr; + bool detach = mbgl::android::attach_jni_thread(context->vm, &env, "HTTPAndroidContext::cancel()"); + + env->CallVoidMethod(obj, mbgl::android::httpRequestCancelId); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + } + + mbgl::android::detach_jni_thread(context->vm, &env, detach); +} + +void HTTPAndroidRequest::start() { + attempts++; + + JNIEnv *env = nullptr; + bool detach = mbgl::android::attach_jni_thread(context->vm, &env, "HTTPAndroidContext::start()"); + + env->CallVoidMethod(obj, mbgl::android::httpRequestStartId); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + } + + mbgl::android::detach_jni_thread(context->vm, &env, detach); +} + +void HTTPAndroidRequest::retry(uint64_t timeout) { + response.reset(); + + timer.stop(); + timer.start(timeout, 0, [this] { start(); }); +} + +void HTTPAndroidRequest::retry() { + if (strategy == PreemptImmediately) { + timer.stop(); + timer.start(0, 0, [this] { start(); }); + } +} + +void HTTPAndroidRequest::finish() { + if (!cancelled) { + if (status == ResponseStatus::TemporaryError && attempts < maxAttempts) { + strategy = ExponentialBackoff; + return retry((1 << (attempts - 1)) * 1000); + } else if (status == ResponseStatus::ConnectionError && attempts < maxAttempts) { + strategy = PreemptImmediately; + return retry(30000); + } + + if (status == ResponseStatus::NotModified) { + notify(std::move(response), FileCache::Hint::Refresh); + } else { + notify(std::move(response), FileCache::Hint::Full); + } + } + + delete this; +} + +int64_t parseCacheControl(const char *value) { + if (value) { + unsigned long long seconds = 0; + // TODO: cache-control may contain other information as well: + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9 + if (std::sscanf(value, "max-age=%llu", &seconds) == 1) { + return std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()).count() + + seconds; + } + } + + return 0; +} + +void HTTPAndroidRequest::onResponse(int code, std::string message, std::string etag, std::string modified, std::string cacheControl, std::string expires, std::string body) { + if (!response) { + response = std::make_unique(); + } + + response->message = message; + response->modified = parse_date(modified.c_str()); + response->etag = etag; + response->expires = parseCacheControl(cacheControl.c_str()); + if (!expires.empty()) { + response->expires = parse_date(expires.c_str()); + } + response->data = body; + + if (code == 304) { + if (existingResponse) { + response->status = existingResponse->status; + response->message = existingResponse->message; + response->modified = existingResponse->modified; + response->etag = existingResponse->etag; + response->data = existingResponse->data; + status = ResponseStatus::NotModified; + } else { + response->status = Response::Successful; + status = ResponseStatus::Successful; + } + } else if (code == 200) { + response->status = Response::Successful; + status = ResponseStatus::Successful; + } else if (code >= 500 && code < 600) { + response->status = Response::Error; + response->message = "HTTP status code " + util::toString(code); + status = ResponseStatus::TemporaryError; + } else { + response->status = Response::Error; + response->message = "HTTP status code " + util::toString(code); + status = ResponseStatus::PermanentError; + } + + async.send(); +} + +void HTTPAndroidRequest::onFailure(int type, std::string message) { + if (!response) { + response = std::make_unique(); + } + + response->status = Response::Error; + response->message = message; + + switch (type) { + case connectionError: + status = ResponseStatus::ConnectionError; + + case temporaryError: + status = ResponseStatus::TemporaryError; + + default: + status = ResponseStatus::PermanentError; + } + + async.send(); +} + +std::unique_ptr HTTPContextBase::createContext(uv_loop_t* loop) { + return std::make_unique(loop); +} + +#pragma clang diagnostic ignored "-Wunused-parameter" + +void JNICALL nativeOnFailure(JNIEnv *env, jobject obj, jlong nativePtr, jint type, jstring message) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeOnFailure"); + assert(nativePtr != 0); + HTTPAndroidRequest *request = reinterpret_cast(nativePtr); + std::string messageStr = mbgl::android::std_string_from_jstring(env, message); + return request->onFailure(type, messageStr); +} + +void JNICALL nativeOnResponse(JNIEnv *env, jobject obj, jlong nativePtr, jint code, jstring message, jstring etag, jstring modified, jstring cacheControl, jstring expires, jbyteArray body) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeOnResponse"); + assert(nativePtr != 0); + HTTPAndroidRequest *request = reinterpret_cast(nativePtr); + std::string messageStr = mbgl::android::std_string_from_jstring(env, message); + std::string etagStr; + if (etag != nullptr) { + etagStr = mbgl::android::std_string_from_jstring(env, etag); + } + std::string modifiedStr; + if (modified != nullptr) { + modifiedStr = mbgl::android::std_string_from_jstring(env, modified); + } + std::string cacheControlStr; + if (cacheControl != nullptr) { + cacheControlStr = mbgl::android::std_string_from_jstring(env, cacheControl); + } + std::string expiresStr; + if (expires != nullptr) { + expiresStr = mbgl::android::std_string_from_jstring(env, expires); + } + jbyte* bodyData = env->GetByteArrayElements(body, nullptr); + std::string bodyStr(reinterpret_cast(bodyData), env->GetArrayLength(body)); + env->ReleaseByteArrayElements(body, bodyData, JNI_ABORT); + return request->onResponse(code, messageStr, etagStr, modifiedStr, cacheControlStr, expiresStr, bodyStr); +} + +} diff --git a/platform/darwin/asset_root.mm b/platform/darwin/asset_root.mm index 4dc6b58dc..f47f5d36d 100644 --- a/platform/darwin/asset_root.mm +++ b/platform/darwin/asset_root.mm @@ -14,7 +14,7 @@ namespace platform { // Returns the path to the root folder of the application. const std::string &assetRoot() { static const std::string root = []() -> std::string { - NSString *path = [[NSBundle bundleForClass:[MGLApplicationRootBundleCanary class]] pathForResource:@"MapboxGL" ofType:@"bundle"]; + NSString *path = [[NSBundle bundleForClass:[MGLApplicationRootBundleCanary class]] pathForResource:@"Mapbox" ofType:@"bundle"]; if (!path) { path = [[[NSBundle mainBundle] resourceURL] path]; } diff --git a/platform/darwin/http_request_nsurl.mm b/platform/darwin/http_request_nsurl.mm index 207c33b23..33f5f043e 100644 --- a/platform/darwin/http_request_nsurl.mm +++ b/platform/darwin/http_request_nsurl.mm @@ -1,4 +1,5 @@ -#include +#include +#include #include #include @@ -12,55 +13,25 @@ namespace mbgl { -enum class ResponseStatus : uint8_t { - // This error probably won't be resolved by retrying anytime soon. We are giving up. - PermanentError, - - // This error might be resolved by waiting some time (e.g. server issues). - // We are going to do an exponential back-off and will try again in a few seconds. - TemporaryError, - - // This error was caused by a temporary error and it is likely that it will be resolved - // immediately. We are going to try again right away. This is like the TemporaryError, except - // that we will not perform exponential back-off. - SingularError, - - // This error might be resolved once the network reachability status changes. - // We are going to watch the network status for changes and will retry as soon as the - // operating system notifies us of a network status change. - ConnectionError, - - // The request was canceled mid-way. - Canceled, - - // The request returned data successfully. We retrieved and decoded the data successfully. - Successful, - - // The request confirmed that the data wasn't changed. We already have the data. - NotModified, -}; - -// ------------------------------------------------------------------------------------------------- - class HTTPNSURLContext; -class HTTPRequest : public RequestBase { +class HTTPNSURLRequest : public HTTPRequestBase { public: - HTTPRequest(HTTPNSURLContext*, + HTTPNSURLRequest(HTTPNSURLContext*, const Resource&, Callback, uv_loop_t*, std::shared_ptr); - ~HTTPRequest(); + ~HTTPNSURLRequest(); - void cancel() override; - void retry() override; + void cancel() final; + void retry() final; private: void start(); void handleResult(NSData *data, NSURLResponse *res, NSError *error); void handleResponse(); - void retry(uint64_t timeout); + void retry(uint64_t timeout) final; HTTPNSURLContext *context = nullptr; bool cancelled = false; @@ -78,22 +49,22 @@ private: // ------------------------------------------------------------------------------------------------- -class HTTPNSURLContext : public HTTPContext { +class HTTPNSURLContext : public HTTPContextBase { public: HTTPNSURLContext(uv_loop_t *loop); ~HTTPNSURLContext(); - RequestBase* createRequest(const Resource&, + HTTPRequestBase* createRequest(const Resource&, RequestBase::Callback, uv_loop_t*, - std::shared_ptr) override; + std::shared_ptr) final; NSURLSession *session = nil; NSString *userAgent = nil; NSInteger accountType = 0; }; -HTTPNSURLContext::HTTPNSURLContext(uv_loop_t *loop_) : HTTPContext(loop_) { +HTTPNSURLContext::HTTPNSURLContext(uv_loop_t *loop_) : HTTPContextBase(loop_) { @autoreleasepool { NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; @@ -120,17 +91,17 @@ HTTPNSURLContext::~HTTPNSURLContext() { userAgent = nullptr; } -RequestBase* HTTPNSURLContext::createRequest(const Resource& resource, +HTTPRequestBase* HTTPNSURLContext::createRequest(const Resource& resource, RequestBase::Callback callback, uv_loop_t* loop, std::shared_ptr response) { - return new HTTPRequest(this, resource, callback, loop, response); + return new HTTPNSURLRequest(this, resource, callback, loop, response); } // ------------------------------------------------------------------------------------------------- -HTTPRequest::HTTPRequest(HTTPNSURLContext* context_, const Resource& resource_, Callback callback_, uv_loop_t *loop, std::shared_ptr existingResponse_) - : RequestBase(resource_, callback_), +HTTPNSURLRequest::HTTPNSURLRequest(HTTPNSURLContext* context_, const Resource& resource_, Callback callback_, uv_loop_t *loop, std::shared_ptr existingResponse_) + : HTTPRequestBase(resource_, callback_), context(context_), existingResponse(existingResponse_), async(loop, [this] { handleResponse(); }), @@ -139,7 +110,7 @@ HTTPRequest::HTTPRequest(HTTPNSURLContext* context_, const Resource& resource_, start(); } -HTTPRequest::~HTTPRequest() { +HTTPNSURLRequest::~HTTPNSURLRequest() { assert(!task); // Stop the backoff timer to avoid re-triggering this request. @@ -148,7 +119,7 @@ HTTPRequest::~HTTPRequest() { context->removeRequest(this); } -void HTTPRequest::start() { +void HTTPNSURLRequest::start() { assert(!task); attempts++; @@ -183,7 +154,7 @@ void HTTPRequest::start() { } } -void HTTPRequest::handleResponse() { +void HTTPNSURLRequest::handleResponse() { if (task) { [task release]; task = nullptr; @@ -211,7 +182,7 @@ void HTTPRequest::handleResponse() { delete this; } -void HTTPRequest::cancel() { +void HTTPNSURLRequest::cancel() { context->removeRequest(this); cancelled = true; @@ -242,7 +213,7 @@ int64_t parseCacheControl(const char *value) { return 0; } -void HTTPRequest::handleResult(NSData *data, NSURLResponse *res, NSError *error) { +void HTTPNSURLRequest::handleResult(NSData *data, NSURLResponse *res, NSError *error) { if (error) { if ([error code] == NSURLErrorCancelled) { status = ResponseStatus::Canceled; @@ -345,14 +316,14 @@ void HTTPRequest::handleResult(NSData *data, NSURLResponse *res, NSError *error) async.send(); } -void HTTPRequest::retry(uint64_t timeout) { +void HTTPNSURLRequest::retry(uint64_t timeout) { response.reset(); timer.stop(); timer.start(timeout, 0, [this] { start(); }); } -void HTTPRequest::retry() { +void HTTPNSURLRequest::retry() { // All batons get notified when the network status changed, but some of them // might not actually wait for the network to become available again. if (strategy == PreemptImmediately) { @@ -362,7 +333,7 @@ void HTTPRequest::retry() { } } -std::unique_ptr HTTPContext::createContext(uv_loop_t* loop) { +std::unique_ptr HTTPContextBase::createContext(uv_loop_t* loop) { return std::make_unique(loop); } diff --git a/platform/darwin/image.mm b/platform/darwin/image.mm index f6a8a6783..31373e9d1 100644 --- a/platform/darwin/image.mm +++ b/platform/darwin/image.mm @@ -11,7 +11,7 @@ namespace mbgl { namespace util { -std::string compress_png(int width, int height, void *rgba) { +std::string compress_png(int width, int height, const void *rgba) { CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, rgba, width * height * 4, NULL); if (!provider) { return ""; diff --git a/platform/darwin/settings_nsuserdefaults.mm b/platform/darwin/settings_nsuserdefaults.mm index 168cba172..548ee9b22 100644 --- a/platform/darwin/settings_nsuserdefaults.mm +++ b/platform/darwin/settings_nsuserdefaults.mm @@ -11,6 +11,7 @@ Settings_NSUserDefaults::Settings_NSUserDefaults() @"latitude" : @(latitude), @"zoom" : @(zoom), @"bearing" : @(bearing), + @"pitch" : @(pitch), @"userTrackingMode" : @(userTrackingMode), @"showsUserLocation" : @(showsUserLocation), @"debug" : @(debug), @@ -26,11 +27,12 @@ void Settings_NSUserDefaults::load() latitude = [settings[@"latitude"] doubleValue]; zoom = [settings[@"zoom"] doubleValue]; bearing = [settings[@"bearing"] doubleValue]; + pitch = [settings[@"pitch"] doubleValue]; debug = [settings[@"debug"] boolValue]; - unsigned uncheckedTrackingMode = [settings[@"trackingMode"] unsignedIntValue]; + unsigned uncheckedTrackingMode = [settings[@"userTrackingMode"] unsignedIntValue]; if (uncheckedTrackingMode > MGLUserTrackingModeNone && - uncheckedTrackingMode <= MGLUserTrackingModeFollowWithHeading) + uncheckedTrackingMode <= MGLUserTrackingModeFollowWithCourse) { userTrackingMode = (MGLUserTrackingMode)uncheckedTrackingMode; } @@ -44,6 +46,7 @@ void Settings_NSUserDefaults::save() @"latitude" : @(latitude), @"zoom" : @(zoom), @"bearing" : @(bearing), + @"pitch" : @(pitch), @"userTrackingMode" : @(userTrackingMode), @"showsUserLocation" : @(showsUserLocation), @"debug" : @(debug), @@ -54,4 +57,4 @@ void Settings_NSUserDefaults::save() void Settings_NSUserDefaults::clear() { [NSUserDefaults resetStandardUserDefaults]; -} \ No newline at end of file +} diff --git a/platform/default/asset_request_fs.cpp b/platform/default/asset_request_fs.cpp index 04f85fbc3..733e240fb 100644 --- a/platform/default/asset_request_fs.cpp +++ b/platform/default/asset_request_fs.cpp @@ -1,9 +1,10 @@ -#include +#include #include #include #include #include #include +#include #include @@ -19,7 +20,7 @@ public: AssetRequest(const Resource&, Callback, uv_loop_t*, const std::string& assetRoot); ~AssetRequest(); - void cancel() override; + void cancel() final; static void fileOpened(uv_fs_t *req); static void fileStated(uv_fs_t *req); @@ -36,11 +37,11 @@ public: std::unique_ptr response; }; -class AssetFSContext : public AssetContext { +class AssetFSContext : public AssetContextBase { RequestBase* createRequest(const Resource& resource, RequestBase::Callback callback, uv_loop_t* loop, - const std::string& assetRoot) override { + const std::string& assetRoot) final { return new AssetRequest(resource, callback, loop, assetRoot); } }; @@ -131,7 +132,7 @@ void AssetRequest::fileStated(uv_fs_t *req) { #else self->response->modified = stat->st_mtime; #endif - self->response->etag = std::to_string(stat->st_ino); + self->response->etag = util::toString(stat->st_ino); const auto size = (unsigned int)(stat->st_size); self->response->data.resize(size); self->buffer = uv_buf_init(const_cast(self->response->data.data()), size); @@ -206,7 +207,7 @@ void AssetRequest::cancel() { uv_cancel((uv_req_t *)&req); } -std::unique_ptr AssetContext::createContext(uv_loop_t*) { +std::unique_ptr AssetContextBase::createContext(uv_loop_t*) { return std::make_unique(); } diff --git a/platform/default/asset_request_zip.cpp b/platform/default/asset_request_zip.cpp index 7fd93c55d..13c7df4cc 100644 --- a/platform/default/asset_request_zip.cpp +++ b/platform/default/asset_request_zip.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -15,15 +15,15 @@ namespace mbgl { -class AssetZipContext : public AssetContext { +class AssetZipContext : public AssetContextBase { public: - AssetZipContext(uv_loop_t *loop); + explicit AssetZipContext(uv_loop_t *loop); ~AssetZipContext(); RequestBase* createRequest(const Resource& resource, RequestBase::Callback callback, uv_loop_t* loop, - const std::string& assetRoot) override; + const std::string& assetRoot) final; uv_zip_t *getHandle(const std::string &path); void returnHandle(const std::string &path, uv_zip_t *zip); @@ -71,7 +71,7 @@ public: AssetRequest(AssetZipContext&, const Resource&, Callback, const std::string& assetRoot); ~AssetRequest(); - void cancel() override; + void cancel() final; private: AssetZipContext &context; @@ -259,7 +259,7 @@ void AssetRequest::cancel() { cancelled = true; } -std::unique_ptr AssetContext::createContext(uv_loop_t* loop) { +std::unique_ptr AssetContextBase::createContext(uv_loop_t* loop) { return std::make_unique(loop); } diff --git a/platform/default/default_styles.cpp b/platform/default/default_styles.cpp index a631008a4..9ee541497 100644 --- a/platform/default/default_styles.cpp +++ b/platform/default/default_styles.cpp @@ -4,10 +4,11 @@ namespace mbgl { namespace util { const std::vector> defaultStyles = { - { "asset://styles/mapbox-streets-v7.json", "Mapbox Streets" }, - { "asset://styles/emerald-v7.json", "Emerald" }, - { "asset://styles/light-v7.json", "Light" }, - { "asset://styles/dark-v7.json", "Dark" } + { "asset://styles/streets-v8.json", "Mapbox Streets" }, + { "asset://styles/emerald-v8.json", "Emerald" }, + { "asset://styles/light-v8.json", "Light" }, + { "asset://styles/dark-v8.json", "Dark" }, + { "asset://styles/satellite-v8.json", "Satellite" } }; } // end namespace util diff --git a/platform/default/glfw_view.cpp b/platform/default/glfw_view.cpp index fe37b398d..cf7bb0121 100644 --- a/platform/default/glfw_view.cpp +++ b/platform/default/glfw_view.cpp @@ -1,9 +1,11 @@ #include #include +#include #include #include #include #include +#include #include #include @@ -13,7 +15,8 @@ void glfwError(int error, const char *description) { assert(false); } -GLFWView::GLFWView(bool fullscreen_) : fullscreen(fullscreen_) { +GLFWView::GLFWView(bool fullscreen_, bool benchmark_) + : fullscreen(fullscreen_), benchmark(benchmark_) { glfwSetErrorCallback(glfwError); std::srand(std::time(0)); @@ -26,6 +29,9 @@ GLFWView::GLFWView(bool fullscreen_) : fullscreen(fullscreen_) { GLFWmonitor *monitor = nullptr; if (fullscreen) { monitor = glfwGetPrimaryMonitor(); + auto videoMode = glfwGetVideoMode(monitor); + width = videoMode->width; + height = videoMode->height; } #ifdef DEBUG @@ -54,17 +60,27 @@ GLFWView::GLFWView(bool fullscreen_) : fullscreen(fullscreen_) { glfwSetWindowUserPointer(window, this); glfwMakeContextCurrent(window); - glfwSwapInterval(1); + if (benchmark) { + // Disables vsync on platforms that support it. + glfwSwapInterval(0); + } else { + glfwSwapInterval(1); + } + glfwSetCursorPosCallback(window, onMouseMove); glfwSetMouseButtonCallback(window, onMouseClick); - glfwSetWindowSizeCallback(window, onResize); - glfwSetFramebufferSizeCallback(window, onResize); + glfwSetWindowSizeCallback(window, onWindowResize); + glfwSetFramebufferSizeCallback(window, onFramebufferResize); glfwSetScrollCallback(window, onScroll); glfwSetKeyCallback(window, onKey); mbgl::gl::InitializeExtensions(glfwGetProcAddress); + glfwGetWindowSize(window, &width, &height); + glfwGetFramebufferSize(window, &fbWidth, &fbHeight); + pixelRatio = static_cast(fbWidth) / width; + glfwMakeContextCurrent(nullptr); } @@ -75,9 +91,6 @@ GLFWView::~GLFWView() { void GLFWView::initialize(mbgl::Map *map_) { View::initialize(map_); - - glfwGetWindowSize(window, &width, &height); - onResize(window, width, height); } void GLFWView::onKey(GLFWwindow *window, int key, int /*scancode*/, int action, int mods) { @@ -119,6 +132,9 @@ void GLFWView::onKey(GLFWwindow *window, int key, int /*scancode*/, int action, case GLFW_KEY_Q: view->clearAnnotations(); break; + case GLFW_KEY_P: { + view->addRandomCustomPointAnnotations(1); + } break; } } @@ -149,6 +165,49 @@ mbgl::LatLng GLFWView::makeRandomPoint() const { return { lat, lon }; } +std::shared_ptr +GLFWView::makeSpriteImage(int width, int height, float pixelRatio) { + const int r = 255 * (double(std::rand()) / RAND_MAX); + const int g = 255 * (double(std::rand()) / RAND_MAX); + const int b = 255 * (double(std::rand()) / RAND_MAX); + + const int w = std::ceil(pixelRatio * width); + const int h = std::ceil(pixelRatio * height); + + std::string pixels(w * h * 4, '\x00'); + auto data = reinterpret_cast(const_cast(pixels.data())); + const int dist = (w / 2) * (w / 2); + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + const int dx = x - w / 2; + const int dy = y - h / 2; + const int diff = dist - (dx * dx + dy * dy); + if (diff > 0) { + const int a = std::min(0xFF, diff) * 0xFF / dist; + // Premultiply the rgb values with alpha + data[w * y + x] = + (a << 24) | ((a * r / 0xFF) << 16) | ((a * g / 0xFF) << 8) | (a * b / 0xFF); + } + } + } + + return std::make_shared(width, height, pixelRatio, std::move(pixels)); +} + +void GLFWView::addRandomCustomPointAnnotations(int count) { + std::vector points; + + for (int i = 0; i < count; i++) { + static int spriteID = 1; + const auto name = std::string{ "marker-" } + mbgl::util::toString(spriteID++); + map->setSprite(name, makeSpriteImage(22, 22, 1)); + spriteIDs.push_back(name); + points.emplace_back(makeRandomPoint(), name); + } + + auto newIDs = map->addPointAnnotations(points); + annotationIDs.insert(annotationIDs.end(), newIDs.begin(), newIDs.end()); +} void GLFWView::addRandomPointAnnotations(int count) { std::vector points; @@ -226,15 +285,20 @@ void GLFWView::onScroll(GLFWwindow *window, double /*xOffset*/, double yOffset) view->map->scaleBy(scale, view->lastX, view->lastY); } -void GLFWView::onResize(GLFWwindow *window, int width, int height ) { +void GLFWView::onWindowResize(GLFWwindow *window, int width, int height) { GLFWView *view = reinterpret_cast(glfwGetWindowUserPointer(window)); view->width = width; view->height = height; - int fbWidth, fbHeight; - glfwGetFramebufferSize(window, &fbWidth, &fbHeight); + view->map->update(mbgl::Update::Dimensions); +} - view->map->resize(width, height, static_cast(fbWidth) / static_cast(width)); +void GLFWView::onFramebufferResize(GLFWwindow *window, int width, int height) { + GLFWView *view = reinterpret_cast(glfwGetWindowUserPointer(window)); + view->fbWidth = width; + view->fbHeight = height; + + view->map->update(mbgl::Update::Repaint); } void GLFWView::onMouseClick(GLFWwindow *window, int button, int action, int modifiers) { @@ -282,11 +346,28 @@ void GLFWView::run() { glfwWaitEvents(); const bool dirty = !clean.test_and_set(); if (dirty) { + const double started = glfwGetTime(); map->renderSync(); + report(1000 * (glfwGetTime() - started)); + if (benchmark) { + map->update(mbgl::Update::Repaint); + } } } } +float GLFWView::getPixelRatio() const { + return pixelRatio; +} + +std::array GLFWView::getSize() const { + return {{ static_cast(width), static_cast(height) }}; +} + +std::array GLFWView::getFramebufferSize() const { + return {{ static_cast(fbWidth), static_cast(fbHeight) }}; +} + void GLFWView::activate() { glfwMakeContextCurrent(window); } @@ -304,22 +385,26 @@ void GLFWView::invalidate() { glfwPostEmptyEvent(); } -void GLFWView::swap() { - glfwSwapBuffers(window); - fps(); +void GLFWView::beforeRender() { + // no-op } -void GLFWView::fps() { - static int frames = 0; - static double timeElapsed = 0; +void GLFWView::afterRender() { + glfwSwapBuffers(window); +} +void GLFWView::report(float duration) { frames++; - double currentTime = glfwGetTime(); + frameTime += duration; - if (currentTime - timeElapsed >= 1) { - mbgl::Log::Info(mbgl::Event::OpenGL, "FPS: %4.2f", frames / (currentTime - timeElapsed)); - timeElapsed = currentTime; + const double currentTime = glfwGetTime(); + if (currentTime - lastReported >= 1) { + frameTime /= frames; + mbgl::Log::Info(mbgl::Event::OpenGL, "Frame time: %6.2fms (%6.2f fps)", frameTime, + 1000 / frameTime); frames = 0; + frameTime = 0; + lastReported = currentTime; } } @@ -339,8 +424,6 @@ void GLFWView::setWindowTitle(const std::string& title) { namespace mbgl { namespace platform { -double elapsed() { return glfwGetTime(); } - #ifndef GL_ES_VERSION_2_0 void showDebugImage(std::string name, const char *data, size_t width, size_t height) { glfwInit(); diff --git a/platform/default/headless_view.cpp b/platform/default/headless_view.cpp index f4411bb2e..a582ed579 100644 --- a/platform/default/headless_view.cpp +++ b/platform/default/headless_view.cpp @@ -20,21 +20,17 @@ namespace mbgl { -HeadlessView::HeadlessView(uint16_t width, uint16_t height, float pixelRatio) - : display(std::make_shared()) { - activate(); - resize(width, height, pixelRatio); - deactivate(); +HeadlessView::HeadlessView(float pixelRatio_, uint16_t width, uint16_t height) + : display(std::make_shared()), pixelRatio(pixelRatio_) { + resize(width, height); } HeadlessView::HeadlessView(std::shared_ptr display_, + float pixelRatio_, uint16_t width, - uint16_t height, - float pixelRatio) - : display(display_) { - activate(); - resize(width, height, pixelRatio); - deactivate(); + uint16_t height) + : display(display_), pixelRatio(pixelRatio_) { + resize(width, height); } void HeadlessView::loadExtensions() { @@ -118,17 +114,15 @@ bool HeadlessView::isActive() { return std::this_thread::get_id() == thread; } -HeadlessView::Dimensions::Dimensions(uint16_t width_, uint16_t height_, float pixelRatio_) - : width(width_), height(height_), pixelRatio(pixelRatio_) { -} +void HeadlessView::resizeFramebuffer() { + assert(isActive()); -void HeadlessView::resize(const uint16_t width, const uint16_t height, const float pixelRatio) { - dimensions = { width, height, pixelRatio }; + if (!needsResize) return; clearBuffers(); - const unsigned int w = dimensions.width * dimensions.pixelRatio; - const unsigned int h = dimensions.height * dimensions.pixelRatio; + const unsigned int w = dimensions[0] * pixelRatio; + const unsigned int h = dimensions[1] * pixelRatio; // Create depth/stencil buffer MBGL_CHECK_ERROR(glGenRenderbuffersEXT(1, &fboDepthStencil)); @@ -150,26 +144,33 @@ void HeadlessView::resize(const uint16_t width, const uint16_t height, const flo GLenum status = MBGL_CHECK_ERROR(glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT)); if (status != GL_FRAMEBUFFER_COMPLETE_EXT) { - std::stringstream error("Couldn't create framebuffer: "); + std::string error("Couldn't create framebuffer: "); switch (status) { - case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: (error << "incomplete attachment.\n"); break; - case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: error << "incomplete missing attachment.\n"; break; - case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT: error << "incomplete dimensions.\n"; break; - case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT: error << "incomplete formats.\n"; break; - case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: error << "incomplete draw buffer.\n"; break; - case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: error << "incomplete read buffer.\n"; break; - case GL_FRAMEBUFFER_UNSUPPORTED: error << "unsupported.\n"; break; - default: error << "other\n"; break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: (error += "incomplete attachment"); break; + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: error += "incomplete missing attachment"; break; + case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT: error += "incomplete dimensions"; break; + case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT: error += "incomplete formats"; break; + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: error += "incomplete draw buffer"; break; + case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: error += "incomplete read buffer"; break; + case GL_FRAMEBUFFER_UNSUPPORTED: error += "unsupported"; break; + default: error += "other"; break; } - throw std::runtime_error(error.str()); + throw std::runtime_error(error); } + + needsResize = false; +} + +void HeadlessView::resize(const uint16_t width, const uint16_t height) { + dimensions = {{ width, height }}; + needsResize = true; } std::unique_ptr HeadlessView::readStillImage() { assert(isActive()); - const unsigned int w = dimensions.pixelWidth(); - const unsigned int h = dimensions.pixelHeight(); + const unsigned int w = dimensions[0] * pixelRatio; + const unsigned int h = dimensions[1] * pixelRatio; auto image = std::make_unique(); image->width = w; @@ -234,6 +235,19 @@ void HeadlessView::notify() { // no-op } +float HeadlessView::getPixelRatio() const { + return pixelRatio; +} + +std::array HeadlessView::getSize() const { + return dimensions; +} + +std::array HeadlessView::getFramebufferSize() const { + return {{ static_cast(dimensions[0] * pixelRatio), + static_cast(dimensions[1] * pixelRatio) }}; +} + void HeadlessView::activate() { if (thread != std::thread::id()) { throw std::runtime_error("OpenGL context was already current"); @@ -284,7 +298,11 @@ void HeadlessView::invalidate() { // no-op } -void HeadlessView::swap() { +void HeadlessView::beforeRender() { + resizeFramebuffer(); +} + +void HeadlessView::afterRender() { // no-op } diff --git a/platform/default/http_request_curl.cpp b/platform/default/http_request_curl.cpp index 8599ea05e..e416034b4 100644 --- a/platform/default/http_request_curl.cpp +++ b/platform/default/http_request_curl.cpp @@ -1,4 +1,5 @@ -#include +#include +#include #include #include #include @@ -6,19 +7,15 @@ #include #include +#include #include -#ifdef __ANDROID__ -#include -#include -#include -#endif - #include #include #include #include +#include void handleError(CURLMcode code) { if (code != CURLM_OK) { @@ -34,39 +31,19 @@ void handleError(CURLcode code) { namespace mbgl { -enum class ResponseStatus : int8_t { - // This error probably won't be resolved by retrying anytime soon. We are giving up. - PermanentError, +class HTTPCURLRequest; - // This error might be resolved by waiting some time (e.g. server issues). - // We are going to do an exponential back-off and will try again in a few seconds. - TemporaryError, - - // This error might be resolved once the network reachability status changes. - // We are going to watch the network status for changes and will retry as soon as the - // operating system notifies us of a network status change. - ConnectionError, - - // The request returned data successfully. We retrieved and decoded the data successfully. - Successful, - - // The request confirmed that the data wasn't changed. We already have the data. - NotModified, -}; - -class HTTPRequest; - -class HTTPCURLContext : public HTTPContext { +class HTTPCURLContext : public HTTPContextBase { MBGL_STORE_THREAD(tid) public: - HTTPCURLContext(uv_loop_t *loop); + explicit HTTPCURLContext(uv_loop_t *loop); ~HTTPCURLContext(); - RequestBase* createRequest(const Resource&, + HTTPRequestBase* createRequest(const Resource&, RequestBase::Callback, uv_loop_t*, - std::shared_ptr) override; + std::shared_ptr) final; static int handleSocket(CURL *handle, curl_socket_t s, int action, void *userp, void *socketp); static void perform(uv_poll_t *req, int status, int events); @@ -98,19 +75,19 @@ public: std::queue handles; }; -class HTTPRequest : public RequestBase { +class HTTPCURLRequest : public HTTPRequestBase { MBGL_STORE_THREAD(tid) public: - HTTPRequest(HTTPCURLContext*, + HTTPCURLRequest(HTTPCURLContext*, const Resource&, Callback, uv_loop_t*, std::shared_ptr); - ~HTTPRequest(); + ~HTTPCURLRequest(); - void cancel() override; - void retry() override; + void cancel() final; + void retry() final; void handleResult(CURLcode code); @@ -118,7 +95,7 @@ private: static size_t headerCallback(char *const buffer, const size_t size, const size_t nmemb, void *userp); static size_t writeCallback(void *const contents, const size_t size, const size_t nmemb, void *userp); - void retry(uint64_t timeout); + void retry(uint64_t timeout) final; #if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 static void restart(uv_timer_t *timer, int); #else @@ -128,7 +105,6 @@ private: void start(); HTTPCURLContext *context = nullptr; - bool cancelled = false; // Will store the current response. std::unique_ptr response; @@ -187,7 +163,7 @@ private: // ------------------------------------------------------------------------------------------------- HTTPCURLContext::HTTPCURLContext(uv_loop_t *loop_) - : HTTPContext(loop_), + : HTTPContextBase(loop_), loop(loop_) { if (curl_global_init(CURL_GLOBAL_ALL)) { throw std::runtime_error("Could not init cURL"); @@ -222,11 +198,11 @@ HTTPCURLContext::~HTTPCURLContext() { uv::close(timeout); } -RequestBase* HTTPCURLContext::createRequest(const Resource& resource, +HTTPRequestBase* HTTPCURLContext::createRequest(const Resource& resource, RequestBase::Callback callback, uv_loop_t* loop_, std::shared_ptr response) { - return new HTTPRequest(this, resource, callback, loop_, response); + return new HTTPCURLRequest(this, resource, callback, loop_, response); } CURL *HTTPCURLContext::getHandle() { @@ -252,7 +228,7 @@ void HTTPCURLContext::checkMultiInfo() { while ((message = curl_multi_info_read(multi, &pending))) { switch (message->msg) { case CURLMSG_DONE: { - HTTPRequest *baton = nullptr; + HTTPCURLRequest *baton = nullptr; curl_easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, (char *)&baton); assert(baton); baton->handleResult(message->data.result); @@ -349,106 +325,8 @@ int HTTPCURLContext::startTimeout(CURLM * /* multi */, long timeout_ms, void *us // ------------------------------------------------------------------------------------------------- -#ifdef __ANDROID__ - -// This function is called to load the CA bundle -// from http://curl.haxx.se/libcurl/c/cacertinmem.html¯ -static CURLcode sslctx_function(CURL * /* curl */, void *sslctx, void * /* parm */) { - - int error = 0; - struct zip *apk = zip_open(mbgl::android::apkPath.c_str(), 0, &error); - if (apk == nullptr) { - return CURLE_SSL_CACERT_BADFILE; - } - - struct zip_file *apkFile = zip_fopen(apk, "assets/ca-bundle.crt", ZIP_FL_NOCASE); - if (apkFile == nullptr) { - zip_close(apk); - apk = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - struct zip_stat stat; - if (zip_stat(apk, "assets/ca-bundle.crt", ZIP_FL_NOCASE, &stat) != 0) { - zip_fclose(apkFile); - apkFile = nullptr; - zip_close(apk); - apk = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - if (stat.size > std::numeric_limits::max()) { - zip_fclose(apkFile); - apkFile = nullptr; - zip_close(apk); - apk = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - const auto pem = std::make_unique(stat.size); - - if (static_cast(zip_fread(apkFile, reinterpret_cast(pem.get()), stat.size)) != stat.size) { - zip_fclose(apkFile); - apkFile = nullptr; - zip_close(apk); - apk = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - // get a pointer to the X509 certificate store (which may be empty!) - X509_STORE *store = SSL_CTX_get_cert_store((SSL_CTX *)sslctx); - if (store == nullptr) { - return CURLE_SSL_CACERT_BADFILE; - } - - // get a BIO - BIO *bio = BIO_new_mem_buf(pem.get(), static_cast(stat.size)); - if (bio == nullptr) { - store = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - // use it to read the PEM formatted certificate from memory into an X509 - // structure that SSL can use - X509 *cert = nullptr; - while (PEM_read_bio_X509(bio, &cert, 0, nullptr) != nullptr) { - if (cert == nullptr) { - BIO_free(bio); - bio = nullptr; - store = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - // add our certificate to this store - if (X509_STORE_add_cert(store, cert) == 0) { - X509_free(cert); - cert = nullptr; - BIO_free(bio); - bio = nullptr; - store = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - X509_free(cert); - cert = nullptr; - } - - // decrease reference counts - BIO_free(bio); - bio = nullptr; - - zip_fclose(apkFile); - apkFile = nullptr; - zip_close(apk); - apk = nullptr; - - // all set to go - return CURLE_OK; -} -#endif - -HTTPRequest::HTTPRequest(HTTPCURLContext* context_, const Resource& resource_, Callback callback_, uv_loop_t*, std::shared_ptr response_) - : RequestBase(resource_, callback_), +HTTPCURLRequest::HTTPCURLRequest(HTTPCURLContext* context_, const Resource& resource_, Callback callback_, uv_loop_t*, std::shared_ptr response_) + : HTTPRequestBase(resource_, callback_), context(context_), existingResponse(response_), handle(context->getHandle()) { @@ -476,26 +354,25 @@ HTTPRequest::HTTPRequest(HTTPCURLContext* context_, const Resource& resource_, C handleError(curl_easy_setopt(handle, CURLOPT_PRIVATE, this)); handleError(curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, error)); -#ifdef __ANDROID__ - handleError(curl_easy_setopt(handle, CURLOPT_SSLCERTTYPE, "PEM")); - handleError(curl_easy_setopt(handle, CURLOPT_SSL_CTX_FUNCTION, sslctx_function)); -#else handleError(curl_easy_setopt(handle, CURLOPT_CAINFO, "ca-bundle.crt")); -#endif handleError(curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1)); handleError(curl_easy_setopt(handle, CURLOPT_URL, resource.url.c_str())); handleError(curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, writeCallback)); handleError(curl_easy_setopt(handle, CURLOPT_WRITEDATA, this)); handleError(curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, headerCallback)); handleError(curl_easy_setopt(handle, CURLOPT_HEADERDATA, this)); +#if LIBCURL_VERSION_NUM >= ((7) << 16 | (21) << 8 | 6) // Renamed in 7.21.6 handleError(curl_easy_setopt(handle, CURLOPT_ACCEPT_ENCODING, "gzip, deflate")); +#else + handleError(curl_easy_setopt(handle, CURLOPT_ENCODING, "gzip, deflate")); +#endif handleError(curl_easy_setopt(handle, CURLOPT_USERAGENT, "MapboxGL/1.0")); handleError(curl_easy_setopt(handle, CURLOPT_SHARE, context->share)); start(); } -HTTPRequest::~HTTPRequest() { +HTTPCURLRequest::~HTTPCURLRequest() { MBGL_VERIFY_THREAD(tid); context->removeRequest(this); @@ -517,11 +394,11 @@ HTTPRequest::~HTTPRequest() { } } -void HTTPRequest::cancel() { +void HTTPCURLRequest::cancel() { delete this; } -void HTTPRequest::start() { +void HTTPCURLRequest::start() { // Count up the attempts. attempts++; @@ -531,9 +408,9 @@ void HTTPRequest::start() { // This function is called when we have new data for a request. We just append it to the string // containing the previous data. -size_t HTTPRequest::writeCallback(void *const contents, const size_t size, const size_t nmemb, void *userp) { +size_t HTTPCURLRequest::writeCallback(void *const contents, const size_t size, const size_t nmemb, void *userp) { assert(userp); - auto impl = reinterpret_cast(userp); + auto impl = reinterpret_cast(userp); MBGL_VERIFY_THREAD(impl->tid); if (!impl->response) { @@ -575,9 +452,9 @@ int64_t parseCacheControl(const char *value) { return 0; } -size_t HTTPRequest::headerCallback(char *const buffer, const size_t size, const size_t nmemb, void *userp) { +size_t HTTPCURLRequest::headerCallback(char *const buffer, const size_t size, const size_t nmemb, void *userp) { assert(userp); - auto baton = reinterpret_cast(userp); + auto baton = reinterpret_cast(userp); MBGL_VERIFY_THREAD(baton->tid); if (!baton->response) { @@ -604,7 +481,7 @@ size_t HTTPRequest::headerCallback(char *const buffer, const size_t size, const return length; } -void HTTPRequest::retry(uint64_t timeout) { +void HTTPCURLRequest::retry(uint64_t timeout) { handleError(curl_multi_remove_handle(context->multi, handle)); response.reset(); @@ -616,7 +493,7 @@ void HTTPRequest::retry(uint64_t timeout) { uv_timer_start(timer, restart, timeout, 0); } -void HTTPRequest::retry() { +void HTTPCURLRequest::retry() { // All batons get notified when the network status changed, but some of them // might not actually wait for the network to become available again. if (timer && strategy == PreemptImmediately) { @@ -627,12 +504,12 @@ void HTTPRequest::retry() { } #if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 -void HTTPRequest::restart(uv_timer_t *timer, int) { +void HTTPCURLRequest::restart(uv_timer_t *timer, int) { #else -void HTTPRequest::restart(uv_timer_t *timer) { +void HTTPCURLRequest::restart(uv_timer_t *timer) { #endif // Restart the request. - auto baton = reinterpret_cast(timer->data); + auto baton = reinterpret_cast(timer->data); // Get rid of the timer. baton->timer = nullptr; @@ -641,7 +518,7 @@ void HTTPRequest::restart(uv_timer_t *timer) { baton->start(); } -void HTTPRequest::finish(ResponseStatus status) { +void HTTPCURLRequest::finish(ResponseStatus status) { if (status == ResponseStatus::TemporaryError && attempts < maxAttempts) { strategy = ExponentialBackoff; return retry((1 << (attempts - 1)) * 1000); @@ -662,7 +539,7 @@ void HTTPRequest::finish(ResponseStatus status) { delete this; } -void HTTPRequest::handleResult(CURLcode code) { +void HTTPCURLRequest::handleResult(CURLcode code) { MBGL_VERIFY_THREAD(tid); if (cancelled) { @@ -720,12 +597,12 @@ void HTTPRequest::handleResult(CURLcode code) { } else if (responseCode >= 500 && responseCode < 600) { // Server errors may be temporary, so back off exponentially. response->status = Response::Error; - response->message = "HTTP status code " + std::to_string(responseCode); + response->message = "HTTP status code " + util::toString(responseCode); return finish(ResponseStatus::TemporaryError); } else { // We don't know how to handle any other errors, so declare them as permanently failing. response->status = Response::Error; - response->message = "HTTP status code " + std::to_string(responseCode); + response->message = "HTTP status code " + util::toString(responseCode); return finish(ResponseStatus::PermanentError); } } @@ -733,7 +610,7 @@ void HTTPRequest::handleResult(CURLcode code) { throw std::runtime_error("Response hasn't been handled"); } -std::unique_ptr HTTPContext::createContext(uv_loop_t* loop) { +std::unique_ptr HTTPContextBase::createContext(uv_loop_t* loop) { return std::make_unique(loop); } diff --git a/platform/default/image.cpp b/platform/default/image.cpp index 12aea898c..d7dcfc38b 100644 --- a/platform/default/image.cpp +++ b/platform/default/image.cpp @@ -27,7 +27,7 @@ const static bool png_version_check = []() { namespace mbgl { namespace util { -std::string compress_png(int width, int height, void *rgba) { +std::string compress_png(int width, int height, const void *rgba) { png_voidp error_ptr = 0; png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, error_ptr, NULL, NULL); if (!png_ptr) { diff --git a/platform/default/png_reader.cpp b/platform/default/png_reader.cpp index 4f8596a7b..ede7e52be 100644 --- a/platform/default/png_reader.cpp +++ b/platform/default/png_reader.cpp @@ -157,7 +157,10 @@ void PngReader::read(unsigned x0, unsigned y0, unsigned w, unsigned h, char * double gamma; if (png_get_gAMA(png_ptr, info_ptr, &gamma)) png_set_gamma(png_ptr, 2.2, gamma); + +#ifdef PNG_ALPHA_PREMULTIPLIED png_set_alpha_mode(png_ptr, PNG_ALPHA_PREMULTIPLIED, PNG_GAMMA_LINEAR); +#endif if (x0 == 0 && y0 == 0 && w >= width_ && h >= height_) { @@ -175,6 +178,19 @@ void PngReader::read(unsigned x0, unsigned y0, unsigned w, unsigned h, char * for (unsigned row = 0; row < height_; ++row) rows[row] = (png_bytep)image + row * width_ * 4 ; png_read_image(png_ptr, rows.get()); + +#ifndef PNG_ALPHA_PREMULTIPLIED + // Manually premultiply the image if libpng didn't do it for us. + for (unsigned row = 0; row < height_; ++row) { + for (unsigned x = 0; x < width_; x++) { + png_byte* ptr = &(rows[row][x * 4]); + const float a = ptr[3] / 255.0f; + ptr[0] *= a; + ptr[1] *= a; + ptr[2] *= a; + } + } +#endif } else { @@ -188,6 +204,7 @@ void PngReader::read(unsigned x0, unsigned y0, unsigned w, unsigned h, char * png_read_row(png_ptr,row.get(),0); if (i >= y0 && i < (y0 + h)) { + // TODO: premultiply this std::copy(&row[x0 * 4], &row[x0 * 4] + w * 4, image + i * width_* 4); } } diff --git a/platform/default/sqlite3.cpp b/platform/default/sqlite3.cpp index b2ae4e65b..ca525cd20 100644 --- a/platform/default/sqlite3.cpp +++ b/platform/default/sqlite3.cpp @@ -3,6 +3,7 @@ #include #include +#include // Check sqlite3 library version. const static bool sqliteVersionCheck = []() { diff --git a/platform/default/sqlite_cache.cpp b/platform/default/sqlite_cache.cpp index a2d2c6815..9d5d0541e 100644 --- a/platform/default/sqlite_cache.cpp +++ b/platform/default/sqlite_cache.cpp @@ -67,7 +67,7 @@ SQLiteCache::SQLiteCache(const std::string& path_) SQLiteCache::~SQLiteCache() = default; -SQLiteCache::Impl::Impl(uv_loop_t*, const std::string& path_) +SQLiteCache::Impl::Impl(const std::string& path_) : path(path_) { } @@ -126,15 +126,15 @@ void SQLiteCache::Impl::createSchema() { } } -void SQLiteCache::get(const Resource &resource, Callback callback) { +std::unique_ptr SQLiteCache::get(const Resource &resource, Callback callback) { // Can be called from any thread, but most likely from the file source thread. // Will try to load the URL from the SQLite database and call the callback when done. // Note that the callback is probably going to invoked from another thread, so the caller // must make sure that it can run in that thread. - thread->invokeWithResult(&Impl::get, std::move(callback), resource); + return thread->invokeWithCallback(&Impl::get, callback, resource); } -std::unique_ptr SQLiteCache::Impl::get(const Resource &resource) { +void SQLiteCache::Impl::get(const Resource &resource, Callback callback) { try { // This is called in the SQLite event loop. if (!db) { @@ -167,14 +167,14 @@ std::unique_ptr SQLiteCache::Impl::get(const Resource &resource) { if (getStmt->get(5)) { // == compressed response->data = util::decompress(response->data); } - return std::move(response); + callback(std::move(response)); } else { // There is no data. - return nullptr; + callback(nullptr); } } catch (mapbox::sqlite::Exception& ex) { Log::Error(Event::Database, ex.code, ex.what()); - return nullptr; + callback(nullptr); } } @@ -217,7 +217,7 @@ void SQLiteCache::Impl::put(const Resource& resource, std::shared_ptrbind(6 /* expires */, response->expires); std::string data; - if (resource.kind != Resource::Image) { + if (resource.kind != Resource::SpriteImage) { // Do not compress images, since they are typically compressed already. data = util::compress(response->data); } diff --git a/platform/default/sqlite_cache_impl.hpp b/platform/default/sqlite_cache_impl.hpp index e01400a58..700771f2e 100644 --- a/platform/default/sqlite_cache_impl.hpp +++ b/platform/default/sqlite_cache_impl.hpp @@ -3,8 +3,6 @@ #include -typedef struct uv_loop_s uv_loop_t; - namespace mapbox { namespace sqlite { class Database; @@ -16,10 +14,10 @@ namespace mbgl { class SQLiteCache::Impl { public: - Impl(uv_loop_t*, const std::string &path = ":memory:"); + explicit Impl(const std::string &path = ":memory:"); ~Impl(); - std::unique_ptr get(const Resource&); + void get(const Resource&, Callback); void put(const Resource& resource, std::shared_ptr response); void refresh(const Resource& resource, int64_t expires); diff --git a/platform/default/thread.cpp b/platform/default/thread.cpp index c0a1069b9..12d1802c2 100644 --- a/platform/default/thread.cpp +++ b/platform/default/thread.cpp @@ -1,11 +1,23 @@ #include +#include + +#include +#include + namespace mbgl { namespace platform { void makeThreadLowPriority() { - // no-op +#ifdef SCHED_IDLE + struct sched_param param; + param.sched_priority = 0; + int status = sched_setscheduler(0, SCHED_IDLE, ¶m); + if (status != 0) { + Log::Warning(Event::General, "Couldn't set thread scheduling policy"); + } +#endif } -} -} +} // namespace platform +} // namespace mbgl diff --git a/platform/default/uv_zip.c b/platform/default/uv_zip.c index cb1b07898..3019477eb 100644 --- a/platform/default/uv_zip.c +++ b/platform/default/uv_zip.c @@ -44,20 +44,6 @@ void uv__zip_file_error(uv_zip_t *zip) { uv__zip_store_error(zip, zip_file_strerror(zip->file)); } -void uv__zip_work_open(uv_work_t *req) { - uv_zip_t *zip = (uv_zip_t *)req->data; - assert(zip); - assert(!zip->archive); - - int error; - zip->archive = zip_open(zip->path, zip->flags, &error); - if (!zip->archive) { - uv__zip_open_error(zip, error); - } else { - zip->result = 0; - } -} - void uv__zip_work_fdopen(uv_work_t *req) { uv_zip_t *zip = (uv_zip_t *)req->data; assert(zip); @@ -169,18 +155,6 @@ void uv_zip_cleanup(uv_zip_t *zip) { } } -int uv_zip_open(uv_loop_t* loop, uv_zip_t *zip, const char *path, zip_flags_t flags, uv_zip_cb cb) { - assert(loop); - assert(zip); - assert(path); - assert(strlen(path)); - zip->result = 0; - zip->path = path; - zip->flags = flags; - zip->cb = cb; - return uv_queue_work(loop, &zip->work, uv__zip_work_open, uv__zip_after_work); -} - int uv_zip_fdopen(uv_loop_t* loop, uv_zip_t *zip, uv_file fd, int flags, uv_zip_cb cb) { assert(loop); assert(zip); diff --git a/platform/default/uv_zip.h b/platform/default/uv_zip.h index 5908763f0..869a415f2 100644 --- a/platform/default/uv_zip.h +++ b/platform/default/uv_zip.h @@ -30,7 +30,6 @@ struct uv_zip_s { void uv_zip_init(uv_zip_t *zip); void uv_zip_cleanup(uv_zip_t *zip); -int uv_zip_open(uv_loop_t* loop, uv_zip_t *zip, const char *path, zip_flags_t flags, uv_zip_cb cb); int uv_zip_fdopen(uv_loop_t* loop, uv_zip_t *zip, uv_file fd, int flags, uv_zip_cb cb); int uv_zip_stat(uv_loop_t* loop, uv_zip_t *zip, const char *fname, zip_flags_t flags, uv_zip_cb cb); int uv_zip_fopen(uv_loop_t* loop, uv_zip_t *zip, const char *fname, zip_flags_t flags, uv_zip_cb cb); diff --git a/platform/ios/MGLAnnotationImage.m b/platform/ios/MGLAnnotationImage.m new file mode 100644 index 000000000..e5052bc03 --- /dev/null +++ b/platform/ios/MGLAnnotationImage.m @@ -0,0 +1,30 @@ +#import "MGLAnnotationImage.h" + +@interface MGLAnnotationImage () + +@property (nonatomic) UIImage *image; +@property (nonatomic) NSString *reuseIdentifier; + +@end + +@implementation MGLAnnotationImage + ++ (instancetype)annotationImageWithImage:(UIImage *)image reuseIdentifier:(NSString *)reuseIdentifier +{ + return [[self alloc] initWithImage:image reuseIdentifier:reuseIdentifier]; +} + +- (instancetype)initWithImage:(UIImage *)image reuseIdentifier:(NSString *)reuseIdentifier +{ + self = [super init]; + + if (self) + { + _image = image; + _reuseIdentifier = [reuseIdentifier copy]; + } + + return self; +} + +@end diff --git a/platform/ios/MGLMapCamera.mm b/platform/ios/MGLMapCamera.mm new file mode 100644 index 000000000..d04e46fa9 --- /dev/null +++ b/platform/ios/MGLMapCamera.mm @@ -0,0 +1,101 @@ +#import "MGLMapCamera.h" + +#include + +@implementation MGLMapCamera + ++ (BOOL)supportsSecureCoding +{ + return YES; +} + ++ (instancetype)camera +{ + return [[self alloc] init]; +} + ++ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate + fromEyeCoordinate:(CLLocationCoordinate2D)eyeCoordinate + eyeAltitude:(CLLocationDistance)eyeAltitude +{ + mbgl::LatLng centerLatLng = mbgl::LatLng(centerCoordinate.latitude, centerCoordinate.longitude); + mbgl::LatLng eyeLatLng = mbgl::LatLng(eyeCoordinate.latitude, eyeCoordinate.longitude); + + mbgl::ProjectedMeters centerMeters = mbgl::Projection::projectedMetersForLatLng(centerLatLng); + mbgl::ProjectedMeters eyeMeters = mbgl::Projection::projectedMetersForLatLng(eyeLatLng); + CLLocationDirection heading = std::atan((centerMeters.northing - eyeMeters.northing) / + (centerMeters.easting - eyeMeters.easting)); + + double groundDistance = std::hypot(centerMeters.northing - eyeMeters.northing, + centerMeters.easting - eyeMeters.easting); + CGFloat pitch = std::atan(eyeAltitude / groundDistance); + + return [[self alloc] initWithCenterCoordinate:centerCoordinate + altitude:eyeAltitude + pitch:pitch + heading:heading]; +} + ++ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate + fromDistance:(CLLocationDistance)distance + pitch:(CGFloat)pitch + heading:(CLLocationDirection)heading +{ + return [[self alloc] initWithCenterCoordinate:centerCoordinate + altitude:distance + pitch:(CGFloat)pitch + heading:heading]; +} + +- (instancetype)initWithCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate + altitude:(CLLocationDistance)altitude + pitch:(CGFloat)pitch + heading:(CLLocationDirection)heading +{ + if (self = [super init]) + { + _centerCoordinate = centerCoordinate; + _altitude = altitude; + _pitch = pitch; + _heading = heading; + } + return self; +} + +- (nullable instancetype)initWithCoder:(NSCoder *)coder +{ + if (self = [super init]) + { + _centerCoordinate = CLLocationCoordinate2DMake([coder decodeDoubleForKey:@"centerLatitude"], + [coder decodeDoubleForKey:@"centerLongitude"]); + _altitude = [coder decodeDoubleForKey:@"altitude"]; + _pitch = [coder decodeDoubleForKey:@"pitch"]; + _heading = [coder decodeDoubleForKey:@"heading"]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder +{ + [coder encodeDouble:_centerCoordinate.latitude forKey:@"centerLatitude"]; + [coder encodeDouble:_centerCoordinate.longitude forKey:@"centerLongitude"]; + [coder encodeDouble:_altitude forKey:@"altitude"]; + [coder encodeDouble:_pitch forKey:@"pitch"]; + [coder encodeDouble:_heading forKey:@"heading"]; +} + +- (id)copyWithZone:(nullable NSZone *)zone +{ + return [[[self class] allocWithZone:zone] initWithCenterCoordinate:_centerCoordinate + altitude:_altitude + pitch:_pitch + heading:_heading]; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"", + self, _centerCoordinate.latitude, _centerCoordinate.longitude, _altitude, _heading, _pitch]; +} + +@end diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index 4957a0329..705627663 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -10,14 +10,17 @@ #include #include #include +#include #include #include #include #include #include +#include #include +#include -#import "MapboxGL.h" +#import "Mapbox.h" #import "NSBundle+MGLAdditions.h" #import "NSString+MGLAdditions.h" @@ -32,33 +35,41 @@ #import "SMCalloutView.h" #import +#import class MBGLView; -NSString *const MGLDefaultStyleName = @"mapbox-streets"; -NSString *const MGLStyleVersion = @"7"; +NSString *const MGLDefaultStyleName = @"streets"; NSString *const MGLDefaultStyleMarkerSymbolName = @"default_marker"; -NSString *const MGLMapboxSetupDocumentationURLDisplayString = @"mapbox.com/guides/first-steps-gl-ios"; +NSString *const MGLMapboxSetupDocumentationURLDisplayString = @"mapbox.com/guides/first-steps-ios-sdk"; + +NSUInteger const MGLStyleVersion = 8; const NSTimeInterval MGLAnimationDuration = 0.3; const CGSize MGLAnnotationUpdateViewportOutset = {150, 150}; const CGFloat MGLMinimumZoom = 3; +const CGFloat MGLMinimumPitch = 0; +const CGFloat MGLMaximumPitch = 60; +const CLLocationDegrees MGLAngularFieldOfView = M_PI / 6.; NSString *const MGLAnnotationIDKey = @"MGLAnnotationIDKey"; +NSString *const MGLAnnotationSymbolKey = @"MGLAnnotationSymbolKey"; static NSURL *MGLURLForBundledStyleNamed(NSString *styleName) { return [NSURL URLWithString:[NSString stringWithFormat:@"asset://styles/%@.json", styleName]]; } -CGFloat MGLRadiansFromDegrees(CLLocationDegrees degrees) +mbgl::util::UnitBezier MGLUnitBezierForMediaTimingFunction(CAMediaTimingFunction *function) { - return degrees * M_PI / 180; -} - -CLLocationDegrees MGLDegreesFromRadians(CGFloat radians) -{ - return radians * 180 / M_PI; + if ( ! function) + { + function = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]; + } + float p1[2], p2[2]; + [function getControlPointAtIndex:0 values:p1]; + [function getControlPointAtIndex:1 values:p2]; + return { p1[0], p1[1], p2[0], p2[1] }; } #pragma mark - Private - @@ -69,17 +80,19 @@ CLLocationDegrees MGLDegreesFromRadians(CGFloat radians) @property (nonatomic) GLKView *glView; @property (nonatomic) UIImageView *glSnapshotView; @property (nonatomic) NSOperationQueue *regionChangeDelegateQueue; -@property (nonatomic) UIImageView *compass; -@property (nonatomic) UIImageView *logoBug; -@property (nonatomic) NS_MUTABLE_ARRAY_OF(NSLayoutConstraint *) *logoBugConstraints; -@property (nonatomic) UIButton *attributionButton; +@property (nonatomic, readwrite) UIImageView *compassView; +@property (nonatomic, readwrite) UIImageView *logoView; +@property (nonatomic) NS_MUTABLE_ARRAY_OF(NSLayoutConstraint *) *logoViewConstraints; +@property (nonatomic, readwrite) UIButton *attributionButton; @property (nonatomic) NS_MUTABLE_ARRAY_OF(NSLayoutConstraint *) *attributionButtonConstraints; @property (nonatomic) UIActionSheet *attributionSheet; @property (nonatomic) UIPanGestureRecognizer *pan; @property (nonatomic) UIPinchGestureRecognizer *pinch; @property (nonatomic) UIRotationGestureRecognizer *rotate; @property (nonatomic) UILongPressGestureRecognizer *quickZoom; +@property (nonatomic) UIPanGestureRecognizer *twoFingerDrag; @property (nonatomic) NSMapTable *annotationIDsByAnnotation; +@property (nonatomic) NS_MUTABLE_DICTIONARY_OF(NSString *, MGLAnnotationImage *) *annotationImages; @property (nonatomic) std::vector annotationsNearbyLastTap; @property (nonatomic, weak) id selectedAnnotation; @property (nonatomic) SMCalloutView *selectedAnnotationCalloutView; @@ -100,6 +113,7 @@ CLLocationDegrees MGLDegreesFromRadians(CGFloat radians) mbgl::Map *_mbglMap; MBGLView *_mbglView; mbgl::DefaultFileSource *_mbglFileSource; + BOOL _isWaitingForRedundantReachableNotification; NS_MUTABLE_ARRAY_OF(NSURL *) *_bundledStyleURLs; @@ -183,9 +197,9 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) if ( ! styleURL) { - styleURL = MGLURLForBundledStyleNamed([NSString stringWithFormat:@"%@-v%@", + styleURL = MGLURLForBundledStyleNamed([NSString stringWithFormat:@"%@-v%lu", MGLDefaultStyleName.lowercaseString, - MGLStyleVersion]); + (unsigned long)MGLStyleVersion]); } if ( ! [styleURL scheme]) @@ -200,61 +214,29 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) - (void)commonInit { _isTargetingInterfaceBuilder = NSProcessInfo.processInfo.mgl_isInterfaceBuilderDesignablesAgent; - - // create context - // - _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; - NSAssert(_context, @"Failed to create OpenGL ES context."); - - // setup accessibility - // - self.accessibilityLabel = @"Map"; - - // create GL view - // - _glView = [[GLKView alloc] initWithFrame:self.bounds context:_context]; - _glView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - _glView.enableSetNeedsDisplay = YES; - _glView.drawableStencilFormat = GLKViewDrawableStencilFormat8; - _glView.drawableDepthFormat = GLKViewDrawableDepthFormat16; - if ([UIScreen instancesRespondToSelector:@selector(nativeScale)]) { - _glView.contentScaleFactor = [[UIScreen mainScreen] nativeScale]; + + BOOL background = [UIApplication sharedApplication].applicationState == UIApplicationStateBackground; + if (!background) + { + [self createGLView]; } - _glView.delegate = self; - [_glView bindDrawable]; - [self insertSubview:_glView atIndex:0]; - - _glView.contentMode = UIViewContentModeCenter; + self.accessibilityLabel = @"Map"; self.backgroundColor = [UIColor clearColor]; self.clipsToBounds = YES; - // load extensions - // - mbgl::gl::InitializeExtensions([](const char * name) { - static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengles")); - 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(symbol); - }); - // setup mbgl map // - _mbglView = new MBGLView(self); + const float scaleFactor = [UIScreen instancesRespondToSelector:@selector(nativeScale)] ? [[UIScreen mainScreen] nativeScale] : [[UIScreen mainScreen] scale]; + _mbglView = new MBGLView(self, scaleFactor); _mbglFileSource = new mbgl::DefaultFileSource([MGLFileCache obtainSharedCacheWithObject:self]); - // Start paused on the IB canvas + // Start paused _mbglMap = new mbgl::Map(*_mbglView, *_mbglFileSource, mbgl::MapMode::Continuous); - if (_isTargetingInterfaceBuilder) { + if (_isTargetingInterfaceBuilder || background) { + self.dormant = YES; _mbglMap->pause(); } - _mbglMap->resize(self.bounds.size.width, self.bounds.size.height, _glView.contentScaleFactor); // Observe for changes to the global access token (and find out the current one). [[MGLAccountManager sharedManager] addObserver:self @@ -270,22 +252,29 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) object:nil]; MGLReachability* reachability = [MGLReachability reachabilityForInternetConnection]; + if ([reachability isReachable]) + { + _isWaitingForRedundantReachableNotification = YES; + } [reachability startNotifier]; // setup annotations // _annotationIDsByAnnotation = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableStrongMemory]; + + _annotationImages = [NSMutableDictionary dictionary]; + std::string defaultSymbolName([MGLDefaultStyleMarkerSymbolName UTF8String]); _mbglMap->setDefaultPointAnnotationSymbol(defaultSymbolName); // setup logo bug // UIImage *logo = [[MGLMapView resourceImageNamed:@"mapbox.png"] imageWithAlignmentRectInsets:UIEdgeInsetsMake(1.5, 4, 3.5, 2)]; - _logoBug = [[UIImageView alloc] initWithImage:logo]; - _logoBug.accessibilityLabel = @"Mapbox logo"; - _logoBug.translatesAutoresizingMaskIntoConstraints = NO; - [self addSubview:_logoBug]; - _logoBugConstraints = [NSMutableArray array]; + _logoView = [[UIImageView alloc] initWithImage:logo]; + _logoView.accessibilityLabel = @"Mapbox logo"; + _logoView.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:_logoView]; + _logoViewConstraints = [NSMutableArray array]; // setup attribution // @@ -296,7 +285,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) [self addSubview:_attributionButton]; _attributionButtonConstraints = [NSMutableArray array]; - _attributionSheet = [[UIActionSheet alloc] initWithTitle:@"Mapbox GL for iOS" + _attributionSheet = [[UIActionSheet alloc] initWithTitle:@"Mapbox iOS SDK" delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:nil @@ -314,13 +303,12 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) // setup compass // - _compass = [[UIImageView alloc] initWithImage:[MGLMapView resourceImageNamed:@"Compass.png"]]; - _compass.accessibilityLabel = @"Compass"; - UIImage *compassImage = [MGLMapView resourceImageNamed:@"Compass.png"]; - _compass.frame = CGRectMake(0, 0, compassImage.size.width, compassImage.size.height); - _compass.alpha = 0; + _compassView = [[UIImageView alloc] initWithImage:[MGLMapView resourceImageNamed:@"Compass.png"]]; + _compassView.accessibilityLabel = @"Compass"; + _compassView.frame = CGRectMake(0, 0, _compassView.image.size.width, _compassView.image.size.height); + _compassView.alpha = 0; UIView *container = [[UIView alloc] initWithFrame:CGRectZero]; - [container addSubview:_compass]; + [container addSubview:_compassView]; container.translatesAutoresizingMaskIntoConstraints = NO; [container addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleCompassTapGesture:)]]; [self addSubview:container]; @@ -356,6 +344,15 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) [twoFingerTap requireGestureRecognizerToFail:_pinch]; [twoFingerTap requireGestureRecognizerToFail:_rotate]; [self addGestureRecognizer:twoFingerTap]; + + _twoFingerDrag = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleTwoFingerDragGesture:)]; + _twoFingerDrag.minimumNumberOfTouches = 2; + _twoFingerDrag.maximumNumberOfTouches = 2; + _twoFingerDrag.delegate = self; + [_twoFingerDrag requireGestureRecognizerToFail:twoFingerTap]; + [_twoFingerDrag requireGestureRecognizerToFail:_pan]; + [self addGestureRecognizer:_twoFingerDrag]; + _pitchEnabled = YES; if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) { @@ -367,6 +364,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) // observe app activity // + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willTerminate) name:UIApplicationWillTerminateNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sleepGL:) name:UIApplicationWillResignActiveNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sleepGL:) name:UIApplicationDidEnterBackgroundNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(wakeGL:) name:UIApplicationWillEnterForegroundNotification object:nil]; @@ -375,7 +373,10 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) // set initial position // - _mbglMap->setLatLngZoom(mbgl::LatLng(0, 0), _mbglMap->getMinZoom()); + mbgl::CameraOptions options; + options.center = mbgl::LatLng(0, 0); + options.zoom = _mbglMap->getMinZoom(); + _mbglMap->jumpTo(options); _pendingLatitude = NAN; _pendingLongitude = NAN; @@ -396,12 +397,53 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) }]; } --(void)reachabilityChanged:(NSNotification*)notification +- (void)createGLView +{ + if (_context) return; + + // create context + // + _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; + NSAssert(_context, @"Failed to create OpenGL ES context."); + + // create GL view + // + _glView = [[GLKView alloc] initWithFrame:self.bounds context:_context]; + _glView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + _glView.enableSetNeedsDisplay = YES; + _glView.drawableStencilFormat = GLKViewDrawableStencilFormat8; + _glView.drawableDepthFormat = GLKViewDrawableDepthFormat16; + _glView.contentScaleFactor = [UIScreen instancesRespondToSelector:@selector(nativeScale)] ? [[UIScreen mainScreen] nativeScale] : [[UIScreen mainScreen] scale]; + _glView.delegate = self; + [_glView bindDrawable]; + [self insertSubview:_glView atIndex:0]; + + _glView.contentMode = UIViewContentModeCenter; + + // load extensions + // + mbgl::gl::InitializeExtensions([](const char * name) { + static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengles")); + 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(symbol); + }); +} + +- (void)reachabilityChanged:(NSNotification *)notification { MGLReachability *reachability = [notification object]; - if ([reachability isReachable]) { + if ( ! _isWaitingForRedundantReachableNotification && [reachability isReachable]) + { mbgl::NetworkStatus::Reachable(); } + _isWaitingForRedundantReachableNotification = NO; } - (void)dealloc @@ -436,8 +478,8 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) [EAGLContext setCurrentContext:nil]; } - [self.logoBugConstraints removeAllObjects]; - self.logoBugConstraints = nil; + [self.logoViewConstraints removeAllObjects]; + self.logoViewConstraints = nil; [self.attributionButtonConstraints removeAllObjects]; self.attributionButtonConstraints = nil; } @@ -447,6 +489,14 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) if (_delegate == delegate) return; _delegate = delegate; + + if ([delegate respondsToSelector:@selector(mapView:symbolNameForAnnotation:)]) + { + [NSException raise:@"Method unavailable" format: + @"-mapView:symbolNameForAnnotation: has been removed from the MGLMapViewDelegate protocol, but %@ still implements it. " + @"Implement -[%@ mapView:imageForAnnotation:] instead.", + NSStringFromClass([delegate class]), NSStringFromClass([delegate class])]; + } } #pragma mark - Layout - @@ -499,7 +549,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) // compass // - UIView *compassContainer = self.compass.superview; + UIView *compassContainer = self.compassView.superview; if ([NSLayoutConstraint respondsToSelector:@selector(deactivateConstraints:)]) { [NSLayoutConstraint deactivateConstraints:compassContainer.constraints]; @@ -539,65 +589,65 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) multiplier:1 constant:5]]; - [compassContainerConstraints addObject: + [compassContainer addConstraint: [NSLayoutConstraint constraintWithItem:compassContainer attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 - constant:self.compass.image.size.width]]; + constant:self.compassView.image.size.width]]; - [compassContainerConstraints addObject: + [compassContainer addConstraint: [NSLayoutConstraint constraintWithItem:compassContainer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 - constant:self.compass.image.size.height]]; + constant:self.compassView.image.size.height]]; if ([NSLayoutConstraint respondsToSelector:@selector(activateConstraints:)]) { [NSLayoutConstraint activateConstraints:compassContainerConstraints]; } else { - [compassContainer addConstraints:compassContainerConstraints]; + [constraintParentView addConstraints:compassContainerConstraints]; } // logo bug // if ([NSLayoutConstraint respondsToSelector:@selector(deactivateConstraints:)]) { - [NSLayoutConstraint deactivateConstraints:self.logoBugConstraints]; + [NSLayoutConstraint deactivateConstraints:self.logoViewConstraints]; } else { - [self.logoBug removeConstraints:self.logoBugConstraints]; + [self.logoView removeConstraints:self.logoViewConstraints]; } - [self.logoBugConstraints removeAllObjects]; + [self.logoViewConstraints removeAllObjects]; if (viewController) { - [self.logoBugConstraints addObject: + [self.logoViewConstraints addObject: [NSLayoutConstraint constraintWithItem:viewController.bottomLayoutGuide attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationGreaterThanOrEqual - toItem:self.logoBug + toItem:self.logoView attribute:NSLayoutAttributeBaseline multiplier:1 constant:8]]; } - [self.logoBugConstraints addObject: + [self.logoViewConstraints addObject: [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationGreaterThanOrEqual - toItem:self.logoBug + toItem:self.logoView attribute:NSLayoutAttributeBaseline multiplier:1 constant:8]]; - [self.logoBugConstraints addObject: - [NSLayoutConstraint constraintWithItem:self.logoBug + [self.logoViewConstraints addObject: + [NSLayoutConstraint constraintWithItem:self.logoView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self @@ -606,11 +656,11 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) constant:8]]; if ([NSLayoutConstraint respondsToSelector:@selector(activateConstraints:)]) { - [NSLayoutConstraint activateConstraints:self.logoBugConstraints]; + [NSLayoutConstraint activateConstraints:self.logoViewConstraints]; } else { - [constraintParentView addConstraints:self.logoBugConstraints]; + [constraintParentView addConstraints:self.logoViewConstraints]; } // attribution button @@ -665,12 +715,10 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) } // This is the delegate of the GLKView object's display call. -- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect +- (void)glkView:(__unused GLKView *)view drawInRect:(__unused CGRect)rect { if ( ! self.isDormant) { - _mbglMap->resize(rect.size.width, rect.size.height, view.contentScaleFactor); - CGFloat zoomFactor = _mbglMap->getMaxZoom() - _mbglMap->getMinZoom() + 1; CGFloat cpuFactor = (CGFloat)[[NSProcessInfo processInfo] processorCount]; CGFloat memoryFactor = (CGFloat)[[NSProcessInfo processInfo] physicalMemory] / 1000 / 1000 / 1000; @@ -682,6 +730,8 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) _mbglMap->setSourceTileCacheSize(cacheSize); _mbglMap->renderSync(); + + [self updateUserLocationAnnotationView]; } } @@ -692,7 +742,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) if ( ! _isTargetingInterfaceBuilder) { - _mbglMap->update(); + _mbglMap->update(mbgl::Update::Dimensions); } if (self.attributionSheet.visible) @@ -700,15 +750,28 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) [self.attributionSheet dismissWithClickedButtonIndex:self.attributionSheet.cancelButtonIndex animated:YES]; } - if (self.compass.alpha) + if (self.compassView.alpha) { [self updateHeadingForDeviceOrientation]; [self updateCompass]; + [self updateUserLocationAnnotationView]; } } #pragma mark - Life Cycle - +- (void)willTerminate +{ + MGLAssertIsMainThread(); + + if ( ! self.isDormant) + { + self.dormant = YES; + _mbglMap->pause(); + [self.glView deleteDrawable]; + } +} + - (void)sleepGL:(__unused NSNotification *)notification { MGLAssertIsMainThread(); @@ -747,10 +810,11 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) { MGLAssertIsMainThread(); - if (self.isDormant) + if (self.isDormant && [UIApplication sharedApplication].applicationState != UIApplicationStateBackground) { self.dormant = NO; + [self createGLView]; [MGLMapboxEvents validate]; self.glSnapshotView.hidden = YES; @@ -781,7 +845,11 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) { [self resetNorthAnimated:YES]; - if (self.userTrackingMode == MGLUserTrackingModeFollowWithHeading) self.userTrackingMode = MGLUserTrackingModeFollow; + if (self.userTrackingMode == MGLUserTrackingModeFollowWithHeading || + self.userTrackingMode == MGLUserTrackingModeFollowWithCourse) + { + self.userTrackingMode = MGLUserTrackingModeFollow; + } } - (void)touchesBegan:(__unused NS_SET_OF(UITouch *) *)touches withEvent:(__unused UIEvent *)event @@ -1274,6 +1342,57 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) } } +- (void)handleTwoFingerDragGesture:(UIPanGestureRecognizer *)twoFingerDrag +{ + if ( ! self.isPitchEnabled) return; + + _mbglMap->cancelTransitions(); + + if (twoFingerDrag.state == UIGestureRecognizerStateBegan) + { + [self trackGestureEvent:MGLEventGesturePitchStart forRecognizer:twoFingerDrag]; + } + else if (twoFingerDrag.state == UIGestureRecognizerStateBegan || twoFingerDrag.state == UIGestureRecognizerStateChanged) + { + CGFloat gestureDistance = CGPoint([twoFingerDrag translationInView:twoFingerDrag.view]).y; + CGFloat currentPitch = _mbglMap->getPitch(); + CGFloat slowdown = 20.0; + + CGFloat pitchNew = mbgl::util::clamp(currentPitch - (gestureDistance / slowdown), MGLMinimumPitch, MGLMaximumPitch); + + _mbglMap->setPitch(pitchNew); + } + else if (twoFingerDrag.state == UIGestureRecognizerStateEnded || twoFingerDrag.state == UIGestureRecognizerStateCancelled) + { + [self unrotateIfNeededAnimated:YES]; + + //[self notifyMapChange:(mbgl::MapChangeRegionDidChange)]; + } + +} + +- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer +{ + if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) + { + UIPanGestureRecognizer *panGesture = (UIPanGestureRecognizer *)gestureRecognizer; + + if (panGesture.minimumNumberOfTouches == 2) + { + CGPoint velocity = [panGesture velocityInView:panGesture.view]; + double gestureAngle = MGLDegreesFromRadians(atan(velocity.y / velocity.x)); + double horizontalToleranceDegrees = 20.0; + + // cancel if gesture angle is not 90º±20º (more or less vertical) + if ( ! (fabs((fabs(gestureAngle) - 90.0)) < horizontalToleranceDegrees)) + { + return NO; + } + } + } + return YES; +} + - (void)handleCalloutAccessoryTapGesture:(UITapGestureRecognizer *)tap { if ([self.delegate respondsToSelector:@selector(mapView:annotation:calloutAccessoryControlTapped:)]) @@ -1367,14 +1486,20 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) return [NSSet setWithObject:@"allowsRotating"]; } ++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingPitchEnabled +{ + return [NSSet setWithObject:@"allowsPitching"]; +} + - (void)setDebugActive:(BOOL)debugActive { _mbglMap->setDebug(debugActive); + _mbglMap->setCollisionDebug(debugActive); } - (BOOL)isDebugActive { - return _mbglMap->getDebug(); + return (_mbglMap->getDebug() || _mbglMap->getCollisionDebug()); } - (void)resetNorth @@ -1384,24 +1509,9 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) - (void)resetNorthAnimated:(BOOL)animated { - self.userTrackingMode = MGLUserTrackingModeNone; - CGFloat duration = (animated ? MGLAnimationDuration : 0); _mbglMap->setBearing(0, secondsAsDuration(duration)); - - [UIView animateWithDuration:duration - animations:^ - { - self.compass.transform = CGAffineTransformIdentity; - } - completion:^(BOOL finished) - { - if (finished) - { - [self notifyMapChange:(animated ? mbgl::MapChangeRegionDidChangeAnimated : mbgl::MapChangeRegionDidChange)]; - } - }]; } - (void)resetPosition @@ -1414,6 +1524,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) - (void)toggleDebug { _mbglMap->toggleDebug(); + _mbglMap->toggleCollisionDebug(); } - (void)emptyMemoryCache @@ -1425,7 +1536,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) + (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingCenterCoordinate { - return [NSSet setWithObjects:@"latitude", @"longitude", nil]; + return [NSSet setWithObjects:@"latitude", @"longitude", @"camera", nil]; } - (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated preservingTracking:(BOOL)tracking @@ -1437,13 +1548,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) - (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated { - CGFloat duration = (animated ? MGLAnimationDuration : 0); - - _mbglMap->setLatLngZoom(MGLLatLngFromLocationCoordinate2D(coordinate), - fmaxf(_mbglMap->getZoom(), self.currentMinimumZoom), - secondsAsDuration(duration)); - - [self notifyMapChange:(animated ? mbgl::MapChangeRegionDidChangeAnimated : mbgl::MapChangeRegionDidChange)]; + [self setCenterCoordinate:coordinate zoomLevel:self.zoomLevel animated:animated]; } - (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate @@ -1457,43 +1562,58 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration) } - (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel animated:(BOOL)animated +{ + [self setCenterCoordinate:centerCoordinate zoomLevel:zoomLevel direction:self.direction animated:animated]; +} + +- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel direction:(CLLocationDirection)direction animated:(BOOL)animated { self.userTrackingMode = MGLUserTrackingModeNone; - CGFloat duration = (animated ? MGLAnimationDuration : 0); + [self _setCenterCoordinate:centerCoordinate zoomLevel:zoomLevel direction:direction animated:animated]; +} - _mbglMap->setLatLngZoom(MGLLatLngFromLocationCoordinate2D(centerCoordinate), zoomLevel, secondsAsDuration(duration)); +- (void)_setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel direction:(CLLocationDirection)direction animated:(BOOL)animated +{ + mbgl::CameraOptions options; + options.center = MGLLatLngFromLocationCoordinate2D(centerCoordinate); + options.zoom = fmaxf(zoomLevel, self.currentMinimumZoom); + if (direction >= 0) + { + options.angle = MGLRadiansFromDegrees(-direction); + } + if (animated) + { + options.duration = secondsAsDuration(MGLAnimationDuration); + options.easing = MGLUnitBezierForMediaTimingFunction(nil); + } + _mbglMap->easeTo(options); [self unrotateIfNeededAnimated:animated]; [self notifyMapChange:(animated ? mbgl::MapChangeRegionDidChangeAnimated : mbgl::MapChangeRegionDidChange)]; } ++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingZoomLevel +{ + return [NSSet setWithObject:@"camera"]; +} + - (double)zoomLevel { return _mbglMap->getZoom(); } -- (void)setZoomLevel:(double)zoomLevel animated:(BOOL)animated -{ - self.userTrackingMode = MGLUserTrackingModeNone; - - CGFloat duration = (animated ? MGLAnimationDuration : 0); - - _mbglMap->setLatLngZoom(_mbglMap->getLatLng(), - fmaxf(zoomLevel, self.currentMinimumZoom), - secondsAsDuration(duration)); - - [self unrotateIfNeededAnimated:animated]; - - [self notifyMapChange:(animated ? mbgl::MapChangeRegionDidChangeAnimated : mbgl::MapChangeRegionDidChange)]; -} - - (void)setZoomLevel:(double)zoomLevel { [self setZoomLevel:zoomLevel animated:NO]; } +- (void)setZoomLevel:(double)zoomLevel animated:(BOOL)animated +{ + [self setCenterCoordinate:self.centerCoordinate zoomLevel:zoomLevel animated:animated]; +} + MGLCoordinateBounds MGLCoordinateBoundsFromLatLngBounds(mbgl::LatLngBounds latLngBounds) { return MGLCoordinateBoundsMake(MGLLocationCoordinate2DFromLatLng(latLngBounds.sw), @@ -1534,11 +1654,34 @@ mbgl::LatLngBounds MGLLatLngBoundsFromCoordinateBounds(MGLCoordinateBounds coord animated:animated]; } +- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction animated:(BOOL)animated +{ + CLLocationCoordinate2D coordinates[] = { + {bounds.ne.latitude, bounds.sw.longitude}, + bounds.sw, + {bounds.sw.latitude, bounds.ne.longitude}, + bounds.ne, + }; + [self setVisibleCoordinates:coordinates + count:sizeof(coordinates) / sizeof(coordinates[0]) + edgePadding:insets + direction:direction + animated:animated]; +} + - (void)setVisibleCoordinates:(CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated +{ + [self setVisibleCoordinates:coordinates count:count edgePadding:insets direction:self.direction animated:animated]; +} + +- (void)setVisibleCoordinates:(CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction animated:(BOOL)animated +{ + [self setVisibleCoordinates:coordinates count:count edgePadding:insets direction:direction duration:animated ? MGLAnimationDuration : 0 animationTimingFunction:nil]; +} + +- (void)setVisibleCoordinates:(CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction duration:(NSTimeInterval)duration animationTimingFunction:(CAMediaTimingFunction *)function { // NOTE: does not disrupt tracking mode - CGFloat duration = animated ? MGLAnimationDuration : 0; - [self willChangeValueForKey:@"visibleCoordinateBounds"]; mbgl::EdgeInsets mbglInsets = {insets.top, insets.left, insets.bottom, insets.right}; mbgl::AnnotationSegment segment; @@ -1547,22 +1690,32 @@ mbgl::LatLngBounds MGLLatLngBoundsFromCoordinateBounds(MGLCoordinateBounds coord { segment.push_back({coordinates[i].latitude, coordinates[i].longitude}); } - _mbglMap->fitBounds(segment, mbglInsets, secondsAsDuration(duration)); + mbgl::CameraOptions options = _mbglMap->cameraForLatLngs(segment, mbglInsets); + if (direction >= 0) + { + options.angle = MGLRadiansFromDegrees(-direction); + } + if (duration > 0) + { + options.duration = secondsAsDuration(duration); + options.easing = MGLUnitBezierForMediaTimingFunction(function); + } + _mbglMap->easeTo(options); [self didChangeValueForKey:@"visibleCoordinateBounds"]; - [self unrotateIfNeededAnimated:animated]; + [self unrotateIfNeededAnimated:duration > 0]; - [self notifyMapChange:(animated ? mbgl::MapChangeRegionDidChangeAnimated : mbgl::MapChangeRegionDidChange)]; + [self notifyMapChange:(duration > 0 ? mbgl::MapChangeRegionDidChangeAnimated : mbgl::MapChangeRegionDidChange)]; +} + ++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingDirection +{ + return [NSSet setWithObject:@"camera"]; } - (CLLocationDirection)direction { - double direction = _mbglMap->getBearing(); - - while (direction > 360) direction -= 360; - while (direction < 0) direction += 360; - - return direction; + return mbgl::util::wrap(_mbglMap->getBearing(), 0., 360.); } - (void)setDirection:(CLLocationDirection)direction animated:(BOOL)animated @@ -1583,6 +1736,141 @@ mbgl::LatLngBounds MGLLatLngBoundsFromCoordinateBounds(MGLCoordinateBounds coord [self setDirection:direction animated:NO]; } ++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingPitch +{ + return [NSSet setWithObject:@"camera"]; +} + ++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingCamera +{ + return [NSSet setWithObjects:@"longitude", @"latitude", @"centerCoordinate", @"zoomLevel", @"direction", nil]; +} + +- (MGLMapCamera *)camera +{ + CGRect frame = self.frame; + CGPoint edgePoint; + // Constrain by the shorter of the two axes. + if (frame.size.width > frame.size.height) // landscape + { + edgePoint = CGPointMake(0, frame.size.height / 2.); + } + else // portrait + { + edgePoint = CGPointMake(frame.size.width / 2., 0); + } + CLLocationCoordinate2D edgeCoordinate = [self convertPoint:edgePoint toCoordinateFromView:self]; + mbgl::ProjectedMeters edgeMeters = _mbglMap->projectedMetersForLatLng(MGLLatLngFromLocationCoordinate2D(edgeCoordinate)); + + // Because we constrain the zoom level vertically in portrait orientation, + // the visible medial span is affected by pitch: the distance from the + // center point to the near edge is less than than distance from the center + // point to the far edge. Average the two distances. + mbgl::ProjectedMeters nearEdgeMeters; + if (frame.size.width > frame.size.height) + { + nearEdgeMeters = edgeMeters; + } + else + { + CGPoint nearEdgePoint = CGPointMake(frame.size.width / 2., frame.size.height); + CLLocationCoordinate2D nearEdgeCoordinate = [self convertPoint:nearEdgePoint toCoordinateFromView:self]; + nearEdgeMeters = _mbglMap->projectedMetersForLatLng(MGLLatLngFromLocationCoordinate2D(nearEdgeCoordinate)); + } + + // The opposite side is the distance between the center and one edge. + CLLocationCoordinate2D centerCoordinate = self.centerCoordinate; + mbgl::ProjectedMeters centerMeters = _mbglMap->projectedMetersForLatLng(MGLLatLngFromLocationCoordinate2D(centerCoordinate)); + CLLocationDistance centerToEdge = std::hypot(centerMeters.easting - edgeMeters.easting, + centerMeters.northing - edgeMeters.northing); + CLLocationDistance centerToNearEdge = std::hypot(centerMeters.easting - nearEdgeMeters.easting, + centerMeters.northing - nearEdgeMeters.northing); + CLLocationDistance altitude = (centerToEdge + centerToNearEdge) / 2 / std::tan(MGLAngularFieldOfView / 2.); + + CGFloat pitch = _mbglMap->getPitch(); + + return [MGLMapCamera cameraLookingAtCenterCoordinate:centerCoordinate + fromDistance:altitude + pitch:pitch + heading:self.direction]; +} + +- (void)setCamera:(MGLMapCamera *)camera +{ + [self setCamera:camera animated:NO]; +} + +- (void)setCamera:(MGLMapCamera *)camera animated:(BOOL)animated +{ + [self setCamera:camera withDuration:animated ? MGLAnimationDuration : 0 animationTimingFunction:nil]; +} + +- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(CAMediaTimingFunction *)function +{ + // The opposite side is the distance between the center and one edge. + mbgl::LatLng centerLatLng = MGLLatLngFromLocationCoordinate2D(camera.centerCoordinate); + mbgl::ProjectedMeters centerMeters = _mbglMap->projectedMetersForLatLng(centerLatLng); + CLLocationDistance centerToEdge = camera.altitude * std::tan(MGLAngularFieldOfView / 2.); + + double angle = -1; + if (camera.heading >= 0) + { + angle = MGLRadiansFromDegrees(-camera.heading); + } + double pitch = -1; + if (camera.pitch >= 0) + { + pitch = MGLRadiansFromDegrees(mbgl::util::clamp(camera.pitch, MGLMinimumPitch, MGLMaximumPitch)); + } + + // Make a visible bounds that extends in the constrained direction (the + // shorter of the two axes). + CGRect frame = self.frame; + mbgl::LatLng sw, ne; + if (frame.size.width > frame.size.height) // landscape + { + sw = _mbglMap->latLngForProjectedMeters({ + centerMeters.northing - centerToEdge * std::sin(angle), + centerMeters.easting - centerToEdge * std::cos(angle), + }); + ne = _mbglMap->latLngForProjectedMeters({ + centerMeters.northing + centerToEdge * std::sin(angle), + centerMeters.easting + centerToEdge * std::cos(angle), + }); + } + else // portrait + { + sw = _mbglMap->latLngForProjectedMeters({ + centerMeters.northing - centerToEdge * std::cos(-angle) + centerToEdge * std::cos(-angle) * std::sin(pitch) / 2, + centerMeters.easting - centerToEdge * std::sin(-angle) + centerToEdge * std::sin(-angle) * std::sin(pitch) / 2, + }); + ne = _mbglMap->latLngForProjectedMeters({ + centerMeters.northing + centerToEdge * std::cos(-angle) - centerToEdge * std::cos(-angle) * std::sin(pitch) / 2, + centerMeters.easting + centerToEdge * std::sin(-angle) - centerToEdge * std::sin(-angle) * std::sin(pitch) / 2, + }); + } + + // Fit the viewport to the bounds. Correct the center in case pitch should + // cause the visual center to lie above the screen center. + mbgl::CameraOptions options = _mbglMap->cameraForLatLngs({ sw, ne }, {}); + options.center = centerLatLng; + + if (camera.heading >= 0) + { + options.angle = angle; + } + if (pitch >= 0) + { + options.pitch = pitch; + } + if (duration > 0) + { + options.duration = secondsAsDuration(duration); + options.easing = MGLUnitBezierForMediaTimingFunction(function); + } + _mbglMap->easeTo(options); +} + - (CLLocationCoordinate2D)convertPoint:(CGPoint)point toCoordinateFromView:(nullable UIView *)view { CGPoint convertedPoint = [self convertPoint:point fromView:view]; @@ -1781,18 +2069,24 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) std::vector points; std::vector shapes; - BOOL delegateImplementsSymbolLookup = [self.delegate respondsToSelector:@selector(mapView:symbolNameForAnnotation:)]; + 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:)]; + const std::string spritePrefix = "com.mapbox.sprites."; + for (id annotation in annotations) { - assert([annotation conformsToProtocol:@protocol(MGLAnnotation)]); + NSAssert([annotation conformsToProtocol:@protocol(MGLAnnotation)], @"annotation should conform to MGLAnnotation"); 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); @@ -1846,8 +2140,6 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) userInfo:nil] raise]; } - NSUInteger count = [(MGLMultiPoint *)annotation pointCount]; - CLLocationCoordinate2D *coordinates = (CLLocationCoordinate2D *)malloc(count * sizeof(CLLocationCoordinate2D)); [(MGLMultiPoint *)annotation getCoordinates:coordinates range:NSMakeRange(0, count)]; @@ -1865,11 +2157,49 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) } else { + MGLAnnotationImage *annotationImage = nil; NSString *symbolName = nil; - if (delegateImplementsSymbolLookup) + if (delegateImplementsImageForPoint) { - symbolName = [self.delegate mapView:self symbolNameForAnnotation:annotation]; + annotationImage = [self.delegate mapView:self imageForAnnotation:annotation]; + + if (annotationImage) + { + const std::string cSymbolName = spritePrefix + std::string(annotationImage.reuseIdentifier.UTF8String); + symbolName = [NSString stringWithUTF8String:cSymbolName.c_str()]; + + if ( ! [self.annotationImages objectForKey:annotationImage.reuseIdentifier]) + { + // store image & symbol name + [self.annotationImages setObject:annotationImage forKey:annotationImage.reuseIdentifier]; + + // retrieve pixels + CGImageRef image = annotationImage.image.CGImage; + size_t width = CGImageGetWidth(image); + size_t height = CGImageGetHeight(image); + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + std::string pixels(width * height * 4, '\0'); + size_t bytesPerPixel = 4; + size_t bytesPerRow = bytesPerPixel * width; + size_t bitsPerComponent = 8; + char *pixelData = const_cast(pixels.data()); + CGContextRef context = CGBitmapContextCreate(pixelData, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast); + CGContextDrawImage(context, CGRectMake(0, 0, width, height), image); + CGContextRelease(context); + CGColorSpaceRelease(colorSpace); + + // add sprite + auto cSpriteImage = std::make_shared( + uint16_t(annotationImage.image.size.width), + uint16_t(annotationImage.image.size.height), + float(annotationImage.image.scale), + std::move(pixels)); + + // sprite upload + _mbglMap->setSprite(cSymbolName, cSpriteImage); + } + } } points.emplace_back(MGLLatLngFromLocationCoordinate2D(annotation.coordinate), symbolName ? [symbolName UTF8String] : ""); @@ -1882,8 +2212,10 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) for (size_t i = 0; i < pointAnnotationIDs.size(); ++i) { - [self.annotationIDsByAnnotation setObject:@{ MGLAnnotationIDKey : @(pointAnnotationIDs[i]) } - forKey:annotations[i]]; + [self.annotationIDsByAnnotation setObject:@{ + MGLAnnotationIDKey : @(pointAnnotationIDs[i]), + MGLAnnotationSymbolKey : [NSString stringWithUTF8String:points[i].icon.c_str()] + } forKey:annotations[i]]; } } @@ -1919,10 +2251,11 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) for (id annotation in annotations) { - assert([annotation conformsToProtocol:@protocol(MGLAnnotation)]); + NSAssert([annotation conformsToProtocol:@protocol(MGLAnnotation)], @"annotation should conform to MGLAnnotation"); + + NSDictionary *infoDictionary = [self.annotationIDsByAnnotation objectForKey:annotation]; + annotationIDsToRemove.push_back([[infoDictionary objectForKey:MGLAnnotationIDKey] unsignedIntValue]); - annotationIDsToRemove.push_back([[[self.annotationIDsByAnnotation objectForKey:annotation] - objectForKey:MGLAnnotationIDKey] unsignedIntValue]); [self.annotationIDsByAnnotation removeObjectForKey:annotation]; if (annotation == self.selectedAnnotation) @@ -1934,6 +2267,41 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) _mbglMap->removeAnnotations(annotationIDsToRemove); } +- (void)addOverlay:(id )overlay +{ + [self addOverlays:@[ overlay ]]; +} + +- (void)addOverlays:(NS_ARRAY_OF(id ) *)overlays +{ + for (id overlay in overlays) + { + NSAssert([overlay conformsToProtocol:@protocol(MGLOverlay)], @"overlay should conform to MGLOverlay"); + } + + [self addAnnotations:overlays]; +} + +- (void)removeOverlay:(id )overlay +{ + [self removeOverlays:@[ overlay ]]; +} + +- (void)removeOverlays:(NS_ARRAY_OF(id ) *)overlays +{ + for (id overlay in overlays) + { + NSAssert([overlay conformsToProtocol:@protocol(MGLOverlay)], @"overlay should conform to MGLOverlay"); + } + + [self removeAnnotations:overlays]; +} + +- (MGLAnnotationImage *)dequeueReusableAnnotationImageWithIdentifier:(NSString *)identifier +{ + return [self.annotationImages objectForKey:identifier]; +} + - (NS_ARRAY_OF(id ) *)selectedAnnotations { return (self.selectedAnnotation ? @[ self.selectedAnnotation ] : @[]); @@ -1945,7 +2313,7 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) id firstAnnotation = selectedAnnotations[0]; - assert([firstAnnotation conformsToProtocol:@protocol(MGLAnnotation)]); + NSAssert([firstAnnotation conformsToProtocol:@protocol(MGLAnnotation)], @"annotation should conform to MGLAnnotation"); if ([firstAnnotation isKindOfClass:[MGLMultiPoint class]]) return; @@ -1970,7 +2338,9 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) self.selectedAnnotation = annotation; - if (annotation.title && [self.delegate respondsToSelector:@selector(mapView:annotationCanShowCallout:)] && + if ([annotation respondsToSelector:@selector(title)] && + annotation.title && + [self.delegate respondsToSelector:@selector(mapView:annotationCanShowCallout:)] && [self.delegate mapView:self annotationCanShowCallout:annotation]) { // build the callout @@ -1986,16 +2356,13 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) else { // determine symbol in use for point - NSString *symbol = MGLDefaultStyleMarkerSymbolName; - if ([self.delegate respondsToSelector:@selector(mapView:symbolNameForAnnotation:)]) - { - symbol = [self.delegate mapView:self symbolNameForAnnotation:annotation]; - } - std::string symbolName([symbol UTF8String]); + NSString *customSymbol = [[self.annotationIDsByAnnotation objectForKey:annotation] objectForKey:MGLAnnotationSymbolKey]; + NSString *symbolName = [customSymbol length] ? customSymbol : MGLDefaultStyleMarkerSymbolName; + std::string cSymbolName([symbolName UTF8String]); // determine anchor point based on symbol CGPoint calloutAnchorPoint = [self convertCoordinate:annotation.coordinate toPointToView:self]; - double y = _mbglMap->getTopOffsetPixelsForAnnotationSymbol(symbolName); + double y = _mbglMap->getTopOffsetPixelsForAnnotationSymbol(cSymbolName); calloutBounds = CGRectMake(calloutAnchorPoint.x - 1, calloutAnchorPoint.y + y, 0, 0); } @@ -2075,6 +2442,29 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) } } +- (void)showAnnotations:(NS_ARRAY_OF(id ) *)annotations animated:(BOOL)animated +{ + if ( ! annotations || ! annotations.count) return; + + mbgl::LatLngBounds bounds; + + for (id annotation in annotations) + { + if ([annotation conformsToProtocol:@protocol(MGLOverlay)]) + { + bounds.extend(MGLLatLngBoundsFromCoordinateBounds(((id )annotation).overlayBounds)); + } + else + { + bounds.extend(MGLLatLngFromLocationCoordinate2D(annotation.coordinate)); + } + } + + [self setVisibleCoordinateBounds:MGLCoordinateBoundsFromLatLngBounds(bounds) + edgePadding:UIEdgeInsetsMake(100, 100, 100, 100) + animated:animated]; +} + #pragma mark - User Location - - (void)setShowsUserLocation:(BOOL)showsUserLocation @@ -2108,7 +2498,15 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) [NSException raise:@"Missing Location Services usage description" format: @"In iOS 8 and above, this app must have a value for NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription in its Info.plist."]; } - [self.locationManager requestWhenInUseAuthorization]; + // request location permissions, if both keys exist ask for less permissive + if ([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"]) + { + [self.locationManager requestWhenInUseAuthorization]; + } + else if ([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysUsageDescription"]) + { + [self.locationManager requestAlwaysAuthorization]; + } } #endif @@ -2179,7 +2577,8 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) { if (mode == _userTrackingMode) return; - if (mode == MGLUserTrackingModeFollowWithHeading && ! CLLocationCoordinate2DIsValid(self.userLocation.coordinate)) + if ((mode == MGLUserTrackingModeFollowWithHeading || mode == MGLUserTrackingModeFollowWithCourse) && + ! CLLocationCoordinate2DIsValid(self.userLocation.coordinate)) { mode = MGLUserTrackingModeNone; } @@ -2189,13 +2588,13 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) switch (_userTrackingMode) { case MGLUserTrackingModeNone: - default: { [self.locationManager stopUpdatingHeading]; break; } case MGLUserTrackingModeFollow: + case MGLUserTrackingModeFollowWithCourse: { self.showsUserLocation = YES; @@ -2203,10 +2602,7 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) if (self.userLocationAnnotationView) { - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdeprecated-declarations" - [self locationManager:self.locationManager didUpdateToLocation:self.userLocation.location fromLocation:self.userLocation.location]; - #pragma clang diagnostic pop + [self locationManager:self.locationManager didUpdateLocations:@[self.userLocation.location]]; } break; @@ -2219,10 +2615,7 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) if (self.userLocationAnnotationView) { - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdeprecated-declarations" - [self locationManager:self.locationManager didUpdateToLocation:self.userLocation.location fromLocation:self.userLocation.location]; - #pragma clang diagnostic pop + [self locationManager:self.locationManager didUpdateLocations:@[self.userLocation.location]]; } [self updateHeadingForDeviceOrientation]; @@ -2239,11 +2632,14 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) } } -- (void)locationManager:(__unused CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation +- (void)locationManager:(__unused CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { + CLLocation *oldLocation = self.userLocation.location; + CLLocation *newLocation = locations.lastObject; + if ( ! _showsUserLocation || ! newLocation || ! CLLocationCoordinate2DIsValid(newLocation.coordinate)) return; - if ([newLocation distanceFromLocation:oldLocation] || ! oldLocation) + if (! oldLocation || ! CLLocationCoordinate2DIsValid(oldLocation.coordinate) || [newLocation distanceFromLocation:oldLocation]) { self.userLocation.location = newLocation; @@ -2255,6 +2651,12 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) [self.delegate mapView:self didUpdateUserLocation:self.userLocation]; } } + + CLLocationDirection course = self.userLocation.location.course; + if (course < 0 || self.userTrackingMode != MGLUserTrackingModeFollowWithCourse) + { + course = -1; + } if (self.userTrackingMode != MGLUserTrackingModeNone) { @@ -2269,7 +2671,7 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) { // at sufficient detail, just re-center the map; don't zoom // - [self setCenterCoordinate:self.userLocation.location.coordinate animated:YES preservingTracking:YES]; + [self _setCenterCoordinate:self.userLocation.location.coordinate zoomLevel:self.zoomLevel direction:course animated:YES]; } else { @@ -2299,12 +2701,12 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) desiredSouthWest.longitude != actualSouthWest.longitude) { // assumes we won't disrupt tracking mode - [self setVisibleCoordinateBounds:MGLCoordinateBoundsMake(desiredSouthWest, desiredNorthEast) animated:YES]; + [self setVisibleCoordinateBounds:MGLCoordinateBoundsMake(desiredSouthWest, desiredNorthEast) edgePadding:UIEdgeInsetsZero direction:course animated:YES]; } } } } - + self.userLocationAnnotationView.haloLayer.hidden = ! CLLocationCoordinate2DIsValid(self.userLocation.coordinate) || newLocation.horizontalAccuracy > 10; @@ -2333,9 +2735,9 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) if ( ! _showsUserLocation) return; } - CLLocationDirection headingDirection = (newHeading.trueHeading > 0 ? newHeading.trueHeading : newHeading.magneticHeading); + CLLocationDirection headingDirection = (newHeading.trueHeading >= 0 ? newHeading.trueHeading : newHeading.magneticHeading); - if (headingDirection > 0 && self.userTrackingMode == MGLUserTrackingModeFollowWithHeading) + if (headingDirection >= 0 && self.userTrackingMode == MGLUserTrackingModeFollowWithHeading) { _mbglMap->setBearing(headingDirection, secondsAsDuration(MGLAnimationDuration)); } @@ -2455,14 +2857,16 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) - (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: { - [self updateUserLocationAnnotationView]; - [self updateCompass]; - [self deselectAnnotation:self.selectedAnnotation animated:NO]; BOOL animated = (change == mbgl::MapChangeRegionWillChangeAnimated); @@ -2497,24 +2901,24 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) } case mbgl::MapChangeRegionIsChanging: { - [self updateUserLocationAnnotationView]; [self updateCompass]; if ([self.delegate respondsToSelector:@selector(mapViewRegionIsChanging:)]) { [self.delegate mapViewRegionIsChanging:self]; } + break; } case mbgl::MapChangeRegionDidChange: case mbgl::MapChangeRegionDidChangeAnimated: { - [self updateUserLocationAnnotationView]; [self updateCompass]; - if (self.pan.state == UIGestureRecognizerStateChanged || - self.pinch.state == UIGestureRecognizerStateChanged || - self.rotate.state == UIGestureRecognizerStateChanged || - self.quickZoom.state == UIGestureRecognizerStateChanged) return; + if (self.pan.state == UIGestureRecognizerStateChanged || + self.pinch.state == UIGestureRecognizerStateChanged || + self.rotate.state == UIGestureRecognizerStateChanged || + self.quickZoom.state == UIGestureRecognizerStateChanged || + self.twoFingerDrag.state == UIGestureRecognizerStateChanged) return; if (self.isAnimatingGesture) return; @@ -2613,31 +3017,29 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) - (void)updateCompass { - double degrees = _mbglMap->getBearing() * -1; - while (degrees >= 360) degrees -= 360; - while (degrees < 0) degrees += 360; + CLLocationDirection degrees = mbgl::util::wrap(-self.direction, 0., 360.); - self.compass.transform = CGAffineTransformMakeRotation(MGLRadiansFromDegrees(degrees)); + self.compassView.transform = CGAffineTransformMakeRotation(MGLRadiansFromDegrees(degrees)); - if (_mbglMap->getBearing() && self.compass.alpha < 1) + if (_mbglMap->getBearing() && self.compassView.alpha < 1) { [UIView animateWithDuration:MGLAnimationDuration delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^ { - self.compass.alpha = 1; + self.compassView.alpha = 1; } completion:nil]; } - else if (_mbglMap->getBearing() == 0 && self.compass.alpha > 0) + else if (_mbglMap->getBearing() == 0 && self.compassView.alpha > 0) { [UIView animateWithDuration:MGLAnimationDuration delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^ { - self.compass.alpha = 0; + self.compassView.alpha = 0; } completion:nil]; } @@ -2660,7 +3062,7 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) if (!path) { [NSException raise:@"Resource not found" format: - @"The resource named “%@” could not be found in the Mapbox GL resource bundle.", name]; + @"The resource named “%@” could not be found in the Mapbox resource bundle.", name]; } return path; @@ -2786,10 +3188,26 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) class MBGLView : public mbgl::View { public: - MBGLView(MGLMapView *nativeView_) : nativeView(nativeView_) {} + MBGLView(MGLMapView* nativeView_, const float scaleFactor_) + : nativeView(nativeView_), scaleFactor(scaleFactor_) { + } virtual ~MBGLView() {} + float getPixelRatio() const override { + return scaleFactor; + } + + std::array getSize() const override { + return {{ static_cast([nativeView bounds].size.width), + static_cast([nativeView bounds].size.height) }}; + } + + std::array getFramebufferSize() const override { + return {{ static_cast([[nativeView glView] drawableWidth]), + static_cast([[nativeView glView] drawableHeight]) }}; + } + void notify() override { // no-op @@ -2818,13 +3236,19 @@ class MBGLView : public mbgl::View waitUntilDone:NO]; } - void swap() override + void beforeRender() override + { + // no-op + } + + void afterRender() override { // no-op } private: __weak MGLMapView *nativeView = nullptr; + const float scaleFactor; }; @end @@ -2833,7 +3257,7 @@ class MBGLView : public mbgl::View + (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingLatitude { - return [NSSet setWithObject:@"centerCoordinate"]; + return [NSSet setWithObjects:@"centerCoordinate", @"camera", nil]; } - (double)latitude @@ -2859,7 +3283,7 @@ class MBGLView : public mbgl::View + (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingLongitude { - return [NSSet setWithObject:@"centerCoordinate"]; + return [NSSet setWithObjects:@"centerCoordinate", @"camera", nil]; } - (double)longitude @@ -2928,6 +3352,21 @@ class MBGLView : public mbgl::View self.rotateEnabled = allowsRotating; } ++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingAllowsPitching +{ + return [NSSet setWithObject:@"pitchEnabled"]; +} + +- (BOOL)allowsPitching +{ + return self.pitchEnabled; +} + +- (void)setAllowsPitching:(BOOL)allowsPitching +{ + self.pitchEnabled = allowsPitching; +} + - (void)didReceiveMemoryWarning { _mbglMap->onLowMemory(); diff --git a/platform/ios/MGLMapboxEvents.h b/platform/ios/MGLMapboxEvents.h index 9efb53c5e..45f809495 100644 --- a/platform/ios/MGLMapboxEvents.h +++ b/platform/ios/MGLMapboxEvents.h @@ -30,6 +30,7 @@ extern NSString *const MGLEventGestureQuickZoom; extern NSString *const MGLEventGesturePanStart; extern NSString *const MGLEventGesturePinchStart; extern NSString *const MGLEventGestureRotateStart; +extern NSString *const MGLEventGesturePitchStart; typedef NS_DICTIONARY_OF(NSString *, id) MGLMapboxEventAttributes; typedef NS_MUTABLE_DICTIONARY_OF(NSString *, id) MGLMutableMapboxEventAttributes; diff --git a/platform/ios/MGLMapboxEvents.m b/platform/ios/MGLMapboxEvents.m index eb8d909db..77e68ddde 100644 --- a/platform/ios/MGLMapboxEvents.m +++ b/platform/ios/MGLMapboxEvents.m @@ -45,6 +45,7 @@ NSString *const MGLEventGestureQuickZoom = @"QuickZoom"; NSString *const MGLEventGesturePanStart = @"Pan"; NSString *const MGLEventGesturePinchStart = @"Pinch"; NSString *const MGLEventGestureRotateStart = @"Rotation"; +NSString *const MGLEventGesturePitchStart = @"Pitch"; const NSUInteger MGLMaximumEventsPerFlush = 20; const NSTimeInterval MGLFlushInterval = 60; @@ -604,10 +605,7 @@ const NSTimeInterval MGLFlushInterval = 60; [request setHTTPBody:jsonData]; // Send non blocking HTTP Request to server - NSURLSessionDataTask *task = [_session dataTaskWithRequest:request completionHandler:^(__unused NSData * __nullable data, __unused NSURLResponse * __nullable response, __unused NSError * __nullable error) - { - }]; - [task resume]; + [[_session dataTaskWithRequest:request] resume]; } }); } diff --git a/platform/ios/MGLUserLocation.m b/platform/ios/MGLUserLocation.m index 0b0cf70cb..36d71c13b 100644 --- a/platform/ios/MGLUserLocation.m +++ b/platform/ios/MGLUserLocation.m @@ -37,13 +37,13 @@ NS_ASSUME_NONNULL_END - (void)setLocation:(CLLocation *)newLocation { - if ([newLocation distanceFromLocation:_location] && newLocation.coordinate.latitude != 0 && - newLocation.coordinate.longitude != 0) - { - [self willChangeValueForKey:@"location"]; - _location = newLocation; - [self didChangeValueForKey:@"location"]; - } + if ( ! newLocation || ! CLLocationCoordinate2DIsValid(newLocation.coordinate)) return; + if ( _location && CLLocationCoordinate2DIsValid(_location.coordinate) && [newLocation distanceFromLocation:_location] == 0) return; + if (newLocation.coordinate.latitude == 0 && newLocation.coordinate.longitude == 0) return; + + [self willChangeValueForKey:@"location"]; + _location = newLocation; + [self didChangeValueForKey:@"location"]; } - (BOOL)isUpdating diff --git a/platform/ios/MGLUserLocationAnnotationView.m b/platform/ios/MGLUserLocationAnnotationView.m index 67f360f68..b477b1088 100644 --- a/platform/ios/MGLUserLocationAnnotationView.m +++ b/platform/ios/MGLUserLocationAnnotationView.m @@ -5,7 +5,11 @@ #import "MGLAnnotation.h" #import "MGLMapView.h" -const CGFloat MGLTrackingDotRingWidth = 24.0; +const CGFloat MGLUserLocationAnnotationDotSize = 22.0; +const CGFloat MGLUserLocationAnnotationHaloSize = 115.0; + +const CGFloat MGLUserLocationAnnotationPuckSize = 45.0; +const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuckSize * 0.6; @interface MGLUserLocationAnnotationView () @@ -17,9 +21,21 @@ const CGFloat MGLTrackingDotRingWidth = 24.0; @implementation MGLUserLocationAnnotationView { + BOOL _puckModeActivated; + + CALayer *_puckDot; + CAShapeLayer *_puckArrow; + + CALayer *_headingIndicatorLayer; + CAShapeLayer *_headingIndicatorMaskLayer; CALayer *_accuracyRingLayer; CALayer *_dotBorderLayer; CALayer *_dotLayer; + + double _oldHeadingAccuracy; + CLLocationAccuracy _oldHorizontalAccuracy; + double _oldZoom; + double _oldPitch; } - (instancetype)initWithFrame:(CGRect)frame @@ -30,7 +46,9 @@ const CGFloat MGLTrackingDotRingWidth = 24.0; - (instancetype)initInMapView:(MGLMapView *)mapView { - if (self = [super initWithFrame:CGRectMake(0, 0, MGLTrackingDotRingWidth, MGLTrackingDotRingWidth)]) + CGFloat frameSize = (mapView.userTrackingMode == MGLUserTrackingModeFollowWithCourse) ? MGLUserLocationAnnotationPuckSize : MGLUserLocationAnnotationDotSize; + + if (self = [super initWithFrame:CGRectMake(0, 0, frameSize, frameSize)]) { self.annotation = [[MGLUserLocation alloc] initWithMapView:mapView]; _mapView = mapView; @@ -48,164 +66,427 @@ const CGFloat MGLTrackingDotRingWidth = 24.0; - (void)setTintColor:(UIColor *)tintColor { - UIImage *trackingDotHaloImage = [self trackingDotHaloImage]; - _haloLayer.bounds = CGRectMake(0, 0, trackingDotHaloImage.size.width, trackingDotHaloImage.size.height); - _haloLayer.contents = (__bridge id)[trackingDotHaloImage CGImage]; - - UIImage *dotImage = [self dotImage]; - _dotLayer.bounds = CGRectMake(0, 0, dotImage.size.width, dotImage.size.height); - _dotLayer.contents = (__bridge id)[dotImage CGImage]; + if (_puckModeActivated) + { + _puckArrow.fillColor = [tintColor CGColor]; + } + else + { + if (_accuracyRingLayer) + { + _accuracyRingLayer.backgroundColor = [tintColor CGColor]; + } + + _haloLayer.backgroundColor = [tintColor CGColor]; + _dotLayer.backgroundColor = [tintColor CGColor]; + + _headingIndicatorLayer.contents = (__bridge id)[[self headingIndicatorTintedGradientImage] CGImage]; + } } - (void)setupLayers { if (CLLocationCoordinate2DIsValid(self.annotation.coordinate)) { - if ( ! _accuracyRingLayer && self.annotation.location.horizontalAccuracy) - { - UIImage *accuracyRingImage = [self accuracyRingImage]; - _accuracyRingLayer = [CALayer layer]; - _haloLayer.bounds = CGRectMake(0, 0, accuracyRingImage.size.width, accuracyRingImage.size.height); - _haloLayer.contents = (__bridge id)[accuracyRingImage CGImage]; - _haloLayer.position = CGPointMake(super.layer.bounds.size.width / 2.0, super.layer.bounds.size.height / 2.0); - - [self.layer addSublayer:_accuracyRingLayer]; - } - - if ( ! _haloLayer) - { - UIImage *haloImage = [self trackingDotHaloImage]; - _haloLayer = [CALayer layer]; - _haloLayer.bounds = CGRectMake(0, 0, haloImage.size.width, haloImage.size.height); - _haloLayer.contents = (__bridge id)[haloImage CGImage]; - _haloLayer.position = CGPointMake(super.layer.bounds.size.width / 2.0, super.layer.bounds.size.height / 2.0); - - [CATransaction begin]; - - [CATransaction setAnimationDuration:3.5]; - [CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]]; - - // scale out radially - // - CABasicAnimation *boundsAnimation = [CABasicAnimation animationWithKeyPath:@"transform"]; - boundsAnimation.repeatCount = MAXFLOAT; - boundsAnimation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.1, 0.1, 1.0)]; - boundsAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(2.0, 2.0, 1.0)]; - boundsAnimation.removedOnCompletion = NO; - - [_haloLayer addAnimation:boundsAnimation forKey:@"animateScale"]; - - // go transparent as scaled out - // - CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"]; - opacityAnimation.repeatCount = MAXFLOAT; - opacityAnimation.fromValue = [NSNumber numberWithFloat:1.0]; - opacityAnimation.toValue = [NSNumber numberWithFloat:-1.0]; - opacityAnimation.removedOnCompletion = NO; - - [_haloLayer addAnimation:opacityAnimation forKey:@"animateOpacity"]; - - [CATransaction commit]; - - [self.layer addSublayer:_haloLayer]; - } - - // white dot background with shadow - // - if ( ! _dotBorderLayer) - { - CGRect rect = CGRectMake(0, 0, MGLTrackingDotRingWidth * 1.5, MGLTrackingDotRingWidth * 1.5); - - UIGraphicsBeginImageContextWithOptions(rect.size, NO, [[UIScreen mainScreen] scale]); - CGContextRef context = UIGraphicsGetCurrentContext(); - - CGContextSetShadow(context, CGSizeMake(0, 0), MGLTrackingDotRingWidth / 4.0); - - CGContextSetFillColorWithColor(context, [[UIColor whiteColor] CGColor]); - CGContextFillEllipseInRect(context, CGRectMake((rect.size.width - MGLTrackingDotRingWidth) / 2.0, (rect.size.height - MGLTrackingDotRingWidth) / 2.0, MGLTrackingDotRingWidth, MGLTrackingDotRingWidth)); - - UIImage *whiteBackground = UIGraphicsGetImageFromCurrentImageContext(); - - UIGraphicsEndImageContext(); - - _dotBorderLayer = [CALayer layer]; - _dotBorderLayer.bounds = CGRectMake(0, 0, whiteBackground.size.width, whiteBackground.size.height); - _dotBorderLayer.contents = (__bridge id)[whiteBackground CGImage]; - _dotBorderLayer.position = CGPointMake(super.layer.bounds.size.width / 2.0, super.layer.bounds.size.height / 2.0); - [self.layer addSublayer:_dotBorderLayer]; - } - - // pulsing, tinted dot sublayer - // - if ( ! _dotLayer) - { - UIImage *dotImage = [self dotImage]; - _dotLayer = [CALayer layer]; - _dotLayer.bounds = CGRectMake(0, 0, dotImage.size.width, dotImage.size.height); - _dotLayer.contents = (__bridge id)[dotImage CGImage]; - _dotLayer.position = CGPointMake(super.layer.bounds.size.width / 2.0, super.layer.bounds.size.height / 2.0); - - CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"]; - animation.repeatCount = MAXFLOAT; - animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.0, 1.0, 1.0)]; - animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.8, 0.8, 1.0)]; - animation.removedOnCompletion = NO; - animation.autoreverses = YES; - animation.duration = 1.5; - animation.beginTime = CACurrentMediaTime() + 1.0; - animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]; - - [_dotLayer addAnimation:animation forKey:@"animateTransform"]; - - [self.layer addSublayer:_dotLayer]; - } + (_mapView.userTrackingMode == MGLUserTrackingModeFollowWithCourse) ? [self drawPuck] : [self drawDot]; + [self updatePitch]; } } -- (UIImage *)accuracyRingImage +- (void)updatePitch +{ + if (self.mapView.camera.pitch != _oldPitch) + { + CATransform3D t = CATransform3DRotate(CATransform3DIdentity, MGLRadiansFromDegrees(self.mapView.camera.pitch), 1.0, 0, 0); + self.layer.sublayerTransform = t; + + [self updateFaux3DEffect]; + + _oldPitch = self.mapView.camera.pitch; + } +} + +- (void)updateFaux3DEffect +{ + CGFloat pitch = MGLRadiansFromDegrees(self.mapView.camera.pitch); + + if (_puckDot) + { + _puckDot.shadowOffset = CGSizeMake(0, fmaxf(pitch * 10.f, 1.f)); + _puckDot.shadowRadius = fmaxf(pitch * 5.f, 0.75f); + } + + if (_dotBorderLayer) + { + _dotBorderLayer.shadowOffset = CGSizeMake(0.f, pitch * 10.f); + _dotBorderLayer.shadowRadius = fmaxf(pitch * 5.f, 3.f); + } + + if (_dotLayer) + { + _dotLayer.zPosition = pitch * 2.f; + } +} + +- (void)drawPuck +{ + if ( ! _puckModeActivated) + { + self.layer.sublayers = nil; + + _headingIndicatorLayer = nil; + _headingIndicatorMaskLayer = nil; + _accuracyRingLayer = nil; + _haloLayer = nil; + _dotBorderLayer = nil; + _dotLayer = nil; + } + + // background dot (white with black shadow) + // + if ( ! _puckDot) + { + _puckDot = [self circleLayerWithSize:MGLUserLocationAnnotationPuckSize]; + _puckDot.backgroundColor = [[UIColor whiteColor] CGColor]; + _puckDot.shadowColor = [[UIColor blackColor] CGColor]; + _puckDot.shadowOpacity = 0.25; + + if (self.mapView.camera.pitch) + { + [self updateFaux3DEffect]; + } + else + { + _puckDot.shadowOffset = CGSizeMake(0, 1); + _puckDot.shadowRadius = 0.75; + } + + [self.layer addSublayer:_puckDot]; + } + + // arrow + // + if ( ! _puckArrow) + { + _puckArrow = [CAShapeLayer layer]; + _puckArrow.path = [[self puckArrow] CGPath]; + _puckArrow.fillColor = [_mapView.tintColor CGColor]; + _puckArrow.bounds = CGRectMake(0, 0, MGLUserLocationAnnotationArrowSize, MGLUserLocationAnnotationArrowSize); + _puckArrow.position = CGPointMake(super.bounds.size.width / 2.0, super.bounds.size.height / 2.0); + _puckArrow.shouldRasterize = YES; + _puckArrow.rasterizationScale = [UIScreen mainScreen].scale; + _puckArrow.drawsAsynchronously = YES; + + [self.layer addSublayer:_puckArrow]; + } + + if ( ! _puckModeActivated) + { + _puckModeActivated = YES; + + [self updateFaux3DEffect]; + } +} + +- (UIBezierPath *)puckArrow +{ + CGFloat max = MGLUserLocationAnnotationArrowSize; + + UIBezierPath *bezierPath = UIBezierPath.bezierPath; + [bezierPath moveToPoint: CGPointMake(max * 0.5, 0)]; + [bezierPath addLineToPoint: CGPointMake(max * 0.1, max)]; + [bezierPath addLineToPoint: CGPointMake(max * 0.5, max * 0.65)]; + [bezierPath addLineToPoint: CGPointMake(max * 0.9, max)]; + [bezierPath addLineToPoint: CGPointMake(max * 0.5, 0)]; + [bezierPath closePath]; + + return bezierPath; +} + +- (void)drawDot +{ + if (_puckModeActivated) + { + self.layer.sublayers = nil; + + _puckDot = nil; + _puckArrow = nil; + } + + // update heading indicator + // + if (_headingIndicatorLayer) + { + _headingIndicatorLayer.hidden = !(_mapView.userTrackingMode == MGLUserTrackingModeFollowWithHeading || + _mapView.userTrackingMode == MGLUserTrackingModeFollowWithCourse); + + if (_oldHeadingAccuracy != self.annotation.heading.headingAccuracy) + { + // recalculate the clipping mask based on updated accuracy + _headingIndicatorMaskLayer.path = [[self headingIndicatorClippingMask] CGPath]; + + _oldHeadingAccuracy = self.annotation.heading.headingAccuracy; + } + } + + // heading indicator (tinted, semi-circle) + // + if ( ! _headingIndicatorLayer && self.annotation.heading.headingAccuracy) + { + CGFloat headingIndicatorSize = MGLUserLocationAnnotationHaloSize; + + _headingIndicatorLayer = [CALayer layer]; + _headingIndicatorLayer.bounds = CGRectMake(0, 0, headingIndicatorSize, headingIndicatorSize); + _headingIndicatorLayer.position = CGPointMake(super.bounds.size.width / 2.0, super.bounds.size.height / 2.0); + _headingIndicatorLayer.contents = (__bridge id)[[self headingIndicatorTintedGradientImage] CGImage]; + _headingIndicatorLayer.contentsGravity = kCAGravityBottom; + _headingIndicatorLayer.contentsScale = [UIScreen mainScreen].scale; + _headingIndicatorLayer.opacity = 0.4; + _headingIndicatorLayer.shouldRasterize = YES; + _headingIndicatorLayer.rasterizationScale = [UIScreen mainScreen].scale; + _headingIndicatorLayer.drawsAsynchronously = YES; + + [self.layer insertSublayer:_headingIndicatorLayer below:_dotBorderLayer]; + } + + // heading indicator accuracy mask (fan-shaped) + // + if ( ! _headingIndicatorMaskLayer && self.annotation.heading.headingAccuracy) + { + _headingIndicatorMaskLayer = [CAShapeLayer layer]; + _headingIndicatorMaskLayer.frame = _headingIndicatorLayer.bounds; + _headingIndicatorMaskLayer.path = [[self headingIndicatorClippingMask] CGPath]; + + // apply the mask to the halo-radius-sized gradient layer + _headingIndicatorLayer.mask = _headingIndicatorMaskLayer; + + _oldHeadingAccuracy = self.annotation.heading.headingAccuracy; + } + + // update accuracy ring (if zoom or horizontal accuracy have changed) + // + if (_accuracyRingLayer && (_oldZoom != self.mapView.zoomLevel || _oldHorizontalAccuracy != self.annotation.location.horizontalAccuracy)) + { + CGFloat accuracyRingSize = [self calculateAccuracyRingSize]; + + // only show the accuracy ring if it won't be obscured by the location dot + if (accuracyRingSize > MGLUserLocationAnnotationDotSize + 15) + { + _accuracyRingLayer.hidden = NO; + _accuracyRingLayer.bounds = CGRectMake(0, 0, accuracyRingSize, accuracyRingSize); + _accuracyRingLayer.cornerRadius = accuracyRingSize / 2; + + // match the halo to the accuracy ring + _haloLayer.bounds = _accuracyRingLayer.bounds; + _haloLayer.cornerRadius = _accuracyRingLayer.cornerRadius; + _haloLayer.shouldRasterize = NO; + } + else + { + _accuracyRingLayer.hidden = YES; + + _haloLayer.bounds = CGRectMake(0, 0, MGLUserLocationAnnotationHaloSize, MGLUserLocationAnnotationHaloSize); + _haloLayer.cornerRadius = MGLUserLocationAnnotationHaloSize / 2.0; + _haloLayer.shouldRasterize = YES; + _haloLayer.rasterizationScale = [UIScreen mainScreen].scale; + } + + // store accuracy and zoom so we're not redrawing unchanged location updates + _oldHorizontalAccuracy = self.annotation.location.horizontalAccuracy; + _oldZoom = self.mapView.zoomLevel; + } + + // accuracy ring (circular, tinted, mostly-transparent) + // + if ( ! _accuracyRingLayer && self.annotation.location.horizontalAccuracy) + { + CGFloat accuracyRingSize = [self calculateAccuracyRingSize]; + _accuracyRingLayer = [self circleLayerWithSize:accuracyRingSize]; + _accuracyRingLayer.backgroundColor = [_mapView.tintColor CGColor]; + _accuracyRingLayer.opacity = 0.1; + _accuracyRingLayer.shouldRasterize = NO; + _accuracyRingLayer.allowsGroupOpacity = NO; + + [self.layer addSublayer:_accuracyRingLayer]; + } + + // expanding sonar-like pulse (circular, tinted, fades out) + // + if ( ! _haloLayer) + { + _haloLayer = [self circleLayerWithSize:MGLUserLocationAnnotationHaloSize]; + _haloLayer.backgroundColor = [_mapView.tintColor CGColor]; + _haloLayer.allowsGroupOpacity = NO; + _haloLayer.zPosition = -0.1f; + + // set defaults for the animations + CAAnimationGroup *animationGroup = [self loopingAnimationGroupWithDuration:3.0]; + + // scale out radially with initial acceleration + CAKeyframeAnimation *boundsAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.xy"]; + boundsAnimation.values = @[@0, @0.35, @1]; + boundsAnimation.keyTimes = @[@0, @0.2, @1]; + + // go transparent as scaled out, start semi-opaque + CAKeyframeAnimation *opacityAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"]; + opacityAnimation.values = @[@0.4, @0.4, @0]; + opacityAnimation.keyTimes = @[@0, @0.2, @1]; + + animationGroup.animations = @[boundsAnimation, opacityAnimation]; + + [_haloLayer addAnimation:animationGroup forKey:@"animateTransformAndOpacity"]; + + [self.layer addSublayer:_haloLayer]; + } + + // background dot (white with black shadow) + // + if ( ! _dotBorderLayer) + { + _dotBorderLayer = [self circleLayerWithSize:MGLUserLocationAnnotationDotSize]; + _dotBorderLayer.backgroundColor = [[UIColor whiteColor] CGColor]; + _dotBorderLayer.shadowColor = [[UIColor blackColor] CGColor]; + _dotBorderLayer.shadowOpacity = 0.25; + + if (self.mapView.camera.pitch) + { + [self updateFaux3DEffect]; + } + else + { + _dotBorderLayer.shadowOffset = CGSizeMake(0, 0); + _dotBorderLayer.shadowRadius = 3; + } + + [self.layer addSublayer:_dotBorderLayer]; + } + + // inner dot (pulsing, tinted) + // + if ( ! _dotLayer) + { + _dotLayer = [self circleLayerWithSize:MGLUserLocationAnnotationDotSize * 0.75]; + _dotLayer.backgroundColor = [_mapView.tintColor CGColor]; + _dotLayer.shouldRasterize = NO; + + // set defaults for the animations + CAAnimationGroup *animationGroup = [self loopingAnimationGroupWithDuration:1.5]; + animationGroup.autoreverses = YES; + animationGroup.fillMode = kCAFillModeBoth; + + // scale the dot up and down + CABasicAnimation *pulseAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale.xy"]; + pulseAnimation.fromValue = @0.8; + pulseAnimation.toValue = @1; + + // fade opacity in and out, subtly + CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"]; + opacityAnimation.fromValue = @0.8; + opacityAnimation.toValue = @1; + + animationGroup.animations = @[pulseAnimation, opacityAnimation]; + + [_dotLayer addAnimation:animationGroup forKey:@"animateTransformAndOpacity"]; + + [self.layer addSublayer:_dotLayer]; + } + + if (_puckModeActivated) + { + _puckModeActivated = NO; + + [self updateFaux3DEffect]; + } +} + +- (CALayer *)circleLayerWithSize:(CGFloat)layerSize +{ + CALayer *circleLayer = [CALayer layer]; + circleLayer.bounds = CGRectMake(0, 0, layerSize, layerSize); + circleLayer.position = CGPointMake(super.bounds.size.width / 2.0, super.bounds.size.height / 2.0); + circleLayer.cornerRadius = layerSize / 2.0; + circleLayer.shouldRasterize = YES; + circleLayer.rasterizationScale = [UIScreen mainScreen].scale; + circleLayer.drawsAsynchronously = YES; + + return circleLayer; +} + +- (CAAnimationGroup *)loopingAnimationGroupWithDuration:(CGFloat)animationDuration +{ + CAAnimationGroup *animationGroup = [CAAnimationGroup animation]; + animationGroup.duration = animationDuration; + animationGroup.repeatCount = INFINITY; + animationGroup.removedOnCompletion = NO; + animationGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]; + + return animationGroup; +} + +- (CGFloat)calculateAccuracyRingSize { CGFloat latRadians = self.annotation.coordinate.latitude * M_PI / 180.0f; CGFloat pixelRadius = self.annotation.location.horizontalAccuracy / cos(latRadians) / [self.mapView metersPerPixelAtLatitude:self.annotation.coordinate.latitude]; - UIGraphicsBeginImageContextWithOptions(CGSizeMake(pixelRadius * 2, pixelRadius * 2), NO, [[UIScreen mainScreen] scale]); - - CGContextSetStrokeColorWithColor(UIGraphicsGetCurrentContext(), [[UIColor colorWithRed:0.378 green:0.552 blue:0.827 alpha:0.7] CGColor]); - CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), [[UIColor colorWithRed:0.378 green:0.552 blue:0.827 alpha:0.15] CGColor]); - CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 2.0); - CGContextStrokeEllipseInRect(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, pixelRadius * 2, pixelRadius * 2)); - - UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - return finalImage; + + return pixelRadius * 2; } -- (UIImage *)trackingDotHaloImage +- (UIImage *)headingIndicatorTintedGradientImage { - UIGraphicsBeginImageContextWithOptions(CGSizeMake(100, 100), NO, [[UIScreen mainScreen] scale]); - CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), [[_mapView.tintColor colorWithAlphaComponent:0.75] CGColor]); - CGContextFillEllipseInRect(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, 100, 100)); - UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - - return finalImage; -} + UIImage *image; -- (UIImage *)dotImage -{ - CGFloat tintedWidth = MGLTrackingDotRingWidth * 0.7; - - CGRect rect = CGRectMake(0, 0, tintedWidth, tintedWidth); - - UIGraphicsBeginImageContextWithOptions(rect.size, NO, [[UIScreen mainScreen] scale]); + CGFloat haloRadius = MGLUserLocationAnnotationHaloSize / 2.0; + + UIGraphicsBeginImageContextWithOptions(CGSizeMake(MGLUserLocationAnnotationHaloSize, haloRadius), NO, 0); + + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef context = UIGraphicsGetCurrentContext(); - - CGContextSetFillColorWithColor(context, [_mapView.tintColor CGColor]); - CGContextFillEllipseInRect(context, CGRectMake((rect.size.width - tintedWidth) / 2.0, (rect.size.height - tintedWidth) / 2.0, tintedWidth, tintedWidth)); - - UIImage *tintedForeground = UIGraphicsGetImageFromCurrentImageContext(); - + + // gradient from the tint color to no-alpha tint color + CGFloat gradientLocations[] = {0.0, 1.0}; + CGGradientRef gradient = CGGradientCreateWithColors( + colorSpace, (__bridge CFArrayRef)@[(id)[_mapView.tintColor CGColor], + (id)[[_mapView.tintColor colorWithAlphaComponent:0] CGColor]], gradientLocations); + + // draw the gradient from the center point to the edge (full halo radius) + CGPoint centerPoint = CGPointMake(haloRadius, haloRadius); + CGContextDrawRadialGradient(context, gradient, + centerPoint, 0.0, + centerPoint, haloRadius, + nil); + + image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); - - return tintedForeground; + + CGGradientRelease(gradient); + CGColorSpaceRelease(colorSpace); + + return image; +} + +- (UIBezierPath *)headingIndicatorClippingMask +{ + CGFloat accuracy = self.annotation.heading.headingAccuracy; + + // size the mask using exagerated accuracy, but keep within a good display range + CGFloat clippingDegrees = 90 - (accuracy * 1.5); + clippingDegrees = fmin(clippingDegrees, 55); + clippingDegrees = fmax(clippingDegrees, 10); + + CGRect ovalRect = CGRectMake(0, 0, MGLUserLocationAnnotationHaloSize, MGLUserLocationAnnotationHaloSize); + UIBezierPath *ovalPath = UIBezierPath.bezierPath; + + // clip the oval to ± incoming accuracy degrees (converted to radians), from the top + [ovalPath addArcWithCenter:CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect)) + radius:CGRectGetWidth(ovalRect) / 2.0 + startAngle:(-180 + clippingDegrees) * M_PI / 180 + endAngle:-clippingDegrees * M_PI / 180 + clockwise:YES]; + + [ovalPath addLineToPoint:CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect))]; + [ovalPath closePath]; + + return ovalPath; } @end diff --git a/platform/ios/NSBundle+MGLAdditions.m b/platform/ios/NSBundle+MGLAdditions.m index b0ecf4656..d8ba7dacc 100644 --- a/platform/ios/NSBundle+MGLAdditions.m +++ b/platform/ios/NSBundle+MGLAdditions.m @@ -6,7 +6,7 @@ + (NSString *)mgl_resourceBundlePath { - NSString *resourceBundlePath = [[NSBundle bundleForClass:[MGLMapView class]] pathForResource:@"MapboxGL" ofType:@"bundle"]; + NSString *resourceBundlePath = [[NSBundle bundleForClass:[MGLMapView class]] pathForResource:@"Mapbox" ofType:@"bundle"]; if ( ! resourceBundlePath) resourceBundlePath = [[NSBundle mainBundle] bundlePath]; diff --git a/platform/ios/resources/Compass@2x.png b/platform/ios/resources/Compass@2x.png index 58e7e08d2..2bd8a286d 100644 Binary files a/platform/ios/resources/Compass@2x.png and b/platform/ios/resources/Compass@2x.png differ diff --git a/platform/ios/resources/Compass@3x.png b/platform/ios/resources/Compass@3x.png index 470cd3d08..9308a46a5 100644 Binary files a/platform/ios/resources/Compass@3x.png and b/platform/ios/resources/Compass@3x.png differ diff --git a/platform/ios/resources/README b/platform/ios/resources/README new file mode 100644 index 000000000..34dedaeb0 --- /dev/null +++ b/platform/ios/resources/README @@ -0,0 +1,5 @@ +When adding new image resources, make sure that you've reduced the file size as much as possible using a tool such as: + +ImageOptim — https://imageoptim.com + +See also: https://github.com/mapbox/mapbox-gl-native/pull/2227 \ No newline at end of file diff --git a/platform/ios/resources/mapbox.png b/platform/ios/resources/mapbox.png index 5a9da3fe3..533ad9f72 100644 Binary files a/platform/ios/resources/mapbox.png and b/platform/ios/resources/mapbox.png differ diff --git a/platform/ios/resources/mapbox@2x.png b/platform/ios/resources/mapbox@2x.png index 194aa64da..51ad74adc 100644 Binary files a/platform/ios/resources/mapbox@2x.png and b/platform/ios/resources/mapbox@2x.png differ diff --git a/platform/ios/resources/mapbox@3x.png b/platform/ios/resources/mapbox@3x.png index d1260a16f..1fb83c149 100644 Binary files a/platform/ios/resources/mapbox@3x.png and b/platform/ios/resources/mapbox@3x.png differ diff --git a/platform/node/CHANGELOG.md b/platform/node/CHANGELOG.md new file mode 100644 index 000000000..d4b0abd49 --- /dev/null +++ b/platform/node/CHANGELOG.md @@ -0,0 +1,69 @@ +# 2.0.0 + +- Integrates Node.js bindings into core mapbox-gl-native project. ([#2179](https://github.com/mapbox/mapbox-gl-native/pull/2179)) +- Requires an options object argument to `new mbgl.Map()` + (with required `request` and optional `cancel` methods), + drops `mbgl.FileSource`. ([mapbox/node-mapbox-gl-native#143](https://github.com/mapbox/node-mapbox-gl-native/pull/143)) +- Changes `request` semantics to pass a second, callback argument instead + of needing to call `req.respond`. ([#2299](https://github.com/mapbox/mapbox-gl-native/pull/2299)) +- Requires numerical `ratio` in `mbgl.Map` options argument. + Map pixel ratio is now immutable and can no longer be set with + render options. ([`a8d9b92`](https://github.com/mapbox/mapbox-gl-native/commit/a8d9b921d71a91d7f8eff82e5a584aaab8b7d1c6), [#1799](https://github.com/mapbox/mapbox-gl-native/pull/1799)) +- `map.render` now returns a raw image buffer instead of an object with + `width`, `height` and `pixels` properties. ([#2262](https://github.com/mapbox/mapbox-gl-native/pull/2262)) +- Adds support for rendering v8 styles. +- No longer loads resources before a render request is made. ([`55d25a8`](https://github.com/mapbox/mapbox-gl-native/commit/55d25a80a77c06ef5e66acc0d8518867b03fe8a4)) +- Adds io.js v3.x support. ([#2261](https://github.com/mapbox/mapbox-gl-native/pull/2261)) + +# 1.1.3 + +- Removes deprecated mbgl::Environment from NodeLogObserver. + +# 1.1.2 + +- Check libuv version semver-ishly, fixes segfaults in Node.js 0.12.x + and iojs. +- Fixes segfault, throws JavaScript error instead when attempting to + render without first loading a style. +- Bumps mbgl submodule to v0.4.0 + +# 1.1.1 + +- Bumps mbgl submodule to v0.3.5 + +# 1.1.0 + +- Adds Node.js v0.12.x and io.js support. +- Adds `map.release()` method for manual cleanup of map resources. +- Fixes garbage collection of NodeMap objects. +- Returns an error callback for failed NodeFileSource requests. +- Fixes handling of corrupt NodeFileSource request data. +- Implements request coalescing to fix NodeRequest cancellation. +- Removes `setAccessToken` method from NodeMap, `mapbox://` URLs + should be handled through `NodeFileSource` now. +- Updates build scripts and Travis CI configuration. +- Logs Environment ID and thread name when in an Environment scope. +- Refactors NodeLog to implement mbgl::Log::Observer. +- Fixes uncaught exception from missing sprites. +- Fixes Unicode glyph range end. + +# 1.0.3 + +- Fixes crash during garbage collection by assigning FileSource handle + to a v8::Persistent in NodeMap constructor. + +# 1.0.2 + +- Initialize shared display connection at module load time to avoid + race condition when display connection is initialized on-demand. + +# 1.0.1 + +- Adapts NodeFileSource around mbgl::Environment additions. +- Adapts to minor changes in mapbox-gl-test-suite. +- Adds tests for gzipped vector tile handling. +- Cleans up documentation. + +# 1.0.0 + +- Initial release. diff --git a/platform/node/README.md b/platform/node/README.md new file mode 100644 index 000000000..dfe5d909f --- /dev/null +++ b/platform/node/README.md @@ -0,0 +1,209 @@ +# node-mapbox-gl-native + +[![NPM](https://nodei.co/npm/mapbox-gl-native.png)](https://npmjs.org/package/mapbox-gl-native) + +## Installing + +Requires a modern C++ runtime that supports C++14. + +By default, installs binaries. On these platforms no additional dependencies are needed. + +- 64 bit OS X or 64 bit Linux +- Node.js v0.10.x, Node.js v0.12.x, io.js v2.x + +Just run: + +``` +npm install mapbox-gl-native +``` + +Other platforms will fall back to a source compile with `make node`. To compile this module, make sure all submodules are initialized with `git submodule update --init` and install the [external dependencies required to build from source](https://github.com/mapbox/mapbox-gl-native/blob/master/INSTALL.md#depends). + +## Rendering a map tile + +```js +var map = new mbgl.Map({ request: function() {} }); +map.load(require('./test/fixtures/style.json')); +map.render({}, function(err, image) { + if (err) throw err; + fs.writeFileSync('image.png', image); +}); +``` + +The first argument passed to `map.render` is an options object, all keys are optional: + +```js +{ + zoom: {zoom}, // number, defaults to 0 + width: {width}, // number (px), defaults to 512 + height: {height}, // number (px), defaults to 512 + center: [{latitude}, {longitude}], // array of numbers (coordinates), defaults to [0,0] + bearing: {bearing}, // number (in degrees, counter-clockwise from north), defaults to 0 + classes: {classes} // array of strings +} +``` + +When you are finished using a map object, you can call `map.release()` to dispose the internal map resources manually. This is not necessary, but can be helpful to optimize resource usage (memory, file sockets) on a more granualar level than v8's garbage collector. + +## Testing + +``` +npm test +``` + +## Implementing a file source + +When creating a `Map`, you must pass an options object (with a required `ratio`, required `request` and optional `cancel` method) as the first parameter. + +```js +var map = new mbgl.Map({ + request: function(req) { + // TODO + }, + cancel: function(req) { + // TODO + }, + ratio: 1.0 +}); +``` + +The `request()` method starts a new request to a file, while `cancel()` tells the FileSource to cancel the request (if possible). The `ratio` sets the scale at which the map will render tiles, such as `2.0` for rendering images for high pixel density displays. The `req` parameter has two properties: + +```json +{ + "url": "http://example.com", + "kind": 1 +} +``` + +The `kind` is an enum and defined in [`mbgl.Resource`](https://github.com/mapbox/mapbox-gl-native/blob/node/include/mbgl/storage/resource.hpp): + +```json +{ + "Unknown": 0, + "Style": 1, + "Source": 2, + "Tile": 3, + "Glyphs": 4, + "SpriteImage": 5, + "SpriteJSON": 6 +} +``` + +It has no significance for anything but serves as a hint to your implemention as to what sort of resource to expect. E.g., your implementation could choose caching strategies based on the expected file type. + +The `request` implementation should pass uncompressed data to `callback`. If you are downloading assets from a source that applies gzip transport encoding, the implementation must decompress the results before passing them on. + +A sample implementation that reads files from disk would look like the following: + +```js +var map = new mbgl.Map({ + request: function(req, callback) { + fs.readFile(path.join('base/path', req.url), function(err, data) { + callback(err, { data: data }); + }); + }, + ratio: 1.0 +}); +``` + +This is a very barebones implementation and you'll probably want a better implementation. E.g. it passes the url verbatim to the file system, but you'd want add some logic that normalizes `http` URLs. You'll notice that once your implementation has obtained the requested file, you have to deliver it to the requestee by calling `callback()`, which takes either an error object or `null` and an object with several settings: + +```js +{ + modified: new Date(), + expires: new Date(), + etag: "string", + data: new Buffer() +}; +``` + +A sample implementation that uses [`request`](https://github.com/request/request) to query data from HTTP: + +```js +var mbgl = require('mapbox-gl-native'); +var request = require('request'); + +var map = new mbgl.Map({ + request: function(req, callback) { + request({ + url: req.url, + encoding: null, + gzip: true + }, function (err, res, body) { + if (err) { + callback(err); + } else if (res.statusCode == 200) { + var response = {}; + + if (res.headers.modified) { response.modified = new Date(res.headers.modified); } + if (res.headers.expires) { response.expires = new Date(res.headers.expires); } + if (res.headers.etag) { response.etag = res.headers.etag; } + + response.data = body; + + callback(null, response); + } else { + callback(new Error(JSON.parse(body).message)); + } + }); + }, + ratio: 1.0 +}); +``` + +Mapbox GL uses two types of protocols: `asset://` for files that should be loaded from some local static system, and `http://` (and `https://`), which should be loaded from the internet. However, stylesheets are free to use other protocols too, if your implementation of `request` supports these; e.g. you could use `s3://` to indicate that files are supposed to be loaded from S3. + +## Mapbox API Access tokens + +To use styles that rely on Mapbox vector tiles, you must pass an [API access token](https://www.mapbox.com/developers/api/#access-tokens) in your `request` implementation with requests to `mapbox://` protocols. + +```js +var mbgl = require('mapbox-gl-native'); +var request = require('request'); +var url = require('url'); + +var map = new mbgl.Map({ + request: function(req, callback) { + var opts = { + url: req.url, + encoding: null, + gzip: true + }; + + if (url.parse(req.url).protocol === 'mapbox:') { + opts.qs = { access_token: process.env.MAPBOX_ACCESS_TOKEN}; + } + + request(opts, function (err, res, body) { + if (err) { + callback(err); + } else if (res.statusCode == 200) { + var response = {}; + + if (res.headers.modified) { response.modified = new Date(res.headers.modified); } + if (res.headers.expires) { response.expires = new Date(res.headers.expires); } + if (res.headers.etag) { response.etag = res.headers.etag; } + + response.data = body; + + callback(null, response); + } else { + callback(new Error(JSON.parse(body).message)); + } + }); + }, + ratio: 1.0 +}); + +// includes a datasource with a reference to something like `mapbox://mapbox.mapbox-streets-v6` +var style = mapboxStyle; + +map.load(style); +map.render({}, function(err, image) { + if (err) throw err; + fs.writeFileSync('image.png', image); +}); + +``` + diff --git a/platform/node/src/node_file_source.cpp b/platform/node/src/node_file_source.cpp new file mode 100644 index 000000000..b4828e768 --- /dev/null +++ b/platform/node/src/node_file_source.cpp @@ -0,0 +1,139 @@ +#include "node_file_source.hpp" +#include "node_request.hpp" +#include "util/async_queue.hpp" + +#include + +namespace node_mbgl { + +struct NodeFileSource::Action { + const enum : bool { Add, Cancel } type; + mbgl::Resource const resource; +}; + +NodeFileSource::NodeFileSource(v8::Local options_) : + queue(new Queue(uv_default_loop(), [this](Action &action) { + if (action.type == Action::Add) { + processAdd(action.resource); + } else if (action.type == Action::Cancel) { + processCancel(action.resource); + } + })) +{ + options.Reset(options_); + + // Make sure that the queue doesn't block the loop from exiting. + queue->unref(); +} + +NodeFileSource::~NodeFileSource() { + queue->stop(); + queue = nullptr; + + options.Reset(); +} + +mbgl::Request* NodeFileSource::request(const mbgl::Resource& resource, uv_loop_t* loop, Callback callback) { + auto req = new mbgl::Request(resource, loop, std::move(callback)); + + std::lock_guard lock(observersMutex); + + assert(observers.find(resource) == observers.end()); + observers[resource] = req; + + // This function can be called from any thread. Make sure we're executing the actual call in the + // file source loop by sending it over the queue. It will be processed in processAction(). + queue->send(Action{ Action::Add, resource }); + + return req; +} + +void NodeFileSource::cancel(mbgl::Request* req) { + req->cancel(); + + std::lock_guard lock(observersMutex); + + auto it = observers.find(req->resource); + if (it == observers.end()) { + return; + } + + observers.erase(it); + + // This function can be called from any thread. Make sure we're executing the actual call in the + // file source loop by sending it over the queue. It will be processed in processAction(). + queue->send(Action{ Action::Cancel, req->resource }); + + req->destruct(); +} + +void NodeFileSource::processAdd(const mbgl::Resource& resource) { + Nan::HandleScope scope; + + // Make sure the loop stays alive as long as request is pending. + if (pending.empty()) { + queue->ref(); + } + + auto requestHandle = NodeRequest::Create(this, resource)->ToObject(); + pending.emplace(resource, requestHandle); + + auto callback = Nan::GetFunction(Nan::New(NodeRequest::Respond, requestHandle)).ToLocalChecked(); + callback->SetName(Nan::New("respond").ToLocalChecked()); + + v8::Local argv[] = { requestHandle, callback }; + Nan::MakeCallback(Nan::New(options), "request", 2, argv); +} + +void NodeFileSource::processCancel(const mbgl::Resource& resource) { + Nan::HandleScope scope; + + auto it = pending.find(resource); + if (it == pending.end()) { + // The response callback was already fired. There is no point in calling the cancelation + // callback because the request is already completed. + } else { + v8::Local requestHandle = Nan::New(it->second); + it->second.Reset(); + pending.erase(it); + + // Make sure the the loop can exit when there are no pending requests. + if (pending.empty()) { + queue->unref(); + } + + if (Nan::Has(Nan::New(options), Nan::New("cancel").ToLocalChecked()).FromJust()) { + v8::Local argv[] = { requestHandle }; + Nan::MakeCallback(Nan::New(options), "cancel", 1, argv); + } + + // Set the request handle in the request wrapper handle to null + Nan::ObjectWrap::Unwrap(requestHandle)->cancel(); + } +} + +void NodeFileSource::notify(const mbgl::Resource& resource, const std::shared_ptr& response) { + // First, remove the request, since it might be destructed at any point now. + auto it = pending.find(resource); + if (it != pending.end()) { + it->second.Reset(); + pending.erase(it); + + // Make sure the the loop can exit when there are no pending requests. + if (pending.empty()) { + queue->unref(); + } + } + + std::lock_guard lock(observersMutex); + + auto observersIt = observers.find(resource); + if (observersIt == observers.end()) { + return; + } + + observersIt->second->notify(response); + observers.erase(observersIt); +} + +} diff --git a/platform/node/src/node_file_source.hpp b/platform/node/src/node_file_source.hpp new file mode 100644 index 000000000..038765cb2 --- /dev/null +++ b/platform/node/src/node_file_source.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wshadow" +#include +#pragma GCC diagnostic pop + +#include +#include +#include + +namespace node_mbgl { + +namespace util { template class AsyncQueue; } + +class NodeFileSource : public mbgl::FileSource { +public: + NodeFileSource(v8::Local); + ~NodeFileSource(); + + mbgl::Request* request(const mbgl::Resource&, uv_loop_t*, Callback); + void cancel(mbgl::Request*); + + // visiblity? + void notify(const mbgl::Resource&, const std::shared_ptr&); + +private: + struct Action; + using Queue = util::AsyncQueue; + + void processAdd(const mbgl::Resource&); + void processCancel(const mbgl::Resource&); + + Nan::Persistent options; + +private: + std::unordered_map, mbgl::Resource::Hash> pending; + + // The observers list will hold pointers to all the requests waiting + // for a particular resource. The access must be guarded by a mutex + // because the list is also accessed by a thread from the mbgl::Map + // object and from the main thread when notifying requests of + // completion. Concurrent access is specially needed when + // canceling a request to avoid a deadlock (see #129). + std::unordered_map observers; + std::mutex observersMutex; + + Queue *queue = nullptr; +}; + +} diff --git a/platform/node/src/node_log.cpp b/platform/node/src/node_log.cpp new file mode 100644 index 000000000..8230bb36c --- /dev/null +++ b/platform/node/src/node_log.cpp @@ -0,0 +1,63 @@ +#include "node_log.hpp" +#include "util/async_queue.hpp" + +namespace node_mbgl { + +struct NodeLogObserver::LogMessage { + mbgl::EventSeverity severity; + mbgl::Event event; + int64_t code; + std::string text; + + LogMessage(mbgl::EventSeverity severity_, mbgl::Event event_, int64_t code_, std::string text_) + : severity(severity_), + event(event_), + code(code_), + text(text_) {} +}; + +NodeLogObserver::NodeLogObserver(v8::Local target) + : queue(new Queue(uv_default_loop(), [this](LogMessage &message) { + Nan::HandleScope scope; + + auto msg = Nan::New(); + + Nan::Set(msg, Nan::New("class").ToLocalChecked(), + Nan::New(mbgl::EventClass(message.event).c_str()).ToLocalChecked()); + + Nan::Set(msg, Nan::New("severity").ToLocalChecked(), + Nan::New(mbgl::EventSeverityClass(message.severity).c_str()).ToLocalChecked()); + + if (message.code != -1) { + Nan::Set(msg, Nan::New("code").ToLocalChecked(), + Nan::New(message.code)); + } + + if (!message.text.empty()) { + Nan::Set(msg, Nan::New("text").ToLocalChecked(), + Nan::New(message.text).ToLocalChecked()); + } + + v8::Local argv[] = { Nan::New("message").ToLocalChecked(), msg }; + auto handle = Nan::New(module); + auto emit = Nan::Get(handle, Nan::New("emit").ToLocalChecked()).ToLocalChecked()->ToObject(); + Nan::CallAsFunction(emit, handle, 2, argv); + })) { + Nan::HandleScope scope; + module.Reset(target); + + // Don't keep the event loop alive. + queue->unref(); +} + +NodeLogObserver::~NodeLogObserver() { + queue->stop(); + module.Reset(); +} + +bool NodeLogObserver::onRecord(mbgl::EventSeverity severity, mbgl::Event event, int64_t code, const std::string &text) { + queue->send({ severity, event, code, text }); + return true; +} + +} diff --git a/platform/node/src/node_log.hpp b/platform/node/src/node_log.hpp new file mode 100644 index 000000000..d29e4e28e --- /dev/null +++ b/platform/node/src/node_log.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wshadow" +#include +#pragma GCC diagnostic pop + +namespace node_mbgl { + +namespace util { template class AsyncQueue; } + +class NodeLogObserver : public mbgl::Log::Observer { +public: + NodeLogObserver(v8::Local target); + ~NodeLogObserver(); + + // Log::Observer implementation + virtual bool onRecord(mbgl::EventSeverity severity, mbgl::Event event, int64_t code, const std::string &msg) override; + +private: + Nan::Persistent module; + + struct LogMessage; + using Queue = util::AsyncQueue; + Queue *queue = nullptr; +}; + +} diff --git a/platform/node/src/node_map.cpp b/platform/node/src/node_map.cpp new file mode 100644 index 000000000..5b2a83916 --- /dev/null +++ b/platform/node/src/node_map.cpp @@ -0,0 +1,350 @@ +#include "node_map.hpp" + +#include +#include +#include + +#include + +namespace node_mbgl { + +struct NodeMap::RenderOptions { + double zoom = 0; + double bearing = 0; + double latitude = 0; + double longitude = 0; + unsigned int width = 512; + unsigned int height = 512; + std::vector classes; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////// +// Static Node Methods + +Nan::Persistent NodeMap::constructor; + +static std::shared_ptr sharedDisplay() { + static auto display = std::make_shared(); + return display; +} + +const static char* releasedMessage() { + return "Map resources have already been released"; +} + +NAN_MODULE_INIT(NodeMap::Init) { + v8::Local tpl = Nan::New(New); + + tpl->InstanceTemplate()->SetInternalFieldCount(1); + tpl->SetClassName(Nan::New("Map").ToLocalChecked()); + + Nan::SetPrototypeMethod(tpl, "load", Load); + Nan::SetPrototypeMethod(tpl, "render", Render); + Nan::SetPrototypeMethod(tpl, "release", Release); + + constructor.Reset(tpl->GetFunction()); + Nan::Set(target, Nan::New("Map").ToLocalChecked(), tpl->GetFunction()); + + // Initialize display connection on module load. + sharedDisplay(); +} + +NAN_METHOD(NodeMap::New) { + if (!info.IsConstructCall()) { + return Nan::ThrowTypeError("Use the new operator to create new Map objects"); + } + + if (info.Length() < 1 || !info[0]->IsObject()) { + return Nan::ThrowTypeError("Requires an options object as first argument"); + } + + auto options = info[0]->ToObject(); + + // Check that 'request', 'cancel' and 'ratio' are defined. + if (!Nan::Has(options, Nan::New("request").ToLocalChecked()).FromJust() + || !Nan::Get(options, Nan::New("request").ToLocalChecked()).ToLocalChecked()->IsFunction()) { + return Nan::ThrowError("Options object must have a 'request' method"); + } + + if ( Nan::Has(options, Nan::New("cancel").ToLocalChecked()).FromJust() + && !Nan::Get(options, Nan::New("cancel").ToLocalChecked()).ToLocalChecked()->IsFunction()) { + return Nan::ThrowError("Options object 'cancel' property must be a function"); + } + + if (!Nan::Has(options, Nan::New("ratio").ToLocalChecked()).FromJust() + || !Nan::Get(options, Nan::New("ratio").ToLocalChecked()).ToLocalChecked()->IsNumber()) { + return Nan::ThrowError("Options object must have a numerical 'ratio' property"); + } + + try { + auto nodeMap = new NodeMap(options); + nodeMap->Wrap(info.This()); + } catch(std::exception &ex) { + return Nan::ThrowError(ex.what()); + } + + info.GetReturnValue().Set(info.This()); +} + +const std::string StringifyStyle(v8::Local styleHandle) { + Nan::HandleScope scope; + + v8::Local JSON = Nan::Get( + Nan::GetCurrentContext()->Global(), + Nan::New("JSON").ToLocalChecked()).ToLocalChecked()->ToObject(); + + return *Nan::Utf8String(Nan::MakeCallback(JSON, "stringify", 1, &styleHandle)); +} + +NAN_METHOD(NodeMap::Load) { + auto nodeMap = Nan::ObjectWrap::Unwrap(info.Holder()); + + if (!nodeMap->isValid()) return Nan::ThrowError(releasedMessage()); + + // Reset the flag as this could be the second time + // we are calling this (being the previous successful). + nodeMap->loaded = false; + + if (info.Length() < 1) { + return Nan::ThrowError("Requires a map style as first argument"); + } + + std::string style; + + if (info[0]->IsObject()) { + style = StringifyStyle(info[0]); + } else if (info[0]->IsString()) { + style = *Nan::Utf8String(info[0]); + } else { + return Nan::ThrowTypeError("First argument must be a string or object"); + } + + try { + nodeMap->map->setStyleJSON(style, "."); + } catch (const std::exception &ex) { + return Nan::ThrowError(ex.what()); + } + + nodeMap->loaded = true; + + info.GetReturnValue().SetUndefined(); +} + +std::unique_ptr NodeMap::ParseOptions(v8::Local obj) { + Nan::HandleScope scope; + + auto options = std::make_unique(); + + if (Nan::Has(obj, Nan::New("zoom").ToLocalChecked()).FromJust()) { + options->zoom = Nan::Get(obj, Nan::New("zoom").ToLocalChecked()).ToLocalChecked()->NumberValue(); + } + + if (Nan::Has(obj, Nan::New("bearing").ToLocalChecked()).FromJust()) { + options->bearing = Nan::Get(obj, Nan::New("bearing").ToLocalChecked()).ToLocalChecked()->NumberValue(); + } + + if (Nan::Has(obj, Nan::New("center").ToLocalChecked()).FromJust()) { + auto center = Nan::Get(obj, Nan::New("center").ToLocalChecked()).ToLocalChecked().As(); + if (center->Length() > 0) { options->latitude = Nan::Get(center, 0).ToLocalChecked()->NumberValue(); } + if (center->Length() > 1) { options->longitude = Nan::Get(center, 1).ToLocalChecked()->NumberValue(); } + } + + if (Nan::Has(obj, Nan::New("width").ToLocalChecked()).FromJust()) { + options->width = Nan::Get(obj, Nan::New("width").ToLocalChecked()).ToLocalChecked()->IntegerValue(); + } + + if (Nan::Has(obj, Nan::New("height").ToLocalChecked()).FromJust()) { + options->height = Nan::Get(obj, Nan::New("height").ToLocalChecked()).ToLocalChecked()->IntegerValue(); + } + + if (Nan::Has(obj, Nan::New("classes").ToLocalChecked()).FromJust()) { + auto classes = Nan::Get(obj, Nan::New("classes").ToLocalChecked()).ToLocalChecked()->ToObject().As(); + const int length = classes->Length(); + options->classes.reserve(length); + for (int i = 0; i < length; i++) { + options->classes.push_back(std::string { *Nan::Utf8String(Nan::Get(classes, i).ToLocalChecked()->ToString()) }); + } + } + + return options; +} + +NAN_METHOD(NodeMap::Render) { + auto nodeMap = Nan::ObjectWrap::Unwrap(info.Holder()); + + if (!nodeMap->isValid()) return Nan::ThrowError(releasedMessage()); + + if (info.Length() <= 0 || !info[0]->IsObject()) { + return Nan::ThrowTypeError("First argument must be an options object"); + } + + if (info.Length() <= 1 || !info[1]->IsFunction()) { + return Nan::ThrowTypeError("Second argument must be a callback function"); + } + + if (!nodeMap->isLoaded()) { + return Nan::ThrowTypeError("Style is not loaded"); + } + + auto options = ParseOptions(info[0]->ToObject()); + + assert(!nodeMap->callback); + assert(!nodeMap->image); + nodeMap->callback = std::make_unique(info[1].As()); + + try { + nodeMap->startRender(std::move(options)); + } catch (mbgl::util::Exception &ex) { + return Nan::ThrowError(ex.what()); + } + + info.GetReturnValue().SetUndefined(); +} + +void NodeMap::startRender(std::unique_ptr options) { + view.resize(options->width, options->height); + map->update(mbgl::Update::Dimensions); + map->setClasses(options->classes); + map->setLatLngZoom(mbgl::LatLng(options->latitude, options->longitude), options->zoom); + map->setBearing(options->bearing); + + map->renderStill([this](const std::exception_ptr eptr, std::unique_ptr result) { + if (eptr) { + error = std::move(eptr); + uv_async_send(async); + } else { + assert(!image); + image = std::move(result); + uv_async_send(async); + } + }); + + // Retain this object, otherwise it might get destructed before we are finished rendering the + // still image. + Ref(); + + // Similarly, we're now waiting for the async to be called, so we need to make sure that it + // keeps the loop alive. + uv_ref(reinterpret_cast(async)); +} + +void NodeMap::renderFinished() { + Nan::HandleScope scope; + + // We're done with this render call, so we're unrefing so that the loop could close. + uv_unref(reinterpret_cast(async)); + + // There is no render pending anymore, we the GC could now delete this object if it went out + // of scope. + Unref(); + + // Move the callback and image out of the way so that the callback can start a new render call. + auto cb = std::move(callback); + auto img = std::move(image); + assert(cb); + + // These have to be empty to be prepared for the next render call. + assert(!callback); + assert(!image); + + if (error) { + std::string errorMessage; + + try { + std::rethrow_exception(error); + } catch (const std::exception& ex) { + errorMessage = ex.what(); + } + + v8::Local argv[] = { + Nan::Error(errorMessage.c_str()) + }; + + // This must be empty to be prepared for the next render call. + error = nullptr; + assert(!error); + + cb->Call(1, argv); + } else if (img) { + v8::Local pixels = Nan::NewBuffer( + reinterpret_cast(img->pixels.get()), + size_t(img->width) * size_t(img->height) * sizeof(mbgl::StillImage::Pixel), + + // Retain the StillImage object until the buffer is deleted. + [](char *, void *hint) { + delete reinterpret_cast(hint); + }, + const_cast(img.get()) + ).ToLocalChecked(); + img.release(); + + v8::Local argv[] = { + Nan::Null(), + pixels + }; + cb->Call(2, argv); + } else { + v8::Local argv[] = { + Nan::Error("Didn't get an image") + }; + cb->Call(1, argv); + } +} + +NAN_METHOD(NodeMap::Release) { + auto nodeMap = Nan::ObjectWrap::Unwrap(info.Holder()); + + if (!nodeMap->isValid()) return Nan::ThrowError(releasedMessage()); + + try { + nodeMap->release(); + } catch (const std::exception &ex) { + return Nan::ThrowError(ex.what()); + } + + info.GetReturnValue().SetUndefined(); +} + +void NodeMap::release() { + if (!isValid()) throw mbgl::util::Exception(releasedMessage()); + + valid = false; + + uv_close(reinterpret_cast(async), [] (uv_handle_t *handle) { + delete reinterpret_cast(handle); + }); + + map.reset(nullptr); +} + + +//////////////////////////////////////////////////////////////////////////////////////////////// +// Instance + +NodeMap::NodeMap(v8::Local options) : + view(sharedDisplay(), [&] { + Nan::HandleScope scope; + return Nan::Get(options, Nan::New("ratio").ToLocalChecked()).ToLocalChecked()->NumberValue(); + }()), + fs(options), + map(std::make_unique(view, fs, mbgl::MapMode::Still)), + async(new uv_async_t) { + + async->data = this; +#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 + uv_async_init(uv_default_loop(), async, [](uv_async_t *as, int) { +#else + uv_async_init(uv_default_loop(), async, [](uv_async_t *as) { +#endif + reinterpret_cast(as->data)->renderFinished(); + }); + + // Make sure the async handle doesn't keep the loop alive. + uv_unref(reinterpret_cast(async)); +} + +NodeMap::~NodeMap() { + if (valid) release(); +} + +} diff --git a/platform/node/src/node_map.hpp b/platform/node/src/node_map.hpp new file mode 100644 index 000000000..90edf44f7 --- /dev/null +++ b/platform/node/src/node_map.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include "node_file_source.hpp" + +#include +#include + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wshadow" +#include +#pragma GCC diagnostic pop + +#include + +namespace node_mbgl { + +class NodeMap : public Nan::ObjectWrap { +public: + struct RenderOptions; + class RenderWorker; + + static NAN_MODULE_INIT(Init); + + static NAN_METHOD(New); + static NAN_METHOD(Load); + static NAN_METHOD(Render); + static NAN_METHOD(Release); + + void startRender(std::unique_ptr options); + void renderFinished(); + + void release(); + + inline bool isLoaded() { return loaded; } + inline bool isValid() { return valid; } + + static std::unique_ptr ParseOptions(v8::Local); + static Nan::Persistent constructor; + +private: + NodeMap(v8::Local); + ~NodeMap(); + + mbgl::HeadlessView view; + NodeFileSource fs; + std::unique_ptr map; + + std::exception_ptr error; + std::unique_ptr image; + std::unique_ptr callback; + + // Async for delivering the notifications of render completion. + uv_async_t *async; + + bool loaded = false; + bool valid = true; +}; + +} diff --git a/platform/node/src/node_mapbox_gl_native.cpp b/platform/node/src/node_mapbox_gl_native.cpp new file mode 100644 index 000000000..c0fb47d14 --- /dev/null +++ b/platform/node/src/node_mapbox_gl_native.cpp @@ -0,0 +1,65 @@ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wshadow" +#include +#include +#pragma GCC diagnostic pop + +#include "node_map.hpp" +#include "node_log.hpp" +#include "node_request.hpp" + +NAN_MODULE_INIT(RegisterModule) { + node_mbgl::NodeMap::Init(target); + node_mbgl::NodeRequest::Init(target); + + // Exports Resource constants. + v8::Local resource = Nan::New(); + + Nan::Set(resource, + Nan::New("Unknown").ToLocalChecked(), + Nan::New(mbgl::Resource::Unknown)); + + Nan::Set(resource, + Nan::New("Style").ToLocalChecked(), + Nan::New(mbgl::Resource::Style)); + + Nan::Set(resource, + Nan::New("Source").ToLocalChecked(), + Nan::New(mbgl::Resource::Source)); + + Nan::Set(resource, + Nan::New("Tile").ToLocalChecked(), + Nan::New(mbgl::Resource::Tile)); + + Nan::Set(resource, + Nan::New("Glyphs").ToLocalChecked(), + Nan::New(mbgl::Resource::Glyphs)); + + Nan::Set(resource, + Nan::New("SpriteImage").ToLocalChecked(), + Nan::New(mbgl::Resource::SpriteImage)); + + Nan::Set(resource, + Nan::New("SpriteJSON").ToLocalChecked(), + Nan::New(mbgl::Resource::SpriteJSON)); + + Nan::Set(target, + Nan::New("Resource").ToLocalChecked(), + resource); + + // Make the exported object inherit from process.EventEmitter + v8::Local process = Nan::Get( + Nan::GetCurrentContext()->Global(), + Nan::New("process").ToLocalChecked()).ToLocalChecked()->ToObject(); + + v8::Local EventEmitter = Nan::Get(process, + Nan::New("EventEmitter").ToLocalChecked()).ToLocalChecked()->ToObject(); + + Nan::SetPrototype(target, + Nan::Get(EventEmitter, Nan::New("prototype").ToLocalChecked()).ToLocalChecked()); + + mbgl::Log::setObserver(std::make_unique(target->ToObject())); +} + +NODE_MODULE(mapbox_gl_native, RegisterModule) diff --git a/platform/node/src/node_request.cpp b/platform/node/src/node_request.cpp new file mode 100644 index 000000000..76cafacbd --- /dev/null +++ b/platform/node/src/node_request.cpp @@ -0,0 +1,141 @@ +#include "node_request.hpp" +#include "node_file_source.hpp" +#include +#include + +#include +#include + +namespace node_mbgl { + +//////////////////////////////////////////////////////////////////////////////////////////////// +// Static Node Methods + +Nan::Persistent NodeRequest::constructor; + +NAN_MODULE_INIT(NodeRequest::Init) { + v8::Local tpl = Nan::New(New); + + tpl->InstanceTemplate()->SetInternalFieldCount(1); + tpl->SetClassName(Nan::New("Request").ToLocalChecked()); + + constructor.Reset(tpl->GetFunction()); + Nan::Set(target, Nan::New("Request").ToLocalChecked(), tpl->GetFunction()); +} + +NAN_METHOD(NodeRequest::New) { + // Extract the pointer from the first argument + if (info.Length() < 2 || !info[0]->IsExternal() || !info[1]->IsExternal()) { + return Nan::ThrowTypeError("Cannot create Request objects explicitly"); + } + + auto source = reinterpret_cast(info[0].As()->Value()); + auto resource = reinterpret_cast(info[1].As()->Value()); + auto req = new NodeRequest(source, *resource); + req->Wrap(info.This()); + + info.GetReturnValue().Set(info.This()); +} + +v8::Handle NodeRequest::Create(NodeFileSource* source, const mbgl::Resource& resource) { + Nan::EscapableHandleScope scope; + + v8::Local argv[] = { + Nan::New(const_cast(source)), + Nan::New(const_cast(&resource)) + }; + auto instance = Nan::New(constructor)->NewInstance(2, argv); + + Nan::Set(instance, Nan::New("url").ToLocalChecked(), Nan::New(resource.url).ToLocalChecked()); + Nan::Set(instance, Nan::New("kind").ToLocalChecked(), Nan::New(int(resource.kind))); + + return scope.Escape(instance); +} + +NAN_METHOD(NodeRequest::Respond) { + auto nodeRequest = Nan::ObjectWrap::Unwrap(info.Data().As()); + + // Request has already been responded to, or was canceled, fail silently. + if (!nodeRequest->resource) { + return info.GetReturnValue().SetUndefined(); + } + + auto source = nodeRequest->source; + auto resource = std::move(nodeRequest->resource); + + if (info.Length() < 1) { + return Nan::ThrowTypeError("First argument must be an error object"); + } else if (info[0]->BooleanValue()) { + auto response = std::make_shared(); + + response->status = mbgl::Response::Error; + + // Store the error string. + const Nan::Utf8String message { info[0]->ToString() }; + response->message = std::string { *message, size_t(message.length()) }; + + source->notify(*resource, response); + } else if (info.Length() < 2 || !info[1]->IsObject()) { + return Nan::ThrowTypeError("Second argument must be a response object"); + } else { + auto response = std::make_shared(); + auto res = info[1]->ToObject(); + + response->status = mbgl::Response::Successful; + + if (Nan::Has(res, Nan::New("modified").ToLocalChecked()).FromJust()) { + const double modified = Nan::Get(res, Nan::New("modified").ToLocalChecked()).ToLocalChecked()->ToNumber()->Value(); + if (!std::isnan(modified)) { + response->modified = modified / 1000; // JS timestamps are milliseconds + } + } + + if (Nan::Has(res, Nan::New("expires").ToLocalChecked()).FromJust()) { + const double expires = Nan::Get(res, Nan::New("expires").ToLocalChecked()).ToLocalChecked()->ToNumber()->Value(); + if (!std::isnan(expires)) { + response->expires = expires / 1000; // JS timestamps are milliseconds + } + } + + if (Nan::Has(res, Nan::New("etag").ToLocalChecked()).FromJust()) { + auto etagHandle = Nan::Get(res, Nan::New("etag").ToLocalChecked()).ToLocalChecked(); + if (etagHandle->BooleanValue()) { + const Nan::Utf8String etag { etagHandle->ToString() }; + response->etag = std::string { *etag, size_t(etag.length()) }; + } + } + + if (Nan::Has(res, Nan::New("data").ToLocalChecked()).FromJust()) { + auto dataHandle = Nan::Get(res, Nan::New("data").ToLocalChecked()).ToLocalChecked(); + if (node::Buffer::HasInstance(dataHandle)) { + response->data = std::string { + node::Buffer::Data(dataHandle), + node::Buffer::Length(dataHandle) + }; + } else { + return Nan::ThrowTypeError("Response data must be a Buffer"); + } + } + + // Send the response object to the NodeFileSource object + source->notify(*resource, response); + } + + info.GetReturnValue().SetUndefined(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////// +// Instance + +NodeRequest::NodeRequest(NodeFileSource* source_, const mbgl::Resource& resource_) + : source(source_), + resource(std::make_unique(resource_)) {} + +NodeRequest::~NodeRequest() { +} + +void NodeRequest::cancel() { + resource.reset(); +} + +} diff --git a/platform/node/src/node_request.hpp b/platform/node/src/node_request.hpp new file mode 100644 index 000000000..35bb8e520 --- /dev/null +++ b/platform/node/src/node_request.hpp @@ -0,0 +1,37 @@ +#pragma once + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wshadow" +#include +#pragma GCC diagnostic pop + +#include + +#include + +namespace node_mbgl { + +class NodeFileSource; + +class NodeRequest : public Nan::ObjectWrap { +public: + static NAN_MODULE_INIT(Init); + + static NAN_METHOD(New); + static NAN_METHOD(Respond); + + static v8::Handle Create(NodeFileSource*, const mbgl::Resource&); + static Nan::Persistent constructor; + + NodeRequest(NodeFileSource* source, const mbgl::Resource& resource); + ~NodeRequest(); + + void cancel(); + +private: + NodeFileSource* source; + std::unique_ptr resource; +}; + +} diff --git a/platform/node/src/util/async_queue.hpp b/platform/node/src/util/async_queue.hpp new file mode 100644 index 000000000..81319da8c --- /dev/null +++ b/platform/node/src/util/async_queue.hpp @@ -0,0 +1,90 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include + + +#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10 +#define UV_ASYNC_PARAMS(handle) uv_async_t *handle, int +#else +#define UV_ASYNC_PARAMS(handle) uv_async_t *handle +#endif + +namespace node_mbgl { +namespace util { + +template +class AsyncQueue { +public: + AsyncQueue(uv_loop_t *loop, std::function fn) : + callback(fn) { + async.data = this; + uv_async_init(loop, &async, [](UV_ASYNC_PARAMS(handle)) { + auto q = reinterpret_cast(handle->data); + q->process(); + }); + } + + void send(T &&data) { + { + std::lock_guard lock(mutex); + queue.push(std::make_unique(std::move(data))); + } + uv_async_send(&async); + } + + void send(std::unique_ptr data) { + { + std::lock_guard lock(mutex); + queue.push(std::move(data)); + } + uv_async_send(&async); + } + + void stop() { + uv_close((uv_handle_t *)&async, [](uv_handle_t *handle) { + delete reinterpret_cast(handle->data); + }); + } + + void ref() { + uv_ref((uv_handle_t *)&async); + } + + void unref() { + uv_unref((uv_handle_t *)&async); + } + +private: + ~AsyncQueue() { + } + + void process() { + std::unique_ptr item; + while (true) { + mutex.lock(); + if (queue.empty()) { + mutex.unlock(); + break; + } + item = std::move(queue.front()); + queue.pop(); + mutex.unlock(); + callback(*item); + } + } + +private: + std::mutex mutex; + uv_async_t async; + std::queue> queue; + std::function callback; +}; + +} +} diff --git a/platform/node/test/fixtures/style.json b/platform/node/test/fixtures/style.json new file mode 100644 index 000000000..222ac82bf --- /dev/null +++ b/platform/node/test/fixtures/style.json @@ -0,0 +1,31 @@ +{ + "version": 8, + "name": "Empty", + "sources": { + "mapbox": { + "type": "vector", + "maxzoom": 15, + "tiles": [ + "./fixtures/tiles/{z}-{x}-{y}.vector.pbf" + ] + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "water", + "type": "fill", + "source": "mapbox", + "source-layer": "water", + "paint": { + "fill-color": "blue" + } + } + ] +} diff --git a/platform/node/test/fixtures/tiles/0-0-0.vector.pbf b/platform/node/test/fixtures/tiles/0-0-0.vector.pbf new file mode 100644 index 000000000..87628e06c Binary files /dev/null and b/platform/node/test/fixtures/tiles/0-0-0.vector.pbf differ diff --git a/platform/node/test/js/map.test.js b/platform/node/test/js/map.test.js new file mode 100644 index 000000000..295fec693 --- /dev/null +++ b/platform/node/test/js/map.test.js @@ -0,0 +1,286 @@ +'use strict'; + +var test = require('tape'); +var mbgl = require('../../../..'); +var fs = require('fs'); +var path = require('path'); +var style = require('../fixtures/style.json'); + +test('Map', function(t) { + t.test('must be constructed with new', function(t) { + t.throws(function() { + mbgl.Map(); + }, /Use the new operator to create new Map objects/); + + t.end(); + }); + + t.test('must be constructed with options object', function(t) { + t.throws(function() { + new mbgl.Map(); + }, /Requires an options object as first argument/); + + t.throws(function() { + new mbgl.Map('options'); + }, /Requires an options object as first argument/); + + t.end(); + }); + + t.test('requires request and ratio options', function(t) { + var options = {}; + + t.throws(function() { + new mbgl.Map(options); + }, /Options object must have a 'request' method/); + + options.request = 'test'; + t.throws(function() { + new mbgl.Map(options); + }, /Options object must have a 'request' method/); + + options.request = function() {}; + options.cancel = 'test'; + t.throws(function() { + new mbgl.Map(options); + }, /Options object 'cancel' property must be a function/); + + options.cancel = function() {}; + t.throws(function() { + new mbgl.Map(options); + }, /Options object must have a numerical 'ratio' property/); + + options.ratio = 'test'; + t.throws(function() { + new mbgl.Map(options); + }, /Options object must have a numerical 'ratio' property/); + + options.ratio = 1.0; + t.doesNotThrow(function() { + new mbgl.Map(options); + }); + + t.end(); + }); + + t.test('.load', function(t) { + var options = { + request: function() {}, + ratio: 1 + }; + + t.test('requires a map style as first argument', function(t) { + var map = new mbgl.Map(options); + + t.throws(function() { + map.load(); + }, /Requires a map style as first argument/); + + map.release(); + t.end(); + }); + + t.test('expect either an object or array at root', { timeout: 1000 }, function(t) { + var map = new mbgl.Map(options); + + mbgl.once('message', function(msg) { + t.equal(msg.severity, 'ERROR'); + t.equal(msg.class, 'ParseStyle'); + t.ok(msg.text.match(/Expect either an object or array at root/)); + + map.release(); + t.end(); + }); + + map.load('invalid'); + }); + + t.test('accepts an empty stylesheet string', function(t) { + var map = new mbgl.Map(options); + + t.doesNotThrow(function() { + map.load('{}'); + }); + + map.release(); + t.end(); + }); + + t.test('accepts a JSON stylesheet', { timeout: 1000 }, function(t) { + var map = new mbgl.Map(options); + + t.doesNotThrow(function() { + map.load(style); + }); + + map.release(); + t.end(); + }); + + t.test('accepts a stringified stylesheet', { timeout: 1000 }, function(t) { + var map = new mbgl.Map(options); + + t.doesNotThrow(function() { + map.load(JSON.stringify(style)); + }); + + map.release(); + t.end(); + }); + + t.test('does not immediately trigger any tile loads', function(t) { + var map = new mbgl.Map({ + request: function(req) { + t.fail('unexpected request ' + req.url); + }, + ratio: 1 + }); + + map.load(style); + + setTimeout(function() { + map.release(); + t.end(); + }, 100); + }); + }); + + t.test('.render', function(t) { + var options = { + request: function(req, callback) { + fs.readFile(path.join(__dirname, '..', req.url), function(err, data) { + callback(err, { data: data }); + }); + }, + ratio: 1 + }; + + t.test('requires an object as the first parameter', function(t) { + var map = new mbgl.Map(options); + + t.throws(function() { + map.render(); + }, /First argument must be an options object/); + + t.throws(function() { + map.render('invalid'); + }, /First argument must be an options object/); + + map.release(); + t.end(); + }); + + t.test('requires a callback as the second parameter', function(t) { + var map = new mbgl.Map(options); + + t.throws(function() { + map.render({}); + }, /Second argument must be a callback function/); + + t.throws(function() { + map.render({}, 'invalid'); + }, /Second argument must be a callback function/); + + map.release(); + t.end(); + }); + + t.test('requires a style to be set', function(t) { + var map = new mbgl.Map(options); + + t.throws(function() { + map.render({}, function() {}); + }, /Style is not loaded/); + + map.release(); + t.end(); + }); + + t.test('returns an error', function(t) { + mbgl.on('message', function(msg) { + t.ok(msg, 'emits error'); + t.equal(msg.class, 'Style'); + t.equal(msg.severity, 'ERROR'); + t.ok(msg.text.match(/Failed to load/), 'error text matches'); + }); + + var map = new mbgl.Map(options); + map.load(style); + map.render({ zoom: 1 }, function(err, data) { + mbgl.removeAllListeners('message'); + map.release(); + + t.ok(err, 'returns error'); + t.ok(err.message.match(/Failed to load/), 'error text matches'); + + t.end(); + }); + }); + + t.test('double release', function(t) { + var map = new mbgl.Map(options); + map.release(); + + t.throws(function() { + map.release(); + }, /Map resources have already been released/); + + t.end(); + }); + + t.test('returns an image', function(t) { + var map = new mbgl.Map(options); + map.load(style); + map.render({}, function(err, pixels) { + t.error(err); + map.release(); + t.ok(pixels); + t.ok(pixels instanceof Buffer); + t.equal(pixels.length, 512 * 512 * 4) + t.end(); + }); + }); + + t.test('can be called several times in serial', function(t) { + var completed = 0; + var remaining = 10; + var start = +new Date; + + var map = new mbgl.Map(options); + map.load(style); + + function render() { + map.render({}, function(err, data) { + t.error(err); + + t.ok(true, 'render @ ' + ((+new Date) - start) + 'ms'); + if (++completed === remaining) { + map.release(); + t.end(); + } else { + render(); + } + }); + } + + render(); + }); + + t.skip('throws if called in parallel', function(t) { + var completed = 0; + var remaining = 10; + var start = +new Date; + + var map = new mbgl.Map(options); + map.load(style); + + t.throws(function() { + map.render({}, function() {}); + map.render({}, function() {}); + }); + + map.release(); + t.end(); + }); + }); +}); diff --git a/platform/node/test/render.test.js b/platform/node/test/render.test.js new file mode 100644 index 000000000..c36600157 --- /dev/null +++ b/platform/node/test/render.test.js @@ -0,0 +1,28 @@ +'use strict'; + +var mbgl = require('../../..'); +var suite = require('mapbox-gl-test-suite'); +var request = require('request'); + +var tests; + +if (process.argv[1] === __filename && process.argv.length > 2) { + tests = process.argv.slice(2); +} + +suite.run('native', {tests: tests}, function (style, options, callback) { + var map = new mbgl.Map({ + ratio: options.pixelRatio, + request: function(req, callback) { + request(req.url, {encoding: null}, function (err, response, body) { + callback(err, {data: body}); + }); + } + }); + + map.load(style); + map.render(options, function (err, pixels) { + map.release(); + callback(err, pixels); + }); +}); diff --git a/scripts/android/configure.sh b/scripts/android/configure.sh new file mode 100644 index 000000000..70c8973a8 --- /dev/null +++ b/scripts/android/configure.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +BOOST_VERSION=1.57.0 +LIBPNG_VERSION=1.6.16 +JPEG_VERSION=v9a +SQLITE_VERSION=3.8.8.1 +LIBUV_VERSION=1.4.0 +ZLIB_VERSION=system +NUNICODE_VERSION=1.5.1 +LIBZIP_VERSION=0.11.2 + +export MASON_ANDROID_ABI=${MASON_PLATFORM_VERSION} diff --git a/scripts/android/defaults.mk b/scripts/android/defaults.mk new file mode 100644 index 000000000..29f047c88 --- /dev/null +++ b/scripts/android/defaults.mk @@ -0,0 +1,11 @@ +HEADLESS ?= none +PLATFORM ?= android +ASSET ?= zip +HTTP ?= android +CACHE ?= sqlite + +GYP_FLAVOR_SUFFIX=-android + +HOST_VERSION ?= arm-v7 + +ENV = $(shell MASON_ANDROID_ABI=$(HOST_VERSION) ./scripts/android/toolchain.sh) diff --git a/scripts/android/run.sh b/scripts/android/run.sh index 38749f79f..2ef0c40c9 100755 --- a/scripts/android/run.sh +++ b/scripts/android/run.sh @@ -3,14 +3,10 @@ set -e set -o pipefail -BUILDTYPE=${BUILDTYPE:-Release} -TESTMUNK=${TESTMUNK:-no} -export HOST=android -export MASON_PLATFORM=android -export MASON_ANDROID_ABI=${ANDROID_ABI:-arm-v7} - # Add Mason to PATH export PATH="`pwd`/.mason:${PATH}" MASON_DIR="`pwd`/.mason" +export MASON_PLATFORM=android +export MASON_ANDROID_ABI=${ANDROID_ABI} ################################################################################ # Build @@ -23,10 +19,10 @@ mkdir -p ./android/java/MapboxGLAndroidSDKTestApp/src/main/res/raw echo "${MAPBOX_ACCESS_TOKEN}" > ./android/java/MapboxGLAndroidSDKTestApp/src/main/res/raw/token.txt mapbox_time "compile_library" \ -make android-lib -j${JOBS} BUILDTYPE=${BUILDTYPE} +make android-lib HOST_VERSION=${ANDROID_ABI} -j${JOBS} BUILDTYPE=${BUILDTYPE} mapbox_time "build_apk" \ -make android -j${JOBS} BUILDTYPE=${BUILDTYPE} +make android HOST_VERSION=${ANDROID_ABI} -j${JOBS} BUILDTYPE=${BUILDTYPE} ################################################################################ # Deploy diff --git a/scripts/compare_images.sh b/scripts/compare_images.sh deleted file mode 100755 index 240772646..000000000 --- a/scripts/compare_images.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -o pipefail - -(cd ./test/suite/ && ./bin/compare_images.py native) diff --git a/scripts/ios/configure.sh b/scripts/ios/configure.sh new file mode 100644 index 000000000..0562e0e39 --- /dev/null +++ b/scripts/ios/configure.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +BOOST_VERSION=1.57.0 +SQLITE_VERSION=system +LIBUV_VERSION=0.10.28 +ZLIB_VERSION=system diff --git a/scripts/ios/defaults.mk b/scripts/ios/defaults.mk new file mode 100644 index 000000000..fafc33213 --- /dev/null +++ b/scripts/ios/defaults.mk @@ -0,0 +1,12 @@ +HEADLESS = none +PLATFORM ?= ios +ASSET ?= fs +HTTP ?= nsurl +CACHE ?= sqlite + +HOST_VERSION = all + +PROVISIONING_PROFILE ?= 19324a54-7455-4f0b-8e1c-e6957c718ebc + +export MASON_PLATFORM=$(HOST) +export MASON_PLATFORM_VERSION=$(HOST_VERSION) diff --git a/scripts/ios/package.sh b/scripts/ios/package.sh index 25306bb06..2b1e97c30 100755 --- a/scripts/ios/package.sh +++ b/scripts/ios/package.sh @@ -4,7 +4,7 @@ set -e set -o pipefail set -u -NAME=MapboxGL +NAME=Mapbox OUTPUT=build/ios/pkg IOS_SDK_VERSION=`xcrun --sdk iphoneos --show-sdk-version` LIBUV_VERSION=0.10.28 @@ -50,7 +50,7 @@ if [[ "${BUILD_FOR_DEVICE}" == true ]]; then ARCHS="arm64 armv7 armv7s" \ ONLY_ACTIVE_ARCH=NO \ GCC_GENERATE_DEBUGGING_SYMBOLS=${GCC_GENERATE_DEBUGGING_SYMBOLS} \ - -project ./build/ios/mbgl.xcodeproj \ + -project ./build/ios-all/mbgl.xcodeproj \ -configuration ${BUILDTYPE} \ -target everything \ -jobs ${JOBS} | xcpretty -c @@ -61,7 +61,7 @@ xcodebuild -sdk iphonesimulator${IOS_SDK_VERSION} \ ARCHS="x86_64 i386" \ ONLY_ACTIVE_ARCH=NO \ GCC_GENERATE_DEBUGGING_SYMBOLS=${GCC_GENERATE_DEBUGGING_SYMBOLS} \ - -project ./build/ios/mbgl.xcodeproj \ + -project ./build/ios-all/mbgl.xcodeproj \ -configuration ${BUILDTYPE} \ -target everything \ -jobs ${JOBS} | xcpretty -c @@ -101,7 +101,8 @@ step "Copying Resources..." cp -pv LICENSE.md "${OUTPUT}/static" mkdir -p "${OUTPUT}/static/${NAME}.bundle" cp -pv platform/ios/resources/* "${OUTPUT}/static/${NAME}.bundle" -cp -prv styles/styles "${OUTPUT}/static/${NAME}.bundle/styles" +mkdir -p "${OUTPUT}/static/${NAME}.bundle/styles" +cp -pv styles/styles/{dark,emerald,light,streets,satellite}-v8.json "${OUTPUT}/static/${NAME}.bundle/styles" step "Creating API Docs..." if [ -z `which appledoc` ]; then @@ -109,11 +110,12 @@ if [ -z `which appledoc` ]; then exit 1 fi DOCS_OUTPUT="${OUTPUT}/static/Docs" -DOCS_VERSION=$( git tag --sort -v:refname | grep -v '\-rc.' | sed -n '1p' | sed 's/^v//' ) +git fetch --tags +DOCS_VERSION=$( git tag | sed 's/^ios-//' | sort -r | grep -v '\-rc.' | grep -v '\-pre.' | sed -n '1p' | sed 's/^v//' ) rm -rf /tmp/mbgl mkdir -p /tmp/mbgl/ -README=/tmp/mbgl/GL-README.md -cat ios/README.md > ${README} +README=/tmp/mbgl/README.md +cat ios/docs/pod-README.md > ${README} echo >> ${README} echo -n "#" >> ${README} cat CHANGELOG.md >> ${README} @@ -125,7 +127,7 @@ perl \ /tmp/mbgl/Headers/*.h appledoc \ --output ${DOCS_OUTPUT} \ - --project-name "Mapbox GL for iOS ${DOCS_VERSION}" \ + --project-name "Mapbox iOS SDK ${DOCS_VERSION}" \ --project-company Mapbox \ --create-html \ --no-create-docset \ @@ -133,3 +135,4 @@ appledoc \ --company-id com.mapbox \ --index-desc ${README} \ /tmp/mbgl/Headers +cp ${README} "${OUTPUT}/static" diff --git a/scripts/ios/publish.sh b/scripts/ios/publish.sh index 484ee1fb8..61270fb45 100755 --- a/scripts/ios/publish.sh +++ b/scripts/ios/publish.sh @@ -20,7 +20,7 @@ fi # zip # cd build/ios/pkg/static -ZIP=mapbox-gl-ios-${PUBLISH_VERSION}${PUBLISH_STYLE}.zip +ZIP=mapbox-ios-sdk-${PUBLISH_VERSION}${PUBLISH_STYLE}.zip rm -f ../${ZIP} zip -r ../${ZIP} * # diff --git a/scripts/ios/test.sh b/scripts/ios/test.sh index bfaed4646..170aa0f64 100755 --- a/scripts/ios/test.sh +++ b/scripts/ios/test.sh @@ -7,5 +7,5 @@ set -u xctool \ -project ./test/ios/ios-tests.xcodeproj \ -scheme 'Mapbox GL Tests' \ - -sdk iphonesimulator8.3 \ + -sdk iphonesimulator8.4 \ test diff --git a/scripts/linux/after_script.sh b/scripts/linux/after_script.sh deleted file mode 100755 index c38af0a79..000000000 --- a/scripts/linux/after_script.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -o pipefail - -if [ ! -z "${AWS_ACCESS_KEY_ID}" ] && [ ! -z "${AWS_SECRET_ACCESS_KEY}" ] ; then - # Install and add awscli to PATH for uploading the results - mapbox_time "install_awscli" \ - pip install --user awscli - export PATH="`python -m site --user-base`/bin:${PATH}" - - mapbox_time_start "deploy_results" - (cd ./test/suite/ && ./bin/deploy_results.sh) - mapbox_time_finish -fi diff --git a/scripts/linux/configure.sh b/scripts/linux/configure.sh new file mode 100644 index 000000000..1099a6550 --- /dev/null +++ b/scripts/linux/configure.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +BOOST_VERSION=1.57.0 +BOOST_LIBPROGRAM_OPTIONS_VERSION=1.57.0 +LIBCURL_VERSION=system +GLFW_VERSION=3.1 +LIBPNG_VERSION=1.6.16 +JPEG_VERSION=v9a +SQLITE_VERSION=3.8.8.1 +LIBUV_VERSION=0.10.28 +ZLIB_VERSION=system +NUNICODE_VERSION=1.5.1 +LIBZIP_VERSION=0.11.2 + +function print_opengl_flags { + CONFIG+=" 'opengl_cflags%': $(quote_flags $(pkg-config gl x11 --cflags)),"$LN + CONFIG+=" 'opengl_ldflags%': $(quote_flags $(pkg-config gl x11 --libs)),"$LN +} diff --git a/scripts/linux/defaults.mk b/scripts/linux/defaults.mk new file mode 100644 index 000000000..126c8f99a --- /dev/null +++ b/scripts/linux/defaults.mk @@ -0,0 +1,5 @@ +HEADLESS ?= glx +PLATFORM ?= linux +ASSET ?= fs +HTTP ?= curl +CACHE ?= sqlite diff --git a/scripts/linux/run.sh b/scripts/linux/run.sh index 42d2a16d9..31272ea1c 100755 --- a/scripts/linux/run.sh +++ b/scripts/linux/run.sh @@ -27,11 +27,5 @@ make test -j${JOBS} BUILDTYPE=${BUILDTYPE} # Test ################################################################################ -mapbox_time "checkout_test_suite" \ -git submodule update --init test/suite - mapbox_time "run_tests" \ make test-* BUILDTYPE=${BUILDTYPE} - -mapbox_time "compare_results" \ -./scripts/compare_images.sh diff --git a/scripts/main.mk b/scripts/main.mk new file mode 100644 index 000000000..3639a780a --- /dev/null +++ b/scripts/main.mk @@ -0,0 +1,138 @@ +ifeq (,$(V)) + QUIET = @ +endif + +# Determine host platform +HOST ?= $(BUILD) + +# Defines host defaults +include scripts/$(HOST)/defaults.mk + +HOST_VERSION ?= $(BUILD_VERSION) + +# Optionally include version-specific host defaults +-include scripts/$(HOST)/$(HOST_VERSION)/defaults.mk + +export MASON_PLATFORM=$(HOST) +export MASON_PLATFORM_VERSION=$(HOST_VERSION) + +HOST_SLUG = $(HOST)-$(HOST_VERSION) +CONFIGURE_FILES = scripts/$(HOST)/configure.sh +ifneq (,$(wildcard scripts/$(HOST)/$(HOST_VERSION)/configure.sh)) + CONFIGURE_FILES += scripts/$(HOST)/$(HOST_VERSION)/configure.sh +endif + + +# Text formatting +TEXT_BOLD = \033[1m +COLOR_GREEN = \033[32m +COLOR_CYAN = \033[36m +COLOR_PINK = \033[35m +FORMAT_END = \033[0m + +default: ; + +#### Dependencies ############################################################## + +SUBMODULES += .mason/mason.sh +.mason/mason.sh: + ./scripts/flock.py .git/Submodule.lock git submodule update --init .mason + +SUBMODULES += styles/styles +styles/styles: + ./scripts/flock.py .git/Submodule.lock git submodule update --init styles + +SUBMODULES += src/mbgl/util/geojsonvt/geojsonvt.hpp +src/mbgl/util/geojsonvt/geojsonvt.hpp: + ./scripts/flock.py .git/Submodule.lock git submodule update --init src/mbgl/util/geojsonvt + +ifeq ($(HOST),ios) +SUBMODULES += platform/ios/vendor/SMCalloutView/SMCalloutView.h +platform/ios/vendor/SMCalloutView/SMCalloutView.h: + ./scripts/flock.py .git/Submodule.lock git submodule update --init platform/ios/vendor/SMCalloutView + +SUBMODULES += test/ios/KIF/KIF.xcodeproj +test/ios/KIF/KIF.xcodeproj: + ./scripts/flock.py .git/Submodule.lock git submodule update --init test/ios/KIF +endif + +# Wildcard targets get removed after build by default, but we want to preserve the config. +.PRECIOUS: config/%.gypi +config/%.gypi: $(SUBMODULES) configure $(CONFIGURE_FILES) + @printf "$(TEXT_BOLD)$(COLOR_GREEN)* Recreating project...$(FORMAT_END)\n" + $(QUIET)$(ENV) ./scripts/flock.py build/Configure.lock ./configure config/$*.gypi + + +#### Build files ############################################################### + +GYP_FLAGS += -Dhost=$(HOST) +GYP_FLAGS += -Iconfig/$(HOST_SLUG).gypi +GYP_FLAGS += -Dplatform_lib=$(PLATFORM) +GYP_FLAGS += -Dhttp_lib=$(HTTP) +GYP_FLAGS += -Dasset_lib=$(ASSET) +GYP_FLAGS += -Dcache_lib=$(CACHE) +GYP_FLAGS += -Dheadless_lib=$(HEADLESS) +GYP_FLAGS += -Dtest=$(BUILD_TEST) +GYP_FLAGS += -Drender=$(BUILD_RENDER) +GYP_FLAGS += --depth=. +GYP_FLAGS += -Goutput_dir=. +GYP_FLAGS += --generator-output=./build/$(HOST_SLUG) + +.PHONY: Makefile/__project__ +Makefile/__project__: print-env $(SUBMODULES) config/$(HOST_SLUG).gypi + @printf "$(TEXT_BOLD)$(COLOR_GREEN)* Recreating project...$(FORMAT_END)\n" + $(QUIET)$(ENV) deps/run_gyp gyp/$(HOST).gyp $(GYP_FLAGS) \ + -f make$(GYP_FLAVOR_SUFFIX) + +.PHONY: Xcode/__project__ +Xcode/__project__: print-env $(SUBMODULES) config/$(HOST_SLUG).gypi + @printf "$(TEXT_BOLD)$(COLOR_GREEN)* Recreating project...$(FORMAT_END)\n" + $(QUIET)$(ENV) deps/run_gyp gyp/$(HOST).gyp $(GYP_FLAGS) \ + -f xcode$(GYP_FLAVOR_SUFFIX) + +#### Build individual targets ################################################## + +NODE_PRE_GYP = $(shell npm bin)/node-pre-gyp +node/configure: + $(QUIET)$(ENV) $(NODE_PRE_GYP) configure --clang -- \ + $(GYP_FLAGS) -Dlibuv_ldflags= -Dlibuv_static_libs= + +Makefile/node: Makefile/__project__ node/configure + @printf "$(TEXT_BOLD)$(COLOR_GREEN)* Building target node...$(FORMAT_END)\n" + $(QUIET)$(ENV) $(NODE_PRE_GYP) build --clang -- \ + -j$(JOBS) + +Makefile/%: Makefile/__project__ + @printf "$(TEXT_BOLD)$(COLOR_GREEN)* Building target $*...$(FORMAT_END)\n" + $(QUIET)$(ENV) $(MAKE) -C build/$(HOST_SLUG) BUILDTYPE=$(BUILDTYPE) $* + +Xcode/%: Xcode/__project__ + @printf "$(TEXT_BOLD)$(COLOR_GREEN)* Building target $*...$(FORMAT_END)\n" + $(QUIET)$(ENV) set -o pipefail && xcodebuild \ + $(XCODEBUILD_ARGS) \ + -project ./build/$(HOST_SLUG)/gyp/$(HOST).xcodeproj \ + -configuration $(BUILDTYPE) \ + -target $* \ + -jobs $(JOBS) \ + $(XCPRETTY) + +#### Run tests ################################################################# + +test-%: Makefile/test + ./scripts/run_tests.sh "build/$(HOST_SLUG)/$(BUILDTYPE)/test" --gtest_filter=$* + +#### Helper targets ############################################################ + +.PHONY: print-env +print-env: $(SUBMODULES) + @printf "$(COLOR_PINK)build:$(FORMAT_END) $(BUILD)/$(BUILD_VERSION)\n" + @printf "$(COLOR_PINK)host:$(FORMAT_END) $(HOST)/$(HOST_VERSION)\n" + @printf "$(COLOR_PINK)libs:$(FORMAT_END) " + @printf "headless=$(COLOR_CYAN)%s$(FORMAT_END) " $(HEADLESS) + @printf "platform=$(COLOR_CYAN)%s$(FORMAT_END) " $(PLATFORM) + @printf "asset=$(COLOR_CYAN)%s$(FORMAT_END) " $(ASSET) + @printf "http=$(COLOR_CYAN)%s$(FORMAT_END) " $(HTTP) + @printf "cache=$(COLOR_CYAN)%s$(FORMAT_END)\n" $(CACHE) + +# Never remove intermediate files +.SECONDARY: diff --git a/scripts/node/after_script.sh b/scripts/node/after_script.sh new file mode 100755 index 000000000..237f9fe60 --- /dev/null +++ b/scripts/node/after_script.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +set -e +set -o pipefail + +# Inspect binary. +if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then + ldd ./lib/mapbox-gl-native.node +else + otool -L ./lib/mapbox-gl-native.node +fi + +COMMIT_MESSAGE=$(git show -s --format=%B $TRAVIS_COMMIT | tr -d '\n') + +if test "${COMMIT_MESSAGE#*'[publish binary]'}" != "$COMMIT_MESSAGE"; then + source ~/.nvm/nvm.sh + nvm use $NODE_VERSION + + npm install aws-sdk + + ./node_modules/.bin/node-pre-gyp package + + if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then + ./node_modules/.bin/node-pre-gyp testpackage + fi + + ./node_modules/.bin/node-pre-gyp publish info + + if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then + source ./scripts/${TRAVIS_OS_NAME}/setup.sh + + rm -rf build + rm -rf lib + npm install --fallback-to-build=false + npm test + fi +fi + +if [ ! -z "${AWS_ACCESS_KEY_ID}" ] && [ ! -z "${AWS_SECRET_ACCESS_KEY}" ] ; then + # Install and add awscli to PATH for uploading the results + pip install --user awscli + export PATH="`python -m site --user-base`/bin:${PATH}" + + REPO_NAME=$(basename $TRAVIS_REPO_SLUG) + gzip --stdout node_modules/mapbox-gl-test-suite/tests/index.html | \ + aws s3 cp --acl public-read --content-encoding gzip --content-type text/html \ + - s3://mapbox/$REPO_NAME/tests/$TRAVIS_JOB_NUMBER/index.html + + echo http://mapbox.s3.amazonaws.com/$REPO_NAME/tests/$TRAVIS_JOB_NUMBER/index.html +fi diff --git a/scripts/node/install.sh b/scripts/node/install.sh new file mode 100755 index 000000000..26bf98002 --- /dev/null +++ b/scripts/node/install.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +set -e +set -o pipefail + +mapbox_time "checkout_mason" \ +git submodule update --init .mason + +export PATH="`pwd`/.mason:${PATH}" MASON_DIR="`pwd`/.mason" + +if [ ${TRAVIS_OS_NAME} == "linux" ]; then + mapbox_time "install_mesa" \ + mason install mesa 10.4.3 +fi + +if [ ! -d ~/.nvm ]; then + git clone https://github.com/creationix/nvm.git ~/.nvm + + pushd ~/.nvm + git fetch + git checkout `git describe --abbrev=0 --tags` + popd +fi + +source ~/.nvm/nvm.sh + +mapbox_time $NODE_VERSION \ +nvm install $NODE_VERSION + +nvm alias default $NODE_VERSION + +node --version +npm --version diff --git a/scripts/node/run.sh b/scripts/node/run.sh new file mode 100755 index 000000000..22db3fded --- /dev/null +++ b/scripts/node/run.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +set -e +set -o pipefail + +source ./scripts/${TRAVIS_OS_NAME}/setup.sh + +BUILDTYPE=${BUILDTYPE:-Release} + +################################################################################ +# Build +################################################################################ + +source ~/.nvm/nvm.sh +nvm use $NODE_VERSION + +mapbox_time "checkout_styles" \ +git submodule update --init styles + +mapbox_time "compile_program" \ +npm install --build-from-source + +################################################################################ +# Test +################################################################################ + +# Travis OS X has no GPU +if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then + mapbox_time "run_tests" \ + npm test + + mapbox_time "run_render_tests" \ + npm run test-suite +fi diff --git a/scripts/osx/configure.sh b/scripts/osx/configure.sh new file mode 100644 index 000000000..fc3c5f464 --- /dev/null +++ b/scripts/osx/configure.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +BOOST_VERSION=1.57.0 +BOOST_LIBPROGRAM_OPTIONS_VERSION=1.57.0 +LIBCURL_VERSION=system +GLFW_VERSION=3.1 +LIBPNG_VERSION=1.6.16 +JPEG_VERSION=v9a +SQLITE_VERSION=3.8.8.1 +LIBUV_VERSION=0.10.28 +ZLIB_VERSION=system +NUNICODE_VERSION=1.5.1 +LIBZIP_VERSION=0.11.2 diff --git a/scripts/osx/defaults.mk b/scripts/osx/defaults.mk new file mode 100644 index 000000000..f148f66d8 --- /dev/null +++ b/scripts/osx/defaults.mk @@ -0,0 +1,5 @@ +HEADLESS ?= cgl +PLATFORM ?= osx +ASSET ?= fs +HTTP ?= nsurl +CACHE ?= sqlite diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh index e4c9e77ea..692269e4f 100755 --- a/scripts/run_tests.sh +++ b/scripts/run_tests.sh @@ -5,7 +5,7 @@ set -o pipefail if [ `uname -s` = 'Darwin' ]; then HOST=${HOST:-osx} ; else HOST=${HOST:-linux} ; fi -CMD=$1 +CMD=$@ shift # allow writing core files @@ -24,21 +24,9 @@ if [ ! -d "test/node_modules/express" ]; then (cd test; npm install express@4.11.1) fi -RESULT=0 -${CMD} "$@" || RESULT=$? - -if [[ ${RESULT} != 0 ]]; then - echo "The program crashed with exit code ${RESULT}. We're now trying to output the core dump." -fi - -# output core dump if we got one -for DUMP in $(find ./ -maxdepth 1 -name 'core*' -print); do - gdb ${CMD} ${DUMP} -ex "thread apply all bt" -ex "set pagination 0" -batch - rm -rf ${DUMP} -done - -# now we should present travis with the original -# error code so the run cleanly stops -if [[ ${RESULT} != 0 ]]; then - exit $RESULT +if which -s 'gdb'; then + gdb -batch -return-child-result -ex 'set print thread-events off' \ + -ex 'run' -ex 'thread apply all bt' --args ${CMD} ; +else + ${CMD} ; fi diff --git a/src/csscolorparser/csscolorparser.cpp b/src/csscolorparser/csscolorparser.cpp index 7ba989fde..8281eb420 100644 --- a/src/csscolorparser/csscolorparser.cpp +++ b/src/csscolorparser/csscolorparser.cpp @@ -116,7 +116,7 @@ const size_t namedColorCount = sizeof (namedColors) / sizeof (NamedColor); template uint8_t clamp_css_byte(T i) { // Clamp to integer 0 .. 255. - i = std::round(i); // Seems to be what Chrome does (vs truncation). + i = ::round(i); // Seems to be what Chrome does (vs truncation). return i < 0 ? 0 : i > 255 ? 255 : i; } diff --git a/src/mbgl/annotation/sprite_image.cpp b/src/mbgl/annotation/sprite_image.cpp new file mode 100644 index 000000000..e482d8f13 --- /dev/null +++ b/src/mbgl/annotation/sprite_image.cpp @@ -0,0 +1,29 @@ +#include + +#include + +#include + +namespace mbgl { + +SpriteImage::SpriteImage(const uint16_t width_, + const uint16_t height_, + const float pixelRatio_, + std::string&& data_, + bool sdf_) + : width(width_), + height(height_), + pixelRatio(pixelRatio_), + pixelWidth(std::ceil(width * pixelRatio)), + pixelHeight(std::ceil(height * pixelRatio)), + data(std::move(data_)), + sdf(sdf_) { + const size_t size = pixelWidth * pixelHeight * 4; + if (size == 0) { + throw util::SpriteImageException("Sprite image dimensions may not be zero"); + } else if (size != data.size()) { + throw util::SpriteImageException("Sprite image pixel count mismatch"); + } +} + +} // namespace mbgl diff --git a/src/mbgl/annotation/sprite_parser.cpp b/src/mbgl/annotation/sprite_parser.cpp new file mode 100644 index 000000000..f867b97f4 --- /dev/null +++ b/src/mbgl/annotation/sprite_parser.cpp @@ -0,0 +1,147 @@ +#include +#include + +#include + +#include + +#include + +#include +#include +#include + +namespace mbgl { + +SpriteImagePtr createSpriteImage(const util::Image& image, + const uint16_t srcX, + const uint16_t srcY, + const uint16_t srcWidth, + const uint16_t srcHeight, + const double ratio, + const bool sdf) { + // Disallow invalid parameter configurations. + if (srcWidth == 0 || srcHeight == 0 || ratio <= 0 || ratio > 10 || srcWidth > 1024 || + srcHeight > 1024) { + Log::Warning(Event::Sprite, "Can't create sprite with invalid metrics"); + return nullptr; + } + + const uint16_t width = std::ceil(double(srcWidth) / ratio); + const uint16_t dstWidth = std::ceil(width * ratio); + assert(dstWidth >= srcWidth); + const uint16_t height = std::ceil(double(srcHeight) / ratio); + const uint16_t dstHeight = std::ceil(height * ratio); + assert(dstHeight >= srcHeight); + + std::string data(dstWidth * dstHeight * 4, '\0'); + + auto srcData = reinterpret_cast(image.getData()); + auto dstData = reinterpret_cast(const_cast(data.data())); + + const int32_t maxX = std::min(image.getWidth(), uint32_t(srcWidth + srcX)) - srcX; + assert(maxX <= int32_t(image.getWidth())); + const int32_t maxY = std::min(image.getHeight(), uint32_t(srcHeight + srcY)) - srcY; + assert(maxY <= int32_t(image.getHeight())); + + // Copy from the source image into our individual sprite image + for (uint16_t y = 0; y < maxY; ++y) { + const auto dstRow = y * dstWidth; + const auto srcRow = (y + srcY) * image.getWidth() + srcX; + for (uint16_t x = 0; x < maxX; ++x) { + dstData[dstRow + x] = srcData[srcRow + x]; + } + } + + return std::make_unique(width, height, ratio, std::move(data), sdf); +} + +namespace { + +inline uint16_t getUInt16(const rapidjson::Value& value, const char* name, const uint16_t def = 0) { + if (value.HasMember(name)) { + auto& v = value[name]; + if (v.IsUint() && v.GetUint() <= std::numeric_limits::max()) { + return v.GetUint(); + } else { + Log::Warning(Event::Sprite, "Value of '%s' must be an integer between 0 and 65535", + name); + } + } + + return def; +} + +inline double getDouble(const rapidjson::Value& value, const char* name, const double def = 0) { + if (value.HasMember(name)) { + auto& v = value[name]; + if (v.IsNumber()) { + return v.GetDouble(); + } else { + Log::Warning(Event::Sprite, "Value of '%s' must be a number", name); + } + } + + return def; +} + +inline bool getBoolean(const rapidjson::Value& value, const char* name, const bool def = false) { + if (value.HasMember(name)) { + auto& v = value[name]; + if (v.IsBool()) { + return v.GetBool(); + } else { + Log::Warning(Event::Sprite, "Value of '%s' must be a boolean", name); + } + } + + return def; +} + +} // namespace + +SpriteParseResult parseSprite(const std::string& image, const std::string& json) { + using namespace rapidjson; + + Sprites sprites; + + // Parse the sprite image. + const util::Image raster(image); + if (!raster) { + return std::string("Could not parse sprite image"); + } + + Document doc; + doc.Parse<0>(json.c_str()); + + if (doc.HasParseError()) { + std::stringstream message; + message << "Failed to parse JSON: " << doc.GetParseError() << " at offset " << doc.GetErrorOffset(); + return message.str(); + } else if (!doc.IsObject()) { + return std::string("Sprite JSON root must be an object"); + } else { + for (Value::ConstMemberIterator itr = doc.MemberBegin(); itr != doc.MemberEnd(); ++itr) { + const std::string name = { itr->name.GetString(), itr->name.GetStringLength() }; + const Value& value = itr->value; + + if (value.IsObject()) { + const uint16_t x = getUInt16(value, "x", 0); + const uint16_t y = getUInt16(value, "y", 0); + const uint16_t width = getUInt16(value, "width", 0); + const uint16_t height = getUInt16(value, "height", 0); + const double pixelRatio = getDouble(value, "pixelRatio", 1); + const bool sdf = getBoolean(value, "sdf", false); + + auto sprite = createSpriteImage(raster, x, y, width, height, pixelRatio, sdf); + if (sprite) { + sprites.emplace(name, sprite); + } + } + } + } + + return sprites; +} + +} // namespace mbgl diff --git a/src/mbgl/annotation/sprite_parser.hpp b/src/mbgl/annotation/sprite_parser.hpp new file mode 100644 index 000000000..d7e995335 --- /dev/null +++ b/src/mbgl/annotation/sprite_parser.hpp @@ -0,0 +1,45 @@ +#ifndef MBGL_ANNOTATIONS_SPRITE_PARSER +#define MBGL_ANNOTATIONS_SPRITE_PARSER + +#include +#include +#include + +#include +#include +#include + +namespace mbgl { + +namespace util { + +class Image; + +} // namespace util + +class SpriteImage; + +using SpriteImagePtr = std::shared_ptr; + +// Extracts an individual image from a spritesheet from the given location. +SpriteImagePtr createSpriteImage(const util::Image& image, + uint16_t srcX, + uint16_t srcY, + uint16_t srcWidth, + uint16_t srcHeight, + double ratio, + bool sdf); + +using Sprites = std::map; + + +using SpriteParseResult = mapbox::util::variant< + Sprites, // success + std::string>; // error + +// Parses an image and an associated JSON file and returns the sprite objects. +SpriteParseResult parseSprite(const std::string& image, const std::string& json); + +} // namespace mbgl + +#endif diff --git a/src/mbgl/annotation/sprite_store.cpp b/src/mbgl/annotation/sprite_store.cpp new file mode 100644 index 000000000..8d2231a2b --- /dev/null +++ b/src/mbgl/annotation/sprite_store.cpp @@ -0,0 +1,71 @@ +#include + +#include + +namespace mbgl { + +void SpriteStore::setSprite(const std::string& name, std::shared_ptr sprite) { + std::lock_guard lock(mutex); + _setSprite(name, sprite); +} + +void SpriteStore::_setSprite(const std::string& name, + const std::shared_ptr& sprite) { + if (sprite) { + auto it = sprites.find(name); + if (it != sprites.end()) { + // There is already a sprite with that name in our store. + if ((it->second->width != sprite->width || it->second->height != sprite->height)) { + Log::Warning(Event::Sprite, "Can't change sprite dimensions for '%s'", name.c_str()); + return; + } + it->second = sprite; + } else { + sprites.emplace(name, sprite); + } + + // Always add/replace the value in the dirty list. + auto dirty_it = dirty.find(name); + if (dirty_it != dirty.end()) { + dirty_it->second = sprite; + } else { + dirty.emplace(name, sprite); + } + } else if (sprites.erase(name) > 0) { + dirty.emplace(name, nullptr); + } +} + +void SpriteStore::setSprites(const Sprites& newSprites) { + std::lock_guard lock(mutex); + for (const auto& pair : newSprites) { + _setSprite(pair.first, pair.second); + } +} + +void SpriteStore::removeSprite(const std::string& name) { + std::lock_guard lock(mutex); + _setSprite(name); +} + +std::shared_ptr SpriteStore::getSprite(const std::string& name) { + std::lock_guard lock(mutex); + const auto it = sprites.find(name); + if (it != sprites.end()) { + return it->second; + } else { + if (!sprites.empty()) { + Log::Info(Event::Sprite, "Can't find sprite named '%s'", name.c_str()); + } + return nullptr; + } +} + +SpriteStore::Sprites SpriteStore::getDirty() { + Sprites result; + std::lock_guard lock(mutex); + dirty.swap(result); + return result; +} + +} // namespace mbgl diff --git a/src/mbgl/annotation/sprite_store.hpp b/src/mbgl/annotation/sprite_store.hpp new file mode 100644 index 000000000..78e02ac69 --- /dev/null +++ b/src/mbgl/annotation/sprite_store.hpp @@ -0,0 +1,53 @@ +#ifndef MBGL_ANNOTATION_SPRITE_STORE +#define MBGL_ANNOTATION_SPRITE_STORE + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace mbgl { + +// The SpriteStore object holds Sprite images. +class SpriteStore : private util::noncopyable { + using Sprites = std::map>; + +public: + // Adds/replaces a Sprite image. + void setSprite(const std::string&, std::shared_ptr = nullptr); + + // Adds/replaces mutliple Sprite images. + void setSprites(const Sprites& sprites); + + // Removes a Sprite. + void removeSprite(const std::string&); + + // Obtains a Sprite image. + std::shared_ptr getSprite(const std::string&); + + // Returns Sprite images that changed since the last invocation of this function. + Sprites getDirty(); + +private: + void _setSprite(const std::string&, const std::shared_ptr& = nullptr); + + // Lock for sprites and dirty maps. + std::mutex mutex; + + // Stores all current sprites. + Sprites sprites; + + // Stores all Sprite IDs that changed since the last invocation. + Sprites dirty; +}; + +} // namespace mbgl + +#endif diff --git a/src/mbgl/geometry/circle_buffer.cpp b/src/mbgl/geometry/circle_buffer.cpp new file mode 100644 index 000000000..f4b0cddec --- /dev/null +++ b/src/mbgl/geometry/circle_buffer.cpp @@ -0,0 +1,13 @@ +#include + +#include + +#include + +using namespace mbgl; + +void CircleVertexBuffer::add(vertex_type x, vertex_type y, float ex, float ey) { + vertex_type *vertices = static_cast(addElement()); + vertices[0] = (x * 2) + ((ex + 1) / 2); + vertices[1] = (y * 2) + ((ey + 1) / 2); +} diff --git a/src/mbgl/geometry/circle_buffer.hpp b/src/mbgl/geometry/circle_buffer.hpp new file mode 100644 index 000000000..cab9122e5 --- /dev/null +++ b/src/mbgl/geometry/circle_buffer.hpp @@ -0,0 +1,27 @@ +#ifndef MBGL_GEOMETRY_CIRCLE_BUFFER +#define MBGL_GEOMETRY_CIRCLE_BUFFER + +#include + +namespace mbgl { + +class CircleVertexBuffer : public Buffer< + 4 // 2 bytes per short * 4 of them. +> { +public: + typedef int16_t vertex_type; + + /* + * Add a vertex to this buffer + * + * @param {number} x vertex position + * @param {number} y vertex position + * @param {number} ex extrude normal + * @param {number} ey extrude normal + */ + void add(vertex_type x, vertex_type y, float ex, float ey); +}; + +} + +#endif // MBGL_GEOMETRY_CIRCLE_BUFFER diff --git a/src/mbgl/geometry/collision_box_buffer.cpp b/src/mbgl/geometry/collision_box_buffer.cpp index 050a999db..da9031c89 100644 --- a/src/mbgl/geometry/collision_box_buffer.cpp +++ b/src/mbgl/geometry/collision_box_buffer.cpp @@ -13,8 +13,8 @@ size_t CollisionBoxVertexBuffer::add(int16_t x, int16_t y, float ox, float oy, f int16_t *shorts = static_cast(data); shorts[0] /* pos */ = x; shorts[1] /* pos */ = y; - shorts[2] /* offset */ = std::round(ox); // use 1/64 pixels for placement - shorts[3] /* offset */ = std::round(oy); + shorts[2] /* offset */ = ::round(ox); // use 1/64 pixels for placement + shorts[3] /* offset */ = ::round(oy); uint8_t *ubytes = static_cast(data); // a_data1 diff --git a/src/mbgl/geometry/debug_font_buffer.cpp b/src/mbgl/geometry/debug_font_buffer.cpp index 1ec71463e..e4af70766 100644 --- a/src/mbgl/geometry/debug_font_buffer.cpp +++ b/src/mbgl/geometry/debug_font_buffer.cpp @@ -23,8 +23,8 @@ void DebugFontBuffer::addText(const char *text, double left, double baseline, do if (glyph.data[j] == -1 && glyph.data[j + 1] == -1) { prev = false; } else { - int16_t x = std::round(left + glyph.data[j] * scale); - int16_t y = std::round(baseline - glyph.data[j + 1] * scale); + int16_t x = ::round(left + glyph.data[j] * scale); + int16_t y = ::round(baseline - glyph.data[j + 1] * scale); if (prev) { coords = static_cast(addElement()); coords[0] = prev_x; diff --git a/src/mbgl/geometry/glyph_atlas.cpp b/src/mbgl/geometry/glyph_atlas.cpp index 067c5a406..43ea58553 100644 --- a/src/mbgl/geometry/glyph_atlas.cpp +++ b/src/mbgl/geometry/glyph_atlas.cpp @@ -61,7 +61,7 @@ Rect GlyphAtlas::addGlyph(uintptr_t tileUID, } // The glyph bitmap has zero width. - if (!glyph.bitmap.size()) { + if (glyph.bitmap.empty()) { return Rect{ 0, 0, 0, 0 }; } @@ -114,7 +114,7 @@ void GlyphAtlas::removeGlyphs(uintptr_t tileUID) { GlyphValue& value = it->second; value.ids.erase(tileUID); - if (!value.ids.size()) { + if (value.ids.empty()) { const Rect& rect = value.rect; // Clear out the bitmap. @@ -176,7 +176,7 @@ void GlyphAtlas::upload() { dirty = false; #if defined(DEBUG) - // platform::showDebugImage("Glyph Atlas", data.get(), width, height); + // platform::showDebugImage("Glyph Atlas", reinterpret_cast(data.get()), width, height); #endif } } diff --git a/src/mbgl/geometry/icon_buffer.cpp b/src/mbgl/geometry/icon_buffer.cpp index 2ca15b6c5..831ffc72a 100644 --- a/src/mbgl/geometry/icon_buffer.cpp +++ b/src/mbgl/geometry/icon_buffer.cpp @@ -13,8 +13,8 @@ size_t IconVertexBuffer::add(int16_t x, int16_t y, float ox, float oy, int16_t t int16_t *shorts = static_cast(data); shorts[0] /* pos */ = x; shorts[1] /* pos */ = y; - shorts[2] /* offset */ = std::round(ox * 64); // use 1/64 pixels for placement - shorts[3] /* offset */ = std::round(oy * 64); + shorts[2] /* offset */ = ::round(ox * 64); // use 1/64 pixels for placement + shorts[3] /* offset */ = ::round(oy * 64); uint8_t *ubytes = static_cast(data); // a_data1 @@ -24,7 +24,7 @@ size_t IconVertexBuffer::add(int16_t x, int16_t y, float ox, float oy, int16_t t // a_data2 ubytes[12] /* minzoom */ = minzoom * 10; // 1/10 zoom levels: z16 == 160. - ubytes[13] /* maxzoom */ = std::fmin(maxzoom, 25) * 10; // 1/10 zoom levels: z16 == 160. + ubytes[13] /* maxzoom */ = ::fmin(maxzoom, 25) * 10; // 1/10 zoom levels: z16 == 160. return idx; } diff --git a/src/mbgl/geometry/line_atlas.cpp b/src/mbgl/geometry/line_atlas.cpp index 0c16294d5..aeac3067e 100644 --- a/src/mbgl/geometry/line_atlas.cpp +++ b/src/mbgl/geometry/line_atlas.cpp @@ -20,7 +20,7 @@ LineAtlas::LineAtlas(uint16_t w, uint16_t h) } LineAtlas::~LineAtlas() { - std::lock_guard lock(mtx); + assert(util::ThreadContext::currentlyOn(util::ThreadType::Map)); if (texture) { mbgl::util::ThreadContext::getGLObjectStore()->abandonTexture(texture); @@ -29,14 +29,14 @@ LineAtlas::~LineAtlas() { } LinePatternPos LineAtlas::getDashPosition(const std::vector &dasharray, bool round) { + assert(util::ThreadContext::currentlyOn(util::ThreadType::Map)); + size_t key = round ? std::numeric_limits::min() : std::numeric_limits::max(); for (const float part : dasharray) { boost::hash_combine(key, part); } // Note: We're not handling hash collisions here. - - std::lock_guard lock(mtx); const auto it = positions.find(key); if (it == positions.end()) { auto inserted = positions.emplace(key, addDash(dasharray, round)); @@ -137,7 +137,7 @@ void LineAtlas::upload() { } void LineAtlas::bind() { - std::lock_guard lock(mtx); + assert(util::ThreadContext::currentlyOn(util::ThreadType::Map)); bool first = false; if (!texture) { diff --git a/src/mbgl/geometry/line_atlas.hpp b/src/mbgl/geometry/line_atlas.hpp index 7075a932b..3c3b90f75 100644 --- a/src/mbgl/geometry/line_atlas.hpp +++ b/src/mbgl/geometry/line_atlas.hpp @@ -4,8 +4,6 @@ #include #include #include -#include -#include namespace mbgl { @@ -34,9 +32,8 @@ public: const int height; private: - std::recursive_mutex mtx; const std::unique_ptr data; - std::atomic dirty; + bool dirty; uint32_t texture = 0; int nextRow = 0; std::map positions; diff --git a/src/mbgl/geometry/line_buffer.cpp b/src/mbgl/geometry/line_buffer.cpp index 770483848..f475c63b2 100644 --- a/src/mbgl/geometry/line_buffer.cpp +++ b/src/mbgl/geometry/line_buffer.cpp @@ -14,8 +14,8 @@ size_t LineVertexBuffer::add(vertex_type x, vertex_type y, float ex, float ey, i coords[1] = (y * 2) | ty; int8_t *extrude = static_cast(data); - extrude[4] = std::round(extrudeScale * ex); - extrude[5] = std::round(extrudeScale * ey); + extrude[4] = ::round(extrudeScale * ex); + extrude[5] = ::round(extrudeScale * ey); extrude[6] = static_cast(linesofar / 128); extrude[7] = static_cast(linesofar % 128); diff --git a/src/mbgl/geometry/sprite_atlas.cpp b/src/mbgl/geometry/sprite_atlas.cpp index 799f43b16..743848d5f 100644 --- a/src/mbgl/geometry/sprite_atlas.cpp +++ b/src/mbgl/geometry/sprite_atlas.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -18,50 +19,17 @@ using namespace mbgl; -SpriteAtlas::SpriteAtlas(dimension width_, dimension height_) +SpriteAtlas::SpriteAtlas(dimension width_, dimension height_, float pixelRatio_, SpriteStore& store_) : width(width_), height(height_), + pixelWidth(std::ceil(width * pixelRatio_)), + pixelHeight(std::ceil(height * pixelRatio_)), + pixelRatio(pixelRatio_), + store(store_), bin(width_, height_), + data(std::make_unique(pixelWidth * pixelHeight)), dirty(true) { -} - -bool SpriteAtlas::resize(const float newRatio) { - if (pixelRatio == newRatio) return false; - - std::lock_guard lock(mtx); - - const float oldRatio = pixelRatio; - pixelRatio = newRatio; - - if (data) { - const auto oldData = std::move(data); - allocate(); - - const int old_w = width * oldRatio; - const int old_h = height * oldRatio; - const int new_w = width * newRatio; - const int new_h = height * newRatio; - - // Basic image scaling. TODO: Replace this with better image scaling. - for (int y = 0; y < new_h; y++) { - const int old_yoffset = ((y * old_h) / new_h) * old_w; - const int new_yoffset = y * new_w; - for (int x = 0; x < new_w; x++) { - const int old_x = (x * old_w) / new_w; - data[new_yoffset + x] = oldData[old_yoffset + old_x]; - } - } - - dirty = true; - fullUploadRequired = true; - - // Mark all sprite images as in need of update - for (const auto &pair : images) { - uninitialized.emplace(pair.first); - } - } - - return dirty; + std::fill(data.get(), data.get() + pixelWidth * pixelHeight, 0); } Rect SpriteAtlas::allocateImage(const size_t pixel_width, const size_t pixel_height) { @@ -84,48 +52,48 @@ Rect SpriteAtlas::allocateImage(const size_t pixel_width return rect; } -Rect SpriteAtlas::getImage(const std::string& name, const bool wrap) { +SpriteAtlasElement SpriteAtlas::getImage(const std::string& name, const bool wrap) { std::lock_guard lock(mtx); - auto rect_it = images.find(name); + auto rect_it = images.find({ name, wrap }); if (rect_it != images.end()) { - return rect_it->second; + return { rect_it->second.pos, rect_it->second.texture }; } - const SpritePosition &pos = sprite->getSpritePosition(name); - if (!pos.width || !pos.height) { - return Rect { 0, 0, 0, 0 }; + auto sprite = store.getSprite(name); + if (!sprite) { + return { Rect { 0, 0, 0, 0 }, nullptr }; } - Rect rect = allocateImage(pos.width / pos.pixelRatio, pos.height / pos.pixelRatio); + Rect rect = allocateImage(sprite->width, sprite->height); if (rect.w == 0) { if (debug::spriteWarnings) { Log::Warning(Event::Sprite, "sprite atlas bitmap overflow"); } - return rect; + return { Rect { 0, 0, 0, 0 }, nullptr }; } - images.emplace(name, rect); + const Holder& holder = images.emplace(Key{ name, wrap }, Holder{ sprite, rect }).first->second; + copy(holder, wrap); - copy(rect, pos, wrap); - - return rect; + return { rect, sprite }; } SpriteAtlasPosition SpriteAtlas::getPosition(const std::string& name, bool repeating) { std::lock_guard lock(mtx); - Rect rect = getImage(name, repeating); + auto rect = getImage(name, repeating).pos; if (repeating) { // When the image is repeating, get the correct position of the image, rather than the // one rounded up to 4 pixels. - const SpritePosition &pos = sprite->getSpritePosition(name); - if (!pos.width || !pos.height) { + // TODO: Can't we just use originalW/originalH? + auto sprite = store.getSprite(name); + if (!sprite) { return SpriteAtlasPosition {}; } - rect.w = pos.width / pos.pixelRatio; - rect.h = pos.height / pos.pixelRatio; + rect.w = sprite->width; + rect.h = sprite->height; } const float padding = 1; @@ -137,33 +105,21 @@ SpriteAtlasPosition SpriteAtlas::getPosition(const std::string& name, bool repea }; } -void SpriteAtlas::allocate() { - if (!data) { - dimension w = static_cast(width * pixelRatio); - dimension h = static_cast(height * pixelRatio); - data = std::make_unique(w * h); - std::fill(data.get(), data.get() + w * h, 0); - } -} - -void SpriteAtlas::copy(const Rect& dst, const SpritePosition& src, const bool wrap) { - if (!sprite->raster) return; - - const uint32_t *srcData = reinterpret_cast(sprite->raster->getData()); +void SpriteAtlas::copy(const Holder& holder, const bool wrap) { + const uint32_t *srcData = reinterpret_cast(holder.texture->data.data()); if (!srcData) return; - const vec2 srcSize { sprite->raster->getWidth(), sprite->raster->getHeight() }; - const Rect srcPos { src.x, src.y, src.width, src.height }; + const vec2 srcSize { holder.texture->pixelWidth, holder.texture->pixelHeight }; + const Rect srcPos { 0, 0, srcSize.x, srcSize.y }; + const auto& dst = holder.pos; const int offset = 1; - allocate(); uint32_t *const dstData = data.get(); - const vec2 dstSize { static_cast(width * pixelRatio), - static_cast(height * pixelRatio) }; - const Rect dstPos { static_cast((offset + dst.x) * pixelRatio), - static_cast((offset + dst.y) * pixelRatio), - static_cast(dst.originalW * pixelRatio), - static_cast(dst.originalH * pixelRatio) }; + const vec2 dstSize{ pixelWidth, pixelHeight }; + const Rect dstPos{ static_cast((offset + dst.x) * pixelRatio), + static_cast((offset + dst.y) * pixelRatio), + static_cast(dst.originalW * pixelRatio), + static_cast(dst.originalH * pixelRatio) }; util::bilinearScale(srcData, srcSize, srcPos, dstData, dstSize, dstPos, wrap); @@ -201,41 +157,40 @@ void SpriteAtlas::copy(const Rect& dst, const SpritePosition& src, co dirty = true; } -void SpriteAtlas::setSprite(util::ptr sprite_) { - std::lock_guard lock(mtx); - - sprite = sprite_; - - if (!sprite->isLoaded()) return; - - util::erase_if(uninitialized, [this](const std::string &name) { - Rect dst = getImage(name, false); - const SpritePosition& src = sprite->getSpritePosition(name); - if (!src) { - if (debug::spriteWarnings) { - Log::Warning(Event::Sprite, "sprite doesn't have image with name '%s'", name.c_str()); - } - return true; - } - - if (src.width == dst.w * pixelRatio && src.height == dst.h * pixelRatio && src.pixelRatio == pixelRatio) { - copy(dst, src, false); - return true; - } else { - if (debug::spriteWarnings) { - Log::Warning(Event::Sprite, "sprite icon dimension mismatch"); - } - return false; - } - }); -} - void SpriteAtlas::upload() { if (dirty) { bind(); } } +void SpriteAtlas::updateDirty() { + auto dirtySprites = store.getDirty(); + if (dirtySprites.empty()) { + return; + } + + std::lock_guard lock(mtx); + + auto imageIterator = images.begin(); + auto spriteIterator = dirtySprites.begin(); + while (imageIterator != images.end() && spriteIterator != dirtySprites.end()) { + if (imageIterator->first.first < spriteIterator->first) { + ++imageIterator; + } else if (spriteIterator->first < imageIterator->first.first) { + ++spriteIterator; + } else { + // The two names match; + Holder& holder = imageIterator->second; + holder.texture = spriteIterator->second; + copy(holder, imageIterator->first.second); + + ++imageIterator; + // Don't advance the spriteIterator because there might be another sprite with the same + // name, but a different wrap value. + } + } +} + void SpriteAtlas::bind(bool linear) { if (!texture) { MBGL_CHECK_ERROR(glGenTextures(1, &texture)); @@ -261,15 +216,14 @@ void SpriteAtlas::bind(bool linear) { if (dirty) { std::lock_guard lock(mtx); - allocate(); if (fullUploadRequired) { MBGL_CHECK_ERROR(glTexImage2D( GL_TEXTURE_2D, // GLenum target 0, // GLint level GL_RGBA, // GLint internalformat - width * pixelRatio, // GLsizei width - height * pixelRatio, // GLsizei height + pixelWidth, // GLsizei width + pixelHeight, // GLsizei height 0, // GLint border GL_RGBA, // GLenum format GL_UNSIGNED_BYTE, // GLenum type @@ -282,8 +236,8 @@ void SpriteAtlas::bind(bool linear) { 0, // GLint level 0, // GLint xoffset 0, // GLint yoffset - width * pixelRatio, // GLsizei width - height * pixelRatio, // GLsizei height + pixelWidth, // GLsizei width + pixelHeight, // GLsizei height GL_RGBA, // GLenum format GL_UNSIGNED_BYTE, // GLenum type data.get() // const GLvoid *pixels @@ -293,7 +247,8 @@ void SpriteAtlas::bind(bool linear) { dirty = false; #ifndef GL_ES_VERSION_2_0 - // platform::showColorDebugImage("Sprite Atlas", reinterpret_cast(data), width * pixelRatio, height * pixelRatio, width * pixelRatio, height * pixelRatio); + // platform::showColorDebugImage("Sprite Atlas", reinterpret_cast(data.get()), + // pixelWidth, pixelHeight, pixelWidth, pixelHeight); #endif } }; @@ -305,3 +260,11 @@ SpriteAtlas::~SpriteAtlas() { texture = 0; } } + +SpriteAtlas::Holder::Holder(const std::shared_ptr& texture_, + const Rect& pos_) + : texture(texture_), pos(pos_) { +} + +SpriteAtlas::Holder::Holder(Holder&& h) : texture(std::move(h.texture)), pos(h.pos) { +} diff --git a/src/mbgl/geometry/sprite_atlas.hpp b/src/mbgl/geometry/sprite_atlas.hpp index d34013cd6..bfe4f843d 100644 --- a/src/mbgl/geometry/sprite_atlas.hpp +++ b/src/mbgl/geometry/sprite_atlas.hpp @@ -15,7 +15,8 @@ namespace mbgl { -class Sprite; +class SpriteStore; +class SpriteImage; class SpritePosition; struct SpriteAtlasPosition { @@ -28,56 +29,68 @@ struct SpriteAtlasPosition { std::array br; }; +struct SpriteAtlasElement { + const Rect pos; + const std::shared_ptr texture; +}; + class SpriteAtlas : public util::noncopyable { public: typedef uint16_t dimension; - // Add way to construct this from another SpriteAtlas (e.g. with another pixelRatio) - SpriteAtlas(dimension width, dimension height); + SpriteAtlas(dimension width, dimension height, float pixelRatio, SpriteStore& store); ~SpriteAtlas(); - // Changes the pixel ratio. - bool resize(float newRatio); - - // Changes the source sprite. - void setSprite(util::ptr sprite); - // Returns the coordinates of an image that is sourced from the sprite image. // This getter attempts to read the image from the sprite if it is already loaded. // In that case, it copies it into the sprite atlas and returns the dimensions. // Otherwise, it returns a 0/0/0/0 rect. - Rect getImage(const std::string& name, const bool wrap); + // This function is used during bucket creation. + SpriteAtlasElement getImage(const std::string& name, const bool wrap); + // This function is used for getting the position during render time. SpriteAtlasPosition getPosition(const std::string& name, bool repeating = false); // Binds the atlas texture to the GPU, and uploads data if it is out of date. void bind(bool linear = false); + // Updates sprites in the atlas texture that may have changed in the source SpriteStore object. + void updateDirty(); + // Uploads the texture to the GPU to be available when we need it. This is a lazy operation; // the texture is only bound when the data is out of date (=dirty). void upload(); - inline float getWidth() const { return width; } - inline float getHeight() const { return height; } - inline float getTextureWidth() const { return width * pixelRatio; } - inline float getTextureHeight() const { return height * pixelRatio; } + inline dimension getWidth() const { return width; } + inline dimension getHeight() const { return height; } + inline dimension getTextureWidth() const { return pixelWidth; } + inline dimension getTextureHeight() const { return pixelHeight; } inline float getPixelRatio() const { return pixelRatio; } - - const dimension width = 0; - const dimension height = 0; + inline const uint32_t* getData() const { return data.get(); } private: - void allocate(); + const dimension width, height; + const dimension pixelWidth, pixelHeight; + const float pixelRatio; + + struct Holder : private util::noncopyable { + inline Holder(const std::shared_ptr&, const Rect&); + inline Holder(Holder&&); + std::shared_ptr texture; + const Rect pos; + }; + + using Key = std::pair; + Rect allocateImage(size_t width, size_t height); - void copy(const Rect& dst, const SpritePosition& src, const bool wrap); + void copy(const Holder& holder, const bool wrap); std::recursive_mutex mtx; - float pixelRatio = 1.0f; + SpriteStore& store; BinPack bin; - util::ptr sprite; - std::map> images; + std::map images; std::set uninitialized; - std::unique_ptr data; + const std::unique_ptr data; std::atomic dirty; bool fullUploadRequired = true; uint32_t texture = 0; diff --git a/src/mbgl/geometry/text_buffer.cpp b/src/mbgl/geometry/text_buffer.cpp index e3f7117eb..8f312c9fa 100644 --- a/src/mbgl/geometry/text_buffer.cpp +++ b/src/mbgl/geometry/text_buffer.cpp @@ -13,8 +13,8 @@ size_t TextVertexBuffer::add(int16_t x, int16_t y, float ox, float oy, uint16_t int16_t *shorts = static_cast(data); shorts[0] /* pos */ = x; shorts[1] /* pos */ = y; - shorts[2] /* offset */ = std::round(ox * 64); // use 1/64 pixels for placement - shorts[3] /* offset */ = std::round(oy * 64); + shorts[2] /* offset */ = ::round(ox * 64); // use 1/64 pixels for placement + shorts[3] /* offset */ = ::round(oy * 64); uint8_t *ubytes = static_cast(data); // a_data1 @@ -24,7 +24,7 @@ size_t TextVertexBuffer::add(int16_t x, int16_t y, float ox, float oy, uint16_t // a_data2 ubytes[12] /* minzoom */ = minzoom * 10; // 1/10 zoom levels: z16 == 160. - ubytes[13] /* maxzoom */ = std::fmin(maxzoom, 25) * 10; // 1/10 zoom levels: z16 == 160. + ubytes[13] /* maxzoom */ = ::fmin(maxzoom, 25) * 10; // 1/10 zoom levels: z16 == 160. return idx; } diff --git a/src/mbgl/geometry/vao.cpp b/src/mbgl/geometry/vao.cpp index ef4cb26db..1330a057f 100644 --- a/src/mbgl/geometry/vao.cpp +++ b/src/mbgl/geometry/vao.cpp @@ -69,7 +69,7 @@ void VertexArrayObject::bindVertexArrayObject() { } void VertexArrayObject::verifyBinding(Shader &shader, GLuint vertexBuffer, GLuint elementsBuffer, - char *offset) { + GLbyte *offset) { if (bound_shader != shader.getID()) { throw std::runtime_error(std::string("trying to rebind VAO to another shader from " + util::toString(bound_shader) + "(" + bound_shader_name + ") to " + @@ -84,7 +84,7 @@ void VertexArrayObject::verifyBinding(Shader &shader, GLuint vertexBuffer, GLuin } void VertexArrayObject::storeBinding(Shader &shader, GLuint vertexBuffer, GLuint elementsBuffer, - char *offset) { + GLbyte *offset) { bound_shader = shader.getID(); bound_shader_name = shader.name; bound_offset = offset; diff --git a/src/mbgl/geometry/vao.hpp b/src/mbgl/geometry/vao.hpp index e512a3e48..bd845b1e3 100644 --- a/src/mbgl/geometry/vao.hpp +++ b/src/mbgl/geometry/vao.hpp @@ -18,7 +18,7 @@ public: ~VertexArrayObject(); template - inline void bind(Shader& shader, VertexBuffer &vertexBuffer, char *offset) { + inline void bind(Shader& shader, VertexBuffer &vertexBuffer, GLbyte *offset) { bindVertexArrayObject(); if (bound_shader == 0) { vertexBuffer.bind(); @@ -32,7 +32,7 @@ public: } template - inline void bind(Shader& shader, VertexBuffer &vertexBuffer, ElementsBuffer &elementsBuffer, char *offset) { + inline void bind(Shader& shader, VertexBuffer &vertexBuffer, ElementsBuffer &elementsBuffer, GLbyte *offset) { bindVertexArrayObject(); if (bound_shader == 0) { vertexBuffer.bind(); @@ -52,8 +52,8 @@ public: private: void bindVertexArrayObject(); - void storeBinding(Shader &shader, GLuint vertexBuffer, GLuint elementsBuffer, char *offset); - void verifyBinding(Shader &shader, GLuint vertexBuffer, GLuint elementsBuffer, char *offset); + void storeBinding(Shader &shader, GLuint vertexBuffer, GLuint elementsBuffer, GLbyte *offset); + void verifyBinding(Shader &shader, GLuint vertexBuffer, GLuint elementsBuffer, GLbyte *offset); GLuint vao = 0; @@ -63,7 +63,7 @@ private: const char *bound_shader_name = ""; GLuint bound_vertex_buffer = 0; GLuint bound_elements_buffer = 0; - char *bound_offset = 0; + GLbyte *bound_offset = 0; }; } diff --git a/src/mbgl/gl/debugging.cpp b/src/mbgl/gl/debugging.cpp index 5fc90e827..006112704 100644 --- a/src/mbgl/gl/debugging.cpp +++ b/src/mbgl/gl/debugging.cpp @@ -169,12 +169,11 @@ void enable() { MBGL_CHECK_ERROR(DebugMessageCallback(debugCallback, nullptr)); } -#if defined(DEBUG) group::group(const std::string& str) { if (PushDebugGroup) { PushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0, GLsizei(str.size()), str.c_str()); } else if (PushGroupMarkerEXT) { - PushGroupMarkerEXT(GLsizei(str.size()), str.c_str()); + PushGroupMarkerEXT(GLsizei(str.size() + 1), str.c_str()); } } @@ -185,10 +184,6 @@ group::~group() { PopGroupMarkerEXT(); } } -#else -group::group(const std::string&) {} -group::~group() = default; -#endif } } diff --git a/src/mbgl/gl/debugging.hpp b/src/mbgl/gl/debugging.hpp index 82d08a53f..046f0169d 100644 --- a/src/mbgl/gl/debugging.hpp +++ b/src/mbgl/gl/debugging.hpp @@ -3,6 +3,14 @@ #include +#if defined(DEBUG) +#define __MBGL_DEBUG_GROUP_NAME2(counter) __MBGL_DEBUG_GROUP_##counter +#define __MBGL_DEBUG_GROUP_NAME(counter) __MBGL_DEBUG_GROUP_NAME2(counter) +#define MBGL_DEBUG_GROUP(string) ::mbgl::gl::debugging::group __MBGL_DEBUG_GROUP_NAME(__LINE__)(string); +#else +#define MBGL_DEBUG_GROUP(string) +#endif + namespace mbgl { namespace gl { namespace debugging { diff --git a/src/mbgl/map/annotation.cpp b/src/mbgl/map/annotation.cpp index 5b857e9b5..874459797 100644 --- a/src/mbgl/map/annotation.cpp +++ b/src/mbgl/map/annotation.cpp @@ -6,6 +6,8 @@ #include #include #include +#include + #include #include @@ -49,17 +51,14 @@ AnnotationManager::~AnnotationManager() { } void AnnotationManager::markStaleTiles(std::unordered_set ids) { - std::lock_guard lock(mtx); std::copy(ids.begin(), ids.end(), std::inserter(staleTiles, staleTiles.begin())); } std::unordered_set AnnotationManager::resetStaleTiles() { - std::lock_guard lock(mtx); return std::move(staleTiles); } void AnnotationManager::setDefaultPointAnnotationSymbol(const std::string& symbol) { - std::lock_guard lock(mtx); defaultPointAnnotationSymbol = symbol; } @@ -69,7 +68,7 @@ uint32_t AnnotationManager::nextID() { vec2 AnnotationManager::projectPoint(const LatLng& point) { // Clamp to the latitude limits of Mercator. - const double constrainedLatitude = std::fmin(std::fmax(point.latitude, -util::LATITUDE_MAX), util::LATITUDE_MAX); + const double constrainedLatitude = ::fmin(::fmax(point.latitude, -util::LATITUDE_MAX), util::LATITUDE_MAX); // Project a coordinate into unit space in a square map. const double sine = std::sin(constrainedLatitude * M_PI / 180.0); @@ -112,7 +111,7 @@ AnnotationManager::addTileFeature(const uint32_t annotationID, std::vector points; for (size_t i = 0; i < segments[0].size(); ++i) { // first segment for now (no holes) - const double constraintedLatitude = std::fmin(std::fmax(segments[0][i].latitude, -util::LATITUDE_MAX), util::LATITUDE_MAX); + const double constraintedLatitude = ::fmin(::fmax(segments[0][i].latitude, -util::LATITUDE_MAX), util::LATITUDE_MAX); points.push_back(LonLat(segments[0][i].longitude, constraintedLatitude)); } @@ -136,7 +135,7 @@ AnnotationManager::addTileFeature(const uint32_t annotationID, features.push_back(Convert::create(Tags(), featureType, rings)); - shapeTilers.emplace(annotationID, std::make_unique(features, maxZoom)); + shapeTilers.emplace(annotationID, std::make_unique(features, maxZoom, 4, 100, 10)); } else { @@ -165,9 +164,6 @@ AnnotationManager::addTileFeature(const uint32_t annotationID, featureTiles.emplace(TileID(z, x, y, z), geometries); } else { for (size_t l = 0; l < projectedFeature.size(); ++l) { - - std::vector line; - for (size_t p = 0; p < projectedFeature[l].size(); ++p) { auto& pp = projectedFeature[l][p]; @@ -181,7 +177,7 @@ AnnotationManager::addTileFeature(const uint32_t annotationID, if (tile_it != featureTiles.end()) { GeometryCollection& geometries = featureTiles.find(TileID(z, x, y, z))->second; - if (geometries.size()) { + if (!geometries.empty()) { geometries.back().push_back(coordinate); } else { geometries.push_back({{ coordinate }}); @@ -225,7 +221,7 @@ AnnotationManager::addTileFeature(const uint32_t annotationID, if (type == AnnotationType::Point) { layerID = PointLayerID; } else { - layerID = ShapeLayerID + "." + std::to_string(annotationID); + layerID = ShapeLayerID + "." + util::toString(annotationID); } if (tile_pos.second || tile_pos.first->second.second->getMutableLayer(layerID) == nullptr) { layer = std::make_shared(); @@ -261,8 +257,6 @@ AnnotationManager::addTileFeature(const uint32_t annotationID, std::pair, AnnotationIDs> AnnotationManager::addPointAnnotations(const std::vector& points, const uint8_t maxZoom) { - std::lock_guard lock(mtx); - // We pre-generate tiles to contain each annotation up to the map's max zoom. // We do this for fast rendering without projection conversions on the fly, as well as // to simplify bounding box queries of annotations later. Tiles get invalidated when @@ -310,8 +304,6 @@ AnnotationManager::addPointAnnotations(const std::vector& point std::pair, AnnotationIDs> AnnotationManager::addShapeAnnotations(const std::vector& shapes, const uint8_t maxZoom) { - std::lock_guard lock(mtx); - // We pre-generate tiles to contain each annotation up to the map's max zoom. // We do this for fast rendering without projection conversions on the fly, as well as // to simplify bounding box queries of annotations later. Tiles get invalidated when @@ -348,8 +340,6 @@ AnnotationManager::addShapeAnnotations(const std::vector& shape std::unordered_set AnnotationManager::removeAnnotations(const AnnotationIDs& ids, const uint8_t maxZoom) { - std::lock_guard lock(mtx); - std::unordered_set affectedTiles; std::vector z2s; @@ -394,7 +384,7 @@ std::unordered_set AnnotationManager::removeAnnotations(co // remove shape layer from tiles if relevant for (auto tile_it = tiles.begin(); tile_it != tiles.end(); ++tile_it) { if (tile_it->second.first.count(annotationID)) { - tile_it->second.second->removeLayer(ShapeLayerID + "." + std::to_string(annotationID)); + tile_it->second.second->removeLayer(ShapeLayerID + "." + util::toString(annotationID)); affectedTiles.insert(tile_it->first); } } @@ -416,8 +406,6 @@ std::unordered_set AnnotationManager::removeAnnotations(co } const StyleProperties AnnotationManager::getAnnotationStyleProperties(uint32_t annotationID) const { - std::lock_guard lock(mtx); - auto anno_it = annotations.find(annotationID); assert(anno_it != annotations.end()); @@ -427,8 +415,6 @@ const StyleProperties AnnotationManager::getAnnotationStyleProperties(uint32_t a AnnotationIDs AnnotationManager::getAnnotationsInBounds(const LatLngBounds& queryBounds, const uint8_t maxZoom, const AnnotationType& type) const { - std::lock_guard lock(mtx); - const uint8_t z = maxZoom; const uint32_t z2 = 1 << z; const vec2 swPoint = projectPoint(queryBounds.sw); @@ -493,8 +479,6 @@ AnnotationIDs AnnotationManager::getAnnotationsInBounds(const LatLngBounds& quer } LatLngBounds AnnotationManager::getBoundsForAnnotations(const AnnotationIDs& ids) const { - std::lock_guard lock(mtx); - LatLngBounds bounds; for (auto id : ids) { const auto annotation_it = annotations.find(id); @@ -507,27 +491,25 @@ LatLngBounds AnnotationManager::getBoundsForAnnotations(const AnnotationIDs& ids } const LiveTile* AnnotationManager::getTile(const TileID& id) { - std::lock_guard lock(mtx); - // look up any existing annotation tile LiveTile *renderTile = nullptr; const auto tile_lookup_it = tiles.find(id); if (tile_lookup_it != tiles.end()) { // it exists and may have annotations already renderTile = tile_lookup_it->second.second.get(); - } else if (orderedShapeAnnotations.size()) { + } else if (!orderedShapeAnnotations.empty()) { // it needs created, but only for on-demand shapes renderTile = tiles.emplace(id, std::make_pair(std::unordered_set(), std::make_unique()) ).first->second.second.get(); } - if (renderTile != nullptr && orderedShapeAnnotations.size()) { + if (renderTile != nullptr && !orderedShapeAnnotations.empty()) { // create shape tile layers from GeoJSONVT queries for (auto& tiler_it : shapeTilers) { const auto annotationID = tiler_it.first; - const std::string layerID = ShapeLayerID + "." + std::to_string(annotationID); + const std::string layerID = ShapeLayerID + "." + util::toString(annotationID); // check for existing render layer auto renderLayer = renderTile->getMutableLayer(layerID); diff --git a/src/mbgl/map/annotation.hpp b/src/mbgl/map/annotation.hpp index e75c57f1f..5adec162f 100644 --- a/src/mbgl/map/annotation.hpp +++ b/src/mbgl/map/annotation.hpp @@ -13,7 +13,6 @@ #include #include -#include #include #include #include @@ -53,6 +52,7 @@ public: ~AnnotationManager(); void markStaleTiles(std::unordered_set); + size_t getStaleTileCount() const { return staleTiles.size(); } std::unordered_set resetStaleTiles(); void setDefaultPointAnnotationSymbol(const std::string& symbol); @@ -90,7 +90,6 @@ private: const uint8_t maxZoom); private: - mutable std::mutex mtx; std::string defaultPointAnnotationSymbol; std::unordered_map> annotations; std::vector orderedShapeAnnotations; diff --git a/src/mbgl/map/camera.cpp b/src/mbgl/map/camera.cpp new file mode 100644 index 000000000..4a45e904f --- /dev/null +++ b/src/mbgl/map/camera.cpp @@ -0,0 +1 @@ +#include diff --git a/src/mbgl/map/live_tile_data.cpp b/src/mbgl/map/live_tile_data.cpp index ccae542ee..0b73b52f3 100644 --- a/src/mbgl/map/live_tile_data.cpp +++ b/src/mbgl/map/live_tile_data.cpp @@ -1,59 +1,63 @@ #include #include #include -#include +#include #include -#include -#include +#include +#include +#include +#include + +#include using namespace mbgl; LiveTileData::LiveTileData(const TileID& id_, - AnnotationManager& annotationManager_, - const std::vector>& layers_, - Worker& workers_, - GlyphAtlas& glyphAtlas_, - GlyphStore& glyphStore_, - SpriteAtlas& spriteAtlas_, - util::ptr sprite_, + const LiveTile* tile, + Style& style_, const SourceInfo& source_, - float angle_, - bool collisionDebug_) - : VectorTileData::VectorTileData(id_, layers_, workers_, glyphAtlas_, glyphStore_, - spriteAtlas_, sprite_, source_, angle_, collisionDebug_), - annotationManager(annotationManager_) { - // live features are always ready - setState(State::loaded); -} + std::function callback) + : TileData(id_), + worker(style_.workers), + tileWorker(id_, + source_.source_id, + source_.max_zoom, + style_, + style_.layers, + state, + std::make_unique(0, 0, false)) { + state = State::loaded; -LiveTileData::~LiveTileData() { - // Cancel in most derived class destructor so that worker tasks are joined before - // any member data goes away. - cancel(); -} - -void LiveTileData::parse() { - if (getState() != State::loaded) { + if (!tile) { + state = State::parsed; return; } - const LiveTile* tile = annotationManager.getTile(id); - - if (tile) { - try { - // Parsing creates state that is encapsulated in TileParser. While parsing, - // the TileParser object writes results into this objects. All other state - // is going to be discarded afterwards. - TileParser parser(*tile, *this, layers, glyphAtlas, glyphStore, spriteAtlas, sprite); - parser.parse(); - } catch (const std::exception& ex) { - Log::Error(Event::ParseTile, "Live-parsing [%d/%d/%d] failed: %s", id.z, id.x, id.y, ex.what()); - setState(State::obsolete); - return; + workRequest = worker.parseLiveTile(tileWorker, *tile, [this, callback] (TileParseResult result) { + if (result.is()) { + state = result.get(); + } else { + error = result.get(); + state = State::obsolete; } + + callback(); + }); +} + +LiveTileData::~LiveTileData() { + cancel(); +} + +Bucket* LiveTileData::getBucket(const StyleLayer& layer) { + if (!isReady() || !layer.bucket) { + return nullptr; } - if (getState() != State::obsolete) { - setState(State::parsed); - } + return tileWorker.getBucket(layer); +} + +void LiveTileData::cancel() { + state = State::obsolete; + workRequest.reset(); } diff --git a/src/mbgl/map/live_tile_data.hpp b/src/mbgl/map/live_tile_data.hpp index 57d2e8c0d..5c4220fd6 100644 --- a/src/mbgl/map/live_tile_data.hpp +++ b/src/mbgl/map/live_tile_data.hpp @@ -1,31 +1,32 @@ #ifndef MBGL_MAP_LIVE_TILE_DATA #define MBGL_MAP_LIVE_TILE_DATA -#include +#include +#include namespace mbgl { -class AnnotationManager; +class Style; +class SourceInfo; +class WorkRequest; +class LiveTile; -class LiveTileData : public VectorTileData { +class LiveTileData : public TileData { public: LiveTileData(const TileID&, - AnnotationManager&, - const std::vector>&, - Worker&, - GlyphAtlas&, - GlyphStore&, - SpriteAtlas&, - util::ptr, + const LiveTile*, + Style&, const SourceInfo&, - float angle_, - bool collisionDebug_); + std::function callback); ~LiveTileData(); - void parse() override; + void cancel() override; + Bucket* getBucket(const StyleLayer&) override; private: - AnnotationManager& annotationManager; + Worker& worker; + TileWorker tileWorker; + std::unique_ptr workRequest; }; } diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index 91017d89c..9195f6b58 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -9,16 +9,18 @@ #include #include +#include namespace mbgl { Map::Map(View& view_, FileSource& fileSource, MapMode mode) : view(view_), transform(std::make_unique(view)), - data(std::make_unique(mode)), + data(std::make_unique(mode, view.getPixelRatio())), context(std::make_unique>(util::ThreadContext{"Map", util::ThreadType::Map, util::ThreadPriority::Regular}, view, fileSource, *data)) { view.initialize(this); + update(Update::Dimensions); } Map::~Map() { @@ -43,7 +45,8 @@ void Map::resume() { } void Map::renderStill(StillImageCallback callback) { - context->invoke(&MapContext::renderStill, transform->getState(), callback); + context->invoke(&MapContext::renderStill, transform->getState(), + FrameData{ view.getFramebufferSize() }, callback); } void Map::renderSync() { @@ -53,29 +56,33 @@ void Map::renderSync() { view.notifyMapChange(MapChangeWillStartRenderingFrame); - MapContext::RenderResult result = - context->invokeSync(&MapContext::renderSync, transform->getState()); + const Update flags = transform->updateTransitions(Clock::now()); + const bool fullyLoaded = context->invokeSync( + &MapContext::renderSync, transform->getState(), FrameData { view.getFramebufferSize() }); - view.notifyMapChange(result.fullyLoaded ? + view.notifyMapChange(fullyLoaded ? MapChangeDidFinishRenderingFrameFullyRendered : MapChangeDidFinishRenderingFrame); - if (!result.fullyLoaded) { + if (!fullyLoaded) { renderState = RenderState::partial; } else if (renderState != RenderState::fully) { renderState = RenderState::fully; view.notifyMapChange(MapChangeDidFinishRenderingMapFullyRendered); } - if (transform->needsTransition()) { - update(Update(transform->updateTransitions(Clock::now()))); - } else if (result.needsRerender) { - update(); + // Triggers an asynchronous update, that eventually triggers a view + // invalidation, causing renderSync to be called again if in transition. + if (transform->inTransition()) { + update(flags); } } -void Map::update(Update update_) { - context->invoke(&MapContext::triggerUpdate, transform->getState(), update_); +void Map::update(Update flags) { + if (flags & Update::Dimensions) { + transform->resize(view.getSize()); + } + context->invoke(&MapContext::triggerUpdate, transform->getState(), flags); } #pragma mark - Style @@ -96,37 +103,40 @@ std::string Map::getStyleJSON() const { return context->invokeSync(&MapContext::getStyleJSON); } -#pragma mark - Size - -void Map::resize(uint16_t width, uint16_t height, float ratio) { - if (transform->resize(width, height, ratio, width * ratio, height * ratio)) { - context->invoke(&MapContext::resize, width, height, ratio); - update(); - } -} - #pragma mark - Transitions void Map::cancelTransitions() { transform->cancelTransitions(); - update(); + update(Update::Repaint); } void Map::setGestureInProgress(bool inProgress) { transform->setGestureInProgress(inProgress); - update(); + update(Update::Repaint); +} + +#pragma mark - + +void Map::jumpTo(CameraOptions options) { + transform->jumpTo(options); + update(Update::Repaint); +} + +void Map::easeTo(CameraOptions options) { + transform->easeTo(options); + update(options.zoom ? Update::Zoom : Update::Repaint); } #pragma mark - Position -void Map::moveBy(double dx, double dy, Duration duration) { +void Map::moveBy(double dx, double dy, const Duration& duration) { transform->moveBy(dx, dy, duration); - update(); + update(Update::Repaint); } -void Map::setLatLng(LatLng latLng, Duration duration) { +void Map::setLatLng(LatLng latLng, const Duration& duration) { transform->setLatLng(latLng, duration); - update(); + update(Update::Repaint); } LatLng Map::getLatLng() const { @@ -134,21 +144,23 @@ LatLng Map::getLatLng() const { } void Map::resetPosition() { - transform->setAngle(0); - transform->setLatLng(LatLng(0, 0)); - transform->setZoom(0); + CameraOptions options; + options.angle = 0; + options.center = LatLng(0, 0); + options.zoom = 0; + transform->jumpTo(options); update(Update::Zoom); } #pragma mark - Scale -void Map::scaleBy(double ds, double cx, double cy, Duration duration) { +void Map::scaleBy(double ds, double cx, double cy, const Duration& duration) { transform->scaleBy(ds, cx, cy, duration); update(Update::Zoom); } -void Map::setScale(double scale, double cx, double cy, Duration duration) { +void Map::setScale(double scale, double cx, double cy, const Duration& duration) { transform->setScale(scale, cx, cy, duration); update(Update::Zoom); } @@ -157,7 +169,7 @@ double Map::getScale() const { return transform->getScale(); } -void Map::setZoom(double zoom, Duration duration) { +void Map::setZoom(double zoom, const Duration& duration) { transform->setZoom(zoom, duration); update(Update::Zoom); } @@ -166,30 +178,31 @@ double Map::getZoom() const { return transform->getZoom(); } -void Map::setLatLngZoom(LatLng latLng, double zoom, Duration duration) { +void Map::setLatLngZoom(LatLng latLng, double zoom, const Duration& duration) { transform->setLatLngZoom(latLng, zoom, duration); update(Update::Zoom); } -void Map::fitBounds(LatLngBounds bounds, EdgeInsets padding, Duration duration) { +CameraOptions Map::cameraForLatLngBounds(LatLngBounds bounds, EdgeInsets padding) { AnnotationSegment segment = { {bounds.ne.latitude, bounds.sw.longitude}, bounds.sw, {bounds.sw.latitude, bounds.ne.longitude}, bounds.ne, }; - fitBounds(segment, padding, duration); + return cameraForLatLngs(segment, padding); } -void Map::fitBounds(AnnotationSegment segment, EdgeInsets padding, Duration duration) { - if (segment.empty()) { - return; +CameraOptions Map::cameraForLatLngs(std::vector latLngs, EdgeInsets padding) { + CameraOptions options; + if (latLngs.empty()) { + return options; } - + // Calculate the bounds of the possibly rotated shape with respect to the viewport. vec2<> nePixel = {-INFINITY, -INFINITY}; vec2<> swPixel = {INFINITY, INFINITY}; - for (LatLng latLng : segment) { + for (LatLng latLng : latLngs) { vec2<> pixel = pixelForLatLng(latLng); swPixel.x = std::min(swPixel.x, pixel.x); nePixel.x = std::max(nePixel.x, pixel.x); @@ -201,9 +214,9 @@ void Map::fitBounds(AnnotationSegment segment, EdgeInsets padding, Duration dura // Calculate the zoom level. double scaleX = (getWidth() - padding.left - padding.right) / size.x; double scaleY = (getHeight() - padding.top - padding.bottom) / size.y; - double minScale = std::fmin(scaleX, scaleY); - double zoom = std::log2(getScale() * minScale); - zoom = std::fmax(std::fmin(zoom, getMaxZoom()), getMinZoom()); + double minScale = ::fmin(scaleX, scaleY); + double zoom = ::log2(getScale() * minScale); + zoom = ::fmax(::fmin(zoom, getMaxZoom()), getMinZoom()); // Calculate the center point of a virtual bounds that is extended in all directions by padding. vec2<> paddedNEPixel = { @@ -217,7 +230,9 @@ void Map::fitBounds(AnnotationSegment segment, EdgeInsets padding, Duration dura vec2<> centerPixel = (paddedNEPixel + paddedSWPixel) * 0.5; LatLng centerLatLng = latLngForPixel(centerPixel); - setLatLngZoom(centerLatLng, zoom, duration); + options.center = centerLatLng; + options.zoom = zoom; + return options; } void Map::resetZoom() { @@ -246,19 +261,19 @@ uint16_t Map::getHeight() const { #pragma mark - Rotation -void Map::rotateBy(double sx, double sy, double ex, double ey, Duration duration) { +void Map::rotateBy(double sx, double sy, double ex, double ey, const Duration& duration) { transform->rotateBy(sx, sy, ex, ey, duration); - update(); + update(Update::Repaint); } -void Map::setBearing(double degrees, Duration duration) { +void Map::setBearing(double degrees, const Duration& duration) { transform->setAngle(-degrees * M_PI / 180, duration); - update(); + update(Update::Repaint); } void Map::setBearing(double degrees, double cx, double cy) { transform->setAngle(-degrees * M_PI / 180, cx, cy); - update(); + update(Update::Repaint); } double Map::getBearing() const { @@ -267,7 +282,19 @@ double Map::getBearing() const { void Map::resetNorth() { transform->setAngle(0, std::chrono::milliseconds(500)); - update(); + update(Update::Repaint); +} + + +#pragma mark - Pitch + +void Map::setPitch(double pitch, const Duration& duration) { + transform->setPitch(util::clamp(pitch, 0., 60.) * M_PI / 180, duration); + update(Update::Repaint); +} + +double Map::getPitch() const { + return transform->getPitch() / M_PI * 180; } @@ -294,17 +321,17 @@ const LatLng Map::latLngForProjectedMeters(const ProjectedMeters projectedMeters } const vec2 Map::pixelForLatLng(const LatLng latLng) const { - return transform->getState().pixelForLatLng(latLng); + return transform->getState().latLngToPoint(latLng); } const LatLng Map::latLngForPixel(const vec2 pixel) const { - return transform->getState().latLngForPixel(pixel); + return transform->getState().pointToLatLng(pixel); } #pragma mark - Annotations void Map::setDefaultPointAnnotationSymbol(const std::string& symbol) { - data->annotationManager.setDefaultPointAnnotationSymbol(symbol); + data->getAnnotationManager()->setDefaultPointAnnotationSymbol(symbol); } double Map::getTopOffsetPixelsForAnnotationSymbol(const std::string& symbol) { @@ -316,7 +343,7 @@ uint32_t Map::addPointAnnotation(const PointAnnotation& annotation) { } AnnotationIDs Map::addPointAnnotations(const std::vector& annotations) { - auto result = data->annotationManager.addPointAnnotations(annotations, getMaxZoom()); + auto result = data->getAnnotationManager()->addPointAnnotations(annotations, getMaxZoom()); context->invoke(&MapContext::updateAnnotationTiles, result.first); return result.second; } @@ -326,7 +353,7 @@ uint32_t Map::addShapeAnnotation(const ShapeAnnotation& annotation) { } AnnotationIDs Map::addShapeAnnotations(const std::vector& annotations) { - auto result = data->annotationManager.addShapeAnnotations(annotations, getMaxZoom()); + auto result = data->getAnnotationManager()->addShapeAnnotations(annotations, getMaxZoom()); context->invoke(&MapContext::updateAnnotationTiles, result.first); return result.second; } @@ -336,16 +363,27 @@ void Map::removeAnnotation(uint32_t annotation) { } void Map::removeAnnotations(const std::vector& annotations) { - auto result = data->annotationManager.removeAnnotations(annotations, getMaxZoom()); + auto result = data->getAnnotationManager()->removeAnnotations(annotations, getMaxZoom()); context->invoke(&MapContext::updateAnnotationTiles, result); } std::vector Map::getAnnotationsInBounds(const LatLngBounds& bounds, const AnnotationType& type) { - return data->annotationManager.getAnnotationsInBounds(bounds, getMaxZoom(), type); + return data->getAnnotationManager()->getAnnotationsInBounds(bounds, getMaxZoom(), type); } LatLngBounds Map::getBoundsForAnnotations(const std::vector& annotations) { - return data->annotationManager.getBoundsForAnnotations(annotations); + return data->getAnnotationManager()->getBoundsForAnnotations(annotations); +} + + +#pragma mark - Sprites + +void Map::setSprite(const std::string& name, std::shared_ptr sprite) { + context->invoke(&MapContext::setSprite, name, sprite); +} + +void Map::removeSprite(const std::string& name) { + setSprite(name, nullptr); } @@ -353,12 +391,12 @@ LatLngBounds Map::getBoundsForAnnotations(const std::vector& annotatio void Map::setDebug(bool value) { data->setDebug(value); - update(); + update(Update::Repaint); } void Map::toggleDebug() { data->toggleDebug(); - update(); + update(Update::Repaint); } bool Map::getDebug() const { @@ -367,12 +405,12 @@ bool Map::getDebug() const { void Map::setCollisionDebug(bool value) { data->setCollisionDebug(value); - update(); + update(Update::Repaint); } void Map::toggleCollisionDebug() { data->toggleCollisionDebug(); - update(); + update(Update::Repaint); } bool Map::getCollisionDebug() const { @@ -408,15 +446,24 @@ std::vector Map::getClasses() const { return data->getClasses(); } -void Map::setDefaultTransitionDuration(Duration duration) { +void Map::setDefaultTransitionDuration(const Duration& duration) { data->setDefaultTransitionDuration(duration); - update(Update::DefaultTransitionDuration); + update(Update::DefaultTransition); } -Duration Map::getDefaultTransitionDuration() { +Duration Map::getDefaultTransitionDuration() const { return data->getDefaultTransitionDuration(); } +void Map::setDefaultTransitionDelay(const Duration& delay) { + data->setDefaultTransitionDelay(delay); + update(Update::DefaultTransition); +} + +Duration Map::getDefaultTransitionDelay() const { + return data->getDefaultTransitionDelay(); +} + void Map::setSourceTileCacheSize(size_t size) { context->invoke(&MapContext::setSourceTileCacheSize, size); } diff --git a/src/mbgl/map/map_context.cpp b/src/mbgl/map/map_context.cpp index 9dadd05d3..0ebcd73f8 100644 --- a/src/mbgl/map/map_context.cpp +++ b/src/mbgl/map/map_context.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -12,6 +13,8 @@ #include #include +#include + #include #include #include @@ -21,16 +24,17 @@ #include #include #include +#include #include namespace mbgl { -MapContext::MapContext(uv_loop_t* loop, View& view_, FileSource& fileSource, MapData& data_) +MapContext::MapContext(View& view_, FileSource& fileSource, MapData& data_) : view(view_), data(data_), - updated(static_cast(Update::Nothing)), - asyncUpdate(std::make_unique(loop, [this] { update(); })), + asyncUpdate(std::make_unique(util::RunLoop::getLoop(), [this] { update(); })), + asyncInvalidate(std::make_unique(util::RunLoop::getLoop(), [&view_] { view_.invalidate(); })), texturePool(std::make_unique()) { assert(util::ThreadContext::currentlyOn(util::ThreadType::Map)); @@ -38,6 +42,7 @@ MapContext::MapContext(uv_loop_t* loop, View& view_, FileSource& fileSource, Map util::ThreadContext::setGLObjectStore(&glObjectStore); asyncUpdate->unref(); + asyncInvalidate->unref(); view.activate(); } @@ -50,6 +55,12 @@ MapContext::~MapContext() { void MapContext::cleanup() { view.notify(); + if (styleRequest) { + FileSource* fs = util::ThreadContext::getFileSource(); + fs->cancel(styleRequest); + styleRequest = nullptr; + } + // Explicit resets currently necessary because these abandon resources that need to be // cleaned up by glObjectStore.performCleanup(); style.reset(); @@ -71,31 +82,38 @@ void MapContext::pause() { data.condResume.wait(lockPause); view.activate(); + + asyncInvalidate->send(); } -void MapContext::resize(uint16_t width, uint16_t height, float ratio) { - view.resize(width, height, ratio); -} - -void MapContext::triggerUpdate(const TransformState& state, const Update u) { +void MapContext::triggerUpdate(const TransformState& state, const Update flags) { transformState = state; - updated |= static_cast(u); + updateFlags |= flags; asyncUpdate->send(); } void MapContext::setStyleURL(const std::string& url) { + FileSource* fs = util::ThreadContext::getFileSource(); + + if (styleRequest) { + fs->cancel(styleRequest); + } + styleURL = url; styleJSON.clear(); + style = std::make_unique