[build] - initial commit

This commit is contained in:
tobrun
2019-10-09 13:19:56 +02:00
commit a3ef1ac2d0
1061 changed files with 403354 additions and 0 deletions

4
.editorconfig Normal file
View File

@@ -0,0 +1,4 @@
[*.{kt,kts}]
indent_size=2
insert_final_newline=false
disabled_rules=no-wildcard-imports

35
.gitignore vendored Normal file
View File

@@ -0,0 +1,35 @@
# Gradle files
.gradle/
# IntelliJ files
.idea/
*.iml
# Build files
build/
.externalNativeBuild
**/.cxx
*.so
*.apk
# Local settings
local.properties
gradle/configuration.gradle
# Token file
MapboxGLAndroidSDKTestApp/src/main/res/values/developer-config.xml
# Capture files
captures/
# Generated test cases
MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/activity/gen/
# Generated list files from code generation
/scripts/generate-style-code.list
# Input files for running render tests
MapboxGLAndroidSDKTestApp/src/main/assets/integration/
# Local AS configuration file
gradle.properties

12
.gitmodules vendored Normal file
View File

@@ -0,0 +1,12 @@
[submodule "vendor/mapbox-gl-native"]
path = vendor/mapbox-gl-native
url = git@github.com:mapbox/mapbox-gl-native.git
[submodule "vendor/mapbox-events-android"]
path = vendor/mapbox-events-android
url = git@github.com:mapbox/mapbox-events-android.git
[submodule "vendor/mapbox-java"]
path = vendor/mapbox-java
url = git@github.com:mapbox/mapbox-java.git
[submodule "vendor/mapbox-gestures-android"]
path = vendor/mapbox-gestures-android
url = git@github.com:mapbox/mapbox-gestures-android.git

1767
CHANGELOG.md Normal file

File diff suppressed because it is too large Load Diff

57
DISTRIBUTE.md Normal file
View File

@@ -0,0 +1,57 @@
# Distributing Mapbox GL Native for Android
Depending on your use case, you may want to support all or just a subset of [Android ABIs](http://developer.android.com/ndk/guides/abis.html). This document covers building an `.aar` file from the Mapbox Maps SDK for Android and building `.so` files for specific ABIs. In normal circumstances an application developer will use [APK splits](https://developer.android.com/studio/build/configure-apk-splits.html) to optimize this at application level.
##### Build types
With a `BUILDTYPE` var you can specify the build type for the `.so` and `.aar` files:
```bash
BUILDTYPE=Debug or BUILDTYPE=Release
```
##### Creating an Android Archive file that supports all ABIs
```sh
BUILDTYPE=Release make apackage
```
This will build native libraries to support following ABIs:
- armeabi
- armeabi-v7a
- arm64-v8a
- x86
- x86_64
- mips
After succesfully finish building the native libraries, gradle will build the MapboxAndroidSDK module and generate an Android Archive file in `MapboxAndroidSDK/build/outputs/aar `. The packaged native libraries can be found in `MapboxAndroidSDK/src/main/jniLibs/<abi>`.
##### Build native libraries for a specific ABI
```sh
make android-lib-%%
```
In the command above you'll need to replace `%%` with an ABI key listed below:
| ABI Key | Android ABI |
| ------- | ----------- |
| arm-v5 | armeabi |
| arm-v7 | armeabi-v7a |
| arm-v8 | arm64-v8a |
| x86 | x86 |
| x86-64 | x86_64 |
| mips | mips |
For example, to build the arm64-v8a ABI the Makefile target would be:
```sh
make android-lib-arm-v8
```
It's also possible to build multiple ABI (without having to build them all) by running the Makefile multiple times. For example to build arm64-v8a and x86_64 in the same project the commands would be:
```sh
make android-lib-arm-v8
make android-lib-arm-x86-64
```

227
LICENSE.md Normal file
View File

@@ -0,0 +1,227 @@
<!-- This file was generated. Use `make android-license` to update. -->
## Additional Mapbox GL licenses
Mapbox GL uses portions of the Android Arch-Common.
URL: [https://developer.android.com/topic/libraries/architecture/index.html](https://developer.android.com/topic/libraries/architecture/index.html)
License: [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Android Arch-Runtime.
URL: [https://developer.android.com/topic/libraries/architecture/index.html](https://developer.android.com/topic/libraries/architecture/index.html)
License: [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Android Lifecycle LiveData.
URL: [https://developer.android.com/topic/libraries/architecture/index.html](https://developer.android.com/topic/libraries/architecture/index.html)
License: [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Android Lifecycle LiveData Core.
URL: [https://developer.android.com/topic/libraries/architecture/index.html](https://developer.android.com/topic/libraries/architecture/index.html)
License: [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Android Lifecycle Runtime.
URL: [https://developer.android.com/topic/libraries/architecture/index.html](https://developer.android.com/topic/libraries/architecture/index.html)
License: [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Android Lifecycle ViewModel.
URL: [https://developer.android.com/topic/libraries/architecture/index.html](https://developer.android.com/topic/libraries/architecture/index.html)
License: [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Android Lifecycle-Common.
URL: [https://developer.android.com/topic/libraries/architecture/index.html](https://developer.android.com/topic/libraries/architecture/index.html)
License: [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Android Support Library Annotations.
URL: [http://developer.android.com/tools/extras/support-library.html](http://developer.android.com/tools/extras/support-library.html)
License: [The Apache Software License](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Android Support Library Async Layout Inflater.
URL: [http://developer.android.com/tools/extras/support-library.html](http://developer.android.com/tools/extras/support-library.html)
License: [The Apache Software License](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Android Support Library collections.
URL: [http://developer.android.com/tools/extras/support-library.html](http://developer.android.com/tools/extras/support-library.html)
License: [The Apache Software License](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Android Support Library compat.
URL: [http://developer.android.com/tools/extras/support-library.html](http://developer.android.com/tools/extras/support-library.html)
License: [The Apache Software License](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Android Support Library Coordinator Layout.
URL: [http://developer.android.com/tools/extras/support-library.html](http://developer.android.com/tools/extras/support-library.html)
License: [The Apache Software License](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Android Support Library core UI.
URL: [http://developer.android.com/tools/extras/support-library.html](http://developer.android.com/tools/extras/support-library.html)
License: [The Apache Software License](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Android Support Library core utils.
URL: [http://developer.android.com/tools/extras/support-library.html](http://developer.android.com/tools/extras/support-library.html)
License: [The Apache Software License](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Android Support Library Cursor Adapter.
URL: [http://developer.android.com/tools/extras/support-library.html](http://developer.android.com/tools/extras/support-library.html)
License: [The Apache Software License](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Android Support Library Custom View.
URL: [http://developer.android.com/tools/extras/support-library.html](http://developer.android.com/tools/extras/support-library.html)
License: [The Apache Software License](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Android Support Library Custom View.
URL: [http://developer.android.com/tools/extras/support-library.html](http://developer.android.com/tools/extras/support-library.html)
License: [The Apache Software License](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Android Support Library Document File.
URL: [http://developer.android.com/tools/extras/support-library.html](http://developer.android.com/tools/extras/support-library.html)
License: [The Apache Software License](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Android Support Library Drawer Layout.
URL: [http://developer.android.com/tools/extras/support-library.html](http://developer.android.com/tools/extras/support-library.html)
License: [The Apache Software License](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Android Support Library fragment.
URL: [http://developer.android.com/tools/extras/support-library.html](http://developer.android.com/tools/extras/support-library.html)
License: [The Apache Software License](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Android Support Library Interpolators.
URL: [http://developer.android.com/tools/extras/support-library.html](http://developer.android.com/tools/extras/support-library.html)
License: [The Apache Software License](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Android Support Library loader.
URL: [http://developer.android.com/tools/extras/support-library.html](http://developer.android.com/tools/extras/support-library.html)
License: [The Apache Software License](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Android Support Library Local Broadcast Manager.
URL: [http://developer.android.com/tools/extras/support-library.html](http://developer.android.com/tools/extras/support-library.html)
License: [The Apache Software License](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Android Support Library Print.
URL: [http://developer.android.com/tools/extras/support-library.html](http://developer.android.com/tools/extras/support-library.html)
License: [The Apache Software License](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Android Support Library Sliding Pane Layout.
URL: [http://developer.android.com/tools/extras/support-library.html](http://developer.android.com/tools/extras/support-library.html)
License: [The Apache Software License](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Android Support Library View Pager.
URL: [http://developer.android.com/tools/extras/support-library.html](http://developer.android.com/tools/extras/support-library.html)
License: [The Apache Software License](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Gson.
License: [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Mapbox Accounts SDK for Android.
URL: [https://github.com/mapbox/mapbox-accounts-android](https://github.com/mapbox/mapbox-accounts-android)
License: [Mapbox Terms of Service](https://www.mapbox.com/tos/)
===========================================================================
Mapbox GL uses portions of the Mapbox Android Core Library.
URL: [https://github.com/mapbox/mapbox-events-android](https://github.com/mapbox/mapbox-events-android)
License: [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Mapbox Android Gestures Library.
URL: [https://github.com/mapbox/mapbox-gestures-android](https://github.com/mapbox/mapbox-gestures-android)
License: [BSD](https://opensource.org/licenses/BSD-2-Clause)
===========================================================================
Mapbox GL uses portions of the Mapbox Android Telemetry Library.
URL: [https://github.com/mapbox/mapbox-events-android](https://github.com/mapbox/mapbox-events-android)
License: [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Mapbox Java SDK.
URL: [https://github.com/mapbox/mapbox-java](https://github.com/mapbox/mapbox-java)
License: [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Mapbox Java SDK.
URL: [https://github.com/mapbox/mapbox-java](https://github.com/mapbox/mapbox-java)
License: [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the OkHttp.
License: [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Okio.
License: [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the SoLoader.
URL: [https://github.com/facebook/soloader](https://github.com/facebook/soloader)
License: [Apache-2](https://github.com/facebook/soloader/blob/master/LICENSE)
===========================================================================
Mapbox GL uses portions of the VersionedParcelable and friends.
URL: [http://developer.android.com/tools/extras/support-library.html](http://developer.android.com/tools/extras/support-library.html)
License: [The Apache Software License](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================
Mapbox GL uses portions of the Gradle License Plugin.
URL: [https://github.com/jaredsburrows/gradle-license-plugin](https://github.com/jaredsburrows/gradle-license-plugin)
License: [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt)
===========================================================================

2
MapboxGLAndroidSDK/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
dependency-graph-mapbox-libraries.png
src/test/resources/mobile-event*

View File

@@ -0,0 +1,105 @@
apply plugin: 'com.android.library'
apply plugin: "com.jaredsburrows.license"
apply plugin: 'kotlin-android'
apply from: "${rootDir}/gradle/native-build.gradle"
dependencies {
lintChecks project(":MapboxGLAndroidSDKLint")
api dependenciesList.mapboxAndroidTelemetry
api dependenciesList.mapboxJavaGeoJSON
api dependenciesList.mapboxAndroidGestures
api dependenciesList.mapboxAndroidAccounts
implementation dependenciesList.mapboxJavaTurf
implementation dependenciesList.supportAnnotations
implementation dependenciesList.supportFragmentV4
implementation dependenciesList.okhttp3
implementation dependenciesList.soLoader
testImplementation dependenciesList.junit
testImplementation dependenciesList.mockito
testImplementation dependenciesList.mockk
testImplementation dependenciesList.robolectric
testImplementation dependenciesList.kotlinLib
testImplementation dependenciesList.commonsIO
}
android {
compileSdkVersion androidVersions.compileSdkVersion
defaultConfig {
minSdkVersion androidVersions.minSdkVersion
targetSdkVersion androidVersions.targetSdkVersion
buildConfigField "String", "GIT_REVISION_SHORT", String.format("\"%s\"", getGitRevision())
buildConfigField "String", "MAPBOX_SDK_IDENTIFIER", String.format("\"%s\"", "mapbox-maps-android")
buildConfigField "String", "MAPBOX_SDK_VERSION", String.format("\"%s\"", project.VERSION_NAME)
buildConfigField "String", "MAPBOX_VERSION_STRING", String.format("\"Mapbox/%s\"", project.VERSION_NAME)
buildConfigField "String", "MAPBOX_EVENTS_USER_AGENT", String.format("\"mapbox-maps-android/%s\"", project.VERSION_NAME)
consumerProguardFiles 'proguard-rules.pro'
}
// build native libraries
List nativeTargets = ["mapbox-gl"]
if (project.hasProperty("mapbox.with_test")) {
nativeTargets.add("mbgl-test")
}
if (project.hasProperty("mapbox.with_benchmark")) {
nativeTargets.add("mbgl-benchmark")
}
nativeBuild(nativeTargets)
// avoid naming conflicts, force usage of prefix
resourcePrefix 'mapbox_'
sourceSets {
// limit amount of exposed library resources
main.res.srcDirs += 'src/main/res-public'
}
lintOptions {
disable 'MissingTranslation', 'TypographyQuotes', 'ObsoleteLintCustomCheck', 'MissingPermission', 'WrongThreadInterprocedural'
checkAllWarnings true
warningsAsErrors false
}
testOptions {
unitTests {
returnDefaultValues true
// Roboelectric 4.0 required config
// http://robolectric.org/migrating/#migrating-to-40
includeAndroidResources = true
}
}
buildTypes {
debug {
testCoverageEnabled true
jniDebuggable true
}
}
}
licenseReport {
generateHtmlReport = false
generateJsonReport = true
copyHtmlReportToAssets = false
copyJsonReportToAssets = false
}
def static getGitRevision() {
def cmd = "git rev-parse --short HEAD"
def proc = cmd.execute()
def ref = proc.text.trim()
return ref
}
configurations {
all*.exclude group: 'commons-logging', module: 'commons-logging'
all*.exclude group: 'commons-collections', module: 'commons-collections'
}
apply from: "${rootDir}/gradle/gradle-javadoc.gradle"
apply from: "${rootDir}/gradle/gradle-checkstyle.gradle"
apply from: "${rootDir}/gradle/gradle-dependencies-graph.gradle"
apply from: "${rootDir}/gradle/gradle-update-vendor-modules.gradle"
apply from: "${rootDir}/gradle/android-nitpick.gradle"
apply from: "${rootDir}/gradle/gradle-bintray.gradle"
apply from: "${rootDir}/gradle/jacoco-report.gradle"

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<lint>
<!-- Ignore errors caused by OkHTTP -->
<issue id="InvalidPackage" severity="ignore">
<ignore path="*/okio-1.2.0.jar" />
</issue>
</lint>

41
MapboxGLAndroidSDK/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,41 @@
# 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
-keepattributes Signature, *Annotation*, EnclosingMethod
# Reflection on classes from native code
-keep class com.google.gson.JsonArray { *; }
-keep class com.google.gson.JsonElement { *; }
-keep class com.google.gson.JsonObject { *; }
-keep class com.google.gson.JsonPrimitive { *; }
-dontnote com.google.gson.**
# dontnote for keeps the entry point x but not the descriptor class y
-dontnote com.mapbox.mapboxsdk.maps.MapboxMap$OnFpsChangedListener
-dontnote com.mapbox.mapboxsdk.style.layers.PropertyValue
-dontnote com.mapbox.mapboxsdk.maps.MapboxMap
-dontnote com.mapbox.mapboxsdk.maps.MapboxMapOptions
-dontnote com.mapbox.mapboxsdk.log.LoggerDefinition
-dontnote com.mapbox.android.core.location.LocationEnginePriority
# config for okhttp 3.11.0, https://github.com/square/okhttp/pull/3354
-dontwarn javax.annotation.**
-dontnote okhttp3.internal.**
-dontwarn org.codehaus.**
# config for mapbox-sdk-geojson:3.0.1
-keep class com.mapbox.geojson.** { *; }
-dontwarn com.google.auto.value.**
# config for additional notes
-dontnote org.robolectric.Robolectric
-dontnote libcore.io.Memory
-dontnote com.google.protobuf.**
-dontnote android.net.**
-dontnote org.apache.http.**
# config for mapbox-sdk-services
# while we don't include this dependency directly
# a large amount of users combine it with our SDK
# we aren't able to provide a proguard config in that project (jar vs aar)
-dontwarn com.sun.xml.internal.ws.spi.db.*

View File

@@ -0,0 +1,15 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mapbox.mapboxsdk">
<uses-feature
android:glEsVersion="0x00020000"
android:required="true"/>
<uses-feature
android:name="android.hardware.wifi"
android:required="false"/> <!-- Implied by ACCESS_WIFI_STATE. -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
</manifest>

View File

@@ -0,0 +1,170 @@
package com.mapbox.mapboxsdk;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.text.format.DateUtils;
import com.mapbox.android.accounts.v1.MapboxAccounts;
import com.mapbox.mapboxsdk.constants.MapboxConstants;
import com.mapbox.mapboxsdk.log.Logger;
import static com.mapbox.mapboxsdk.constants.MapboxConstants.KEY_PREFERENCE_SKU_TOKEN;
/**
* REMOVAL OR MODIFICATION OF THE FOLLOWING CODE VIOLATES THE MAPBOX TERMS
* OF SERVICE
* <p>
* The following code is used to access Mapbox's Mapping APIs.
* <p>
* Removal or modification of this code when used with Mapbox's Mapping APIs
* can result in termination of your agreement and/or your account with
* Mapbox.
* <p>
* Using this code to access Mapbox Mapping APIs from outside the Mapbox Maps
* SDK also violates the Mapbox Terms of Service. On Android, Mapping APIs
* should be accessed using the methods documented at
* https://www.mapbox.com/android.
* <p>
* You can access the Mapbox Terms of Service at https://www.mapbox.com/tos/
*/
class AccountsManager {
private static final String TAG = "Mbgl-AccountsManager";
private static final String PREFERENCE_USER_ID = "com.mapbox.mapboxsdk.accounts.userid";
private static final String PREFERENCE_TIMESTAMP = "com.mapbox.mapboxsdk.accounts.timestamp";
private SharedPreferences sharedPreferences;
private String skuToken;
private long timestamp;
private boolean isManaged;
AccountsManager() {
isManaged = isSkuTokenManaged();
initialize();
}
@VisibleForTesting
AccountsManager(SharedPreferences sharedPreferences, boolean isManaged) {
this.sharedPreferences = sharedPreferences;
this.isManaged = isManaged;
initialize();
}
private void initialize() {
retrieveSkuTokenAndTimestamp();
if (isManaged) {
String userId = validateUserId();
validateRotation(userId);
}
}
private boolean isSkuTokenManaged() {
boolean value = MapboxConstants.DEFAULT_MANAGE_SKU_TOKEN;
try {
// Try getting a custom value from the app Manifest
ApplicationInfo appInfo = retrieveApplicationInfo();
if (appInfo.metaData != null) {
value = appInfo.metaData.getBoolean(
MapboxConstants.KEY_META_DATA_MANAGE_SKU_TOKEN,
MapboxConstants.DEFAULT_MANAGE_SKU_TOKEN
);
}
} catch (Exception exception) {
Logger.e(TAG, "Failed to read the package metadata: ", exception);
}
return value;
}
private ApplicationInfo retrieveApplicationInfo() throws PackageManager.NameNotFoundException {
return Mapbox.getApplicationContext().getPackageManager().getApplicationInfo(
Mapbox.getApplicationContext().getPackageName(),
PackageManager.GET_META_DATA);
}
private void retrieveSkuTokenAndTimestamp() {
SharedPreferences sharedPreferences = getSharedPreferences();
skuToken = sharedPreferences.getString(KEY_PREFERENCE_SKU_TOKEN, "");
timestamp = sharedPreferences.getLong(PREFERENCE_TIMESTAMP, 0L);
}
private String validateUserId() {
SharedPreferences sharedPreferences = getSharedPreferences();
String userId = sharedPreferences.getString(PREFERENCE_USER_ID, "");
if (TextUtils.isEmpty(userId)) {
userId = generateUserId();
SharedPreferences.Editor editor = getSharedPreferences().edit();
editor.putString(PREFERENCE_USER_ID, userId);
editor.apply();
}
return userId;
}
private void validateRotation(String userId) {
if (TextUtils.isEmpty(skuToken) || timestamp == 0L) {
skuToken = generateSkuToken(userId);
timestamp = persistRotation(skuToken);
}
}
String getSkuToken() {
if (isManaged) {
if (isExpired()) {
SharedPreferences sharedPreferences = getSharedPreferences();
String userId = sharedPreferences.getString(PREFERENCE_USER_ID, "");
skuToken = generateSkuToken(userId);
timestamp = persistRotation(skuToken);
}
} else {
SharedPreferences sharedPreferences = getSharedPreferences();
String notManagedSkuToken = sharedPreferences.getString(KEY_PREFERENCE_SKU_TOKEN, "");
skuToken = notManagedSkuToken;
}
return skuToken;
}
private boolean isExpired() {
return isExpired(getNow(), timestamp);
}
static boolean isExpired(long now, long then) {
return ((now - then) > DateUtils.HOUR_IN_MILLIS);
}
private long persistRotation(String skuToken) {
long now = getNow();
SharedPreferences.Editor editor = getSharedPreferences().edit();
editor.putLong(PREFERENCE_TIMESTAMP, now);
editor.putString(KEY_PREFERENCE_SKU_TOKEN, skuToken);
editor.apply();
return now;
}
@NonNull
private SharedPreferences getSharedPreferences() {
if (sharedPreferences == null) {
sharedPreferences = Mapbox.getApplicationContext()
.getSharedPreferences(MapboxConstants.MAPBOX_SHARED_PREFERENCES, Context.MODE_PRIVATE);
}
return sharedPreferences;
}
static long getNow() {
return System.currentTimeMillis();
}
@NonNull
private String generateUserId() {
return MapboxAccounts.obtainEndUserId();
}
@NonNull
private String generateSkuToken(String userId) {
return MapboxAccounts.obtainMapsSkuUserToken(userId);
}
}

View File

@@ -0,0 +1,55 @@
package com.mapbox.mapboxsdk;
import com.mapbox.mapboxsdk.log.Logger;
/**
* Loads the mapbox-gl shared library
* <p>
* By default uses the {@link System#loadLibrary(String)},
* use {@link #setLibraryLoader(LibraryLoader)} to provide an alternative library loading hook.
* </p>
*/
public abstract class LibraryLoader {
private static final String TAG = "Mbgl-LibraryLoader";
private static final LibraryLoader DEFAULT = Mapbox.getModuleProvider()
.createLibraryLoaderProvider()
.getDefaultLibraryLoader();
private static volatile LibraryLoader loader = DEFAULT;
private static boolean loaded;
/**
* Set the library loader that loads the shared library.
*
* @param libraryLoader the library loader
*/
public static void setLibraryLoader(LibraryLoader libraryLoader) {
loader = libraryLoader;
}
/**
* Loads "libmapbox-gl.so" native shared library.
* <p>
* Catches UnsatisfiedLinkErrors and prints a warning to logcat.
* </p>
*/
public static void load() {
try {
if (!loaded) {
loaded = true;
loader.load("mapbox-gl");
}
} catch (UnsatisfiedLinkError error) {
loaded = false;
String message = "Failed to load native shared library.";
Logger.e(TAG, message, error);
MapStrictMode.strictModeViolation(message, error);
}
}
public abstract void load(String name);
}

View File

@@ -0,0 +1,15 @@
package com.mapbox.mapboxsdk;
/**
* Injects the default library loader.
*/
public interface LibraryLoaderProvider {
/**
* Creates and returns a the default Library Loader.
*
* @return the default library loader
*/
LibraryLoader getDefaultLibraryLoader();
}

View File

@@ -0,0 +1,46 @@
package com.mapbox.mapboxsdk;
/**
* Using this class you can enable a strict mode that will throw the {@link MapStrictModeException}
* whenever the map would fail silently otherwise.
*/
public class MapStrictMode {
private static volatile boolean strictModeEnabled;
/**
* Set the strict mode that will throw the {@link MapStrictModeException}
* whenever the map would fail silently otherwise.
*
* @param strictModeEnabled true to enable the strict mode, false otherwise
*/
public static synchronized void setStrictModeEnabled(boolean strictModeEnabled) {
MapStrictMode.strictModeEnabled = strictModeEnabled;
}
/**
* Internal use. Called whenever the strict mode violation occurs.
*/
public static void strictModeViolation(String message) {
if (strictModeEnabled) {
throw new MapStrictModeException(message);
}
}
/**
* Internal use. Called whenever the strict mode violation occurs.
*/
public static void strictModeViolation(String message, Throwable throwable) {
if (strictModeEnabled) {
throw new MapStrictModeException(String.format("%s - %s", message, throwable));
}
}
/**
* Internal use. Called whenever the strict mode violation occurs.
*/
public static void strictModeViolation(Throwable throwable) {
if (strictModeEnabled) {
throw new MapStrictModeException(String.format("%s", throwable));
}
}
}

View File

@@ -0,0 +1,7 @@
package com.mapbox.mapboxsdk;
class MapStrictModeException extends RuntimeException {
MapStrictModeException(String message) {
super(String.format("Map detected an error that would fail silently otherwise: %s", message));
}
}

View File

@@ -0,0 +1,225 @@
package com.mapbox.mapboxsdk;
import android.annotation.SuppressLint;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import com.mapbox.mapboxsdk.constants.MapboxConstants;
import com.mapbox.mapboxsdk.exceptions.MapboxConfigurationException;
import com.mapbox.mapboxsdk.log.Logger;
import com.mapbox.mapboxsdk.maps.TelemetryDefinition;
import com.mapbox.mapboxsdk.net.ConnectivityReceiver;
import com.mapbox.mapboxsdk.storage.FileSource;
import com.mapbox.mapboxsdk.utils.ThreadUtils;
/**
* The entry point to initialize the Mapbox Android SDK.
* <p>
* Obtain a reference by calling {@link #getInstance(Context, String)}. Usually this class is configured in
* Application#onCreate() and is responsible for the active access token, application context, and
* connectivity state.
* </p>
*/
@UiThread
@SuppressLint("StaticFieldLeak")
public final class Mapbox {
private static final String TAG = "Mbgl-Mapbox";
private static ModuleProvider moduleProvider;
private static Mapbox INSTANCE;
private Context context;
@Nullable
private String accessToken;
@Nullable
private TelemetryDefinition telemetry;
@Nullable
private AccountsManager accounts;
/**
* Get an instance of Mapbox.
* <p>
* This class manages the Mapbox access token, application context, and connectivity state.
* </p>
*
* @param context Android context which holds or is an application context
* @param accessToken Mapbox access token
* @return the single instance of Mapbox
*/
@UiThread
@NonNull
public static synchronized Mapbox getInstance(@NonNull Context context, @Nullable String accessToken) {
ThreadUtils.init(context);
ThreadUtils.checkThread(TAG);
if (INSTANCE == null) {
Context appContext = context.getApplicationContext();
FileSource.initializeFileDirsPaths(appContext);
INSTANCE = new Mapbox(appContext, accessToken);
if (isAccessTokenValid(accessToken)) {
initializeTelemetry();
INSTANCE.accounts = new AccountsManager();
}
ConnectivityReceiver.instance(appContext);
}
return INSTANCE;
}
Mapbox(@NonNull Context context, @Nullable String accessToken) {
this.context = context;
this.accessToken = accessToken;
}
/**
* Get the current active access token for this application.
*
* @return Mapbox access token
*/
@Nullable
public static String getAccessToken() {
validateMapbox();
return INSTANCE.accessToken;
}
/**
* Set the current active accessToken.
*/
public static void setAccessToken(String accessToken) {
validateMapbox();
INSTANCE.accessToken = accessToken;
// cleanup telemetry which is dependent on an access token
if (INSTANCE.telemetry != null) {
INSTANCE.telemetry.disableTelemetrySession();
INSTANCE.telemetry = null;
}
// initialize components dependent on a token
if (isAccessTokenValid(accessToken)) {
initializeTelemetry();
INSTANCE.accounts = new AccountsManager();
} else {
INSTANCE.accounts = null;
}
FileSource.getInstance(getApplicationContext()).setAccessToken(accessToken);
}
/**
* Returns a SKU token, refreshed if necessary. This method is meant for internal SDK
* usage only.
*
* @return the SKU token
*/
public static String getSkuToken() {
if (INSTANCE.accounts == null) {
throw new MapboxConfigurationException(
"A valid access token parameter is required when using a Mapbox service."
+ "\nPlease see https://www.mapbox.com/help/create-api-access-token/ to learn how to create one."
+ "\nMore information in this guide https://www.mapbox.com/help/first-steps-android-sdk/#access-tokens."
+ "Currently provided token is: " + INSTANCE.accessToken);
}
return INSTANCE.accounts.getSkuToken();
}
/**
* Application context
*
* @return the application context
*/
@NonNull
public static Context getApplicationContext() {
validateMapbox();
return INSTANCE.context;
}
/**
* Manually sets the connectivity state of the app. This is useful for apps which control their
* own connectivity state and want to bypass any checks to the ConnectivityManager.
*
* @param connected flag to determine the connectivity state, true for connected, false for
* disconnected, and null for ConnectivityManager to determine.
*/
public static synchronized void setConnected(Boolean connected) {
validateMapbox();
ConnectivityReceiver.instance(INSTANCE.context).setConnected(connected);
}
/**
* Determines whether we have an internet connection available. Please do not rely on this
* method in your apps. This method is used internally by the SDK.
*
* @return true if there is an internet connection, false otherwise
*/
public static synchronized Boolean isConnected() {
validateMapbox();
return ConnectivityReceiver.instance(INSTANCE.context).isConnected();
}
/**
* Initializes telemetry
*/
private static void initializeTelemetry() {
try {
INSTANCE.telemetry = getModuleProvider().obtainTelemetry();
} catch (Exception exception) {
String message = "Error occurred while initializing telemetry";
Logger.e(TAG, message, exception);
MapStrictMode.strictModeViolation(message, exception);
}
}
/**
* Get an instance of Telemetry if initialised
*
* @return instance of telemetry
*/
@Nullable
public static TelemetryDefinition getTelemetry() {
return INSTANCE.telemetry;
}
/**
* Get the module provider
*
* @return moduleProvider
*/
@NonNull
public static ModuleProvider getModuleProvider() {
if (moduleProvider == null) {
moduleProvider = new ModuleProviderImpl();
}
return moduleProvider;
}
/**
* Runtime validation of Mapbox creation.
*/
private static void validateMapbox() {
if (INSTANCE == null) {
throw new MapboxConfigurationException();
}
}
/**
* Runtime validation of Mapbox access token
*
* @param accessToken the access token to validate
* @return true is valid, false otherwise
*/
static boolean isAccessTokenValid(@Nullable String accessToken) {
if (accessToken == null) {
return false;
}
accessToken = accessToken.trim().toLowerCase(MapboxConstants.MAPBOX_LOCALE);
return accessToken.length() != 0 && (accessToken.startsWith("pk.") || accessToken.startsWith("sk."));
}
/**
* Internal use. Check if the {@link Mapbox#INSTANCE} is present.
*/
public static boolean hasInstance() {
return INSTANCE != null;
}
}

View File

@@ -0,0 +1,38 @@
package com.mapbox.mapboxsdk;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.mapbox.mapboxsdk.http.HttpRequest;
import com.mapbox.mapboxsdk.maps.TelemetryDefinition;
/**
* Injects concrete instances of configurable abstractions
*/
public interface ModuleProvider {
/**
* Create a new concrete implementation of HttpRequest.
*
* @return a new instance of an HttpRequest
*/
@NonNull
HttpRequest createHttpRequest();
/**
* Get the concrete implementation of TelemetryDefinition
*
* @return a single instance of Telemetry
*/
@Nullable
TelemetryDefinition obtainTelemetry();
/**
* Get the concrete implementation of LibraryLoaderProvider
*
* @return a new instance of LibraryLoaderProvider
*/
@NonNull
LibraryLoaderProvider createLibraryLoaderProvider();
}

View File

@@ -0,0 +1,30 @@
package com.mapbox.mapboxsdk;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.mapbox.mapboxsdk.http.HttpRequest;
import com.mapbox.mapboxsdk.maps.TelemetryDefinition;
import com.mapbox.mapboxsdk.module.http.HttpRequestImpl;
import com.mapbox.mapboxsdk.module.loader.LibraryLoaderProviderImpl;
import com.mapbox.mapboxsdk.module.telemetry.TelemetryImpl;
public class ModuleProviderImpl implements ModuleProvider {
@Override
@NonNull
public HttpRequest createHttpRequest() {
return new HttpRequestImpl();
}
@Override
@Nullable
public TelemetryDefinition obtainTelemetry() {
return new TelemetryImpl();
}
@NonNull
@Override
public LibraryLoaderProvider createLibraryLoaderProvider() {
return new LibraryLoaderProviderImpl();
}
}

View File

@@ -0,0 +1,153 @@
package com.mapbox.mapboxsdk.annotations;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
/**
* Annotation is an overlay on top of a Map.
* <p>
* Known subclasses are {@link Polygon}, {@link Polyline} and {@link Marker}.
* </p>
* <p>
* This class manages attachment to a map and identification, but does not require
* content to be placed at a geographical point.
* </p>
* @deprecated As of 7.0.0,
* use <a href="https://github.com/mapbox/mapbox-plugins-android/tree/master/plugin-annotation">
* Mapbox Annotation Plugin</a> instead
*/
@Deprecated
public abstract class Annotation implements Comparable<Annotation> {
/**
* <p>
* The annotation id
* </p>
* Internal C++ id is stored as unsigned int.
*/
private long id = -1; // -1 unless added to a MapView
protected MapboxMap mapboxMap;
protected MapView mapView;
protected Annotation() {
}
/**
* <p>
* Gets the annotation's unique ID.
* </p>
* This ID is unique for a MapView instance and is suitable for associating your own extra
* data with.
*
* @return the assigned id.
*/
public long getId() {
return id;
}
/**
* Do not use this method, used internally by the SDK.
*/
public void remove() {
if (mapboxMap == null) {
return;
}
mapboxMap.removeAnnotation(this);
}
/**
* Do not use this method, used internally by the SDK.
*
* @param id the assigned id
*/
public void setId(long id) {
this.id = id;
}
/**
* Do not use this method, used internally by the SDK.
*
* @param mapboxMap the hosting mapbox map
*/
public void setMapboxMap(MapboxMap mapboxMap) {
this.mapboxMap = mapboxMap;
}
/**
* Gets the hosting mapbox map.
*
* @return the MapboxMap
*/
protected MapboxMap getMapboxMap() {
return mapboxMap;
}
/**
* Do not use this method, used internally by the SDK.
*
* @param mapView the hosting map view
*/
public void setMapView(MapView mapView) {
this.mapView = mapView;
}
/**
* Gets the hosting map view.
*
* @return The MapView
*/
protected MapView getMapView() {
return mapView;
}
/**
* Compares this Annotation object with another Annotation.
*
* @param annotation Another Annotation to compare with this object.
* @return returns 0 if id's match, 1 if id is lower, -1 if id is higher of another Annotation
*/
@Override
public int compareTo(@NonNull Annotation annotation) {
if (id < annotation.getId()) {
return 1;
} else if (id > annotation.getId()) {
return -1;
}
return 0;
}
/**
* Checks if this Annotation object is equal to another Annotation.
*
* @param object Another Annotation to check equality with this object.
* @return returns true both id's match else returns false.
*/
@Override
public boolean equals(@Nullable Object object) {
if (this == object) {
return true;
}
if (object == null || !(object instanceof Annotation)) {
return false;
}
Annotation that = (Annotation) object;
return id == that.getId();
}
/**
* Gives an integer which can be used as the bucket number for storing elements of the set/map.
* This bucket number is the address of the element inside the set/map. There's no guarantee
* that this hash value will be consistent between different Java implementations, or even
* between different execution runs of the same program.
*
* @return integer value you can use for storing element.
*/
@Override
public int hashCode() {
return (int) (getId() ^ (getId() >>> 32));
}
}

View File

@@ -0,0 +1,31 @@
package com.mapbox.mapboxsdk.annotations;
import android.support.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Deprecated
class ArrowDirection {
@IntDef( {LEFT, RIGHT, TOP, BOTTOM})
@Retention(RetentionPolicy.SOURCE)
@interface Value {
}
static final int LEFT = 0;
static final int RIGHT = 1;
static final int TOP = 2;
static final int BOTTOM = 3;
@Value
private final int value;
ArrowDirection(@Value int value) {
this.value = value;
}
@Value
public int getValue() {
return value;
}
}

View File

@@ -0,0 +1,124 @@
package com.mapbox.mapboxsdk.annotations;
import android.os.Parcelable;
import com.mapbox.mapboxsdk.geometry.LatLng;
/**
* Abstract builder class for composing custom Marker objects.
* <p>
* Extending this class requires implementing Parceable interface.
*
* @param <U> Type of the marker to be composed
* @param <T> Type of the builder to be used for composing a custom Marker
* @deprecated As of 7.0.0,
* use <a href="https://github.com/mapbox/mapbox-plugins-android/tree/master/plugin-annotation">
* Mapbox Annotation Plugin</a> instead
*/
@Deprecated
public abstract class BaseMarkerOptions<U extends Marker, T extends BaseMarkerOptions<U, T>> implements Parcelable {
protected LatLng position;
protected String snippet;
protected String title;
protected Icon icon;
/**
* Set the geographical location of the Marker.
*
* @param position the location to position the {@link Marker}.
* @return the object for which the method was called.
*/
public T position(LatLng position) {
this.position = position;
return getThis();
}
/**
* Set the snippet of the Marker.
*
* @param snippet the snippet of the {@link Marker}.
* @return the object for which the method was called.
*/
public T snippet(String snippet) {
this.snippet = snippet;
return getThis();
}
/**
* Set the title of the Marker.
*
* @param title the title of the {@link Marker}.
* @return the object for which the method was called.
*/
public T title(String title) {
this.title = title;
return getThis();
}
/**
* Set the icon of the Marker.
*
* @param icon the icon of the {@link Marker}.
* @return the object for which the method was called.
*/
public T icon(Icon icon) {
this.icon = icon;
return getThis();
}
/**
* Set the icon of the Marker.
*
* @param icon the icon of the {@link Marker}.
* @return the object for which the method was called.
*/
public T setIcon(Icon icon) {
return icon(icon);
}
/**
* Set the geographical location of the Marker.
*
* @param position the location to position the {@link Marker}.
* @return the object for which the method was called.
*/
public T setPosition(LatLng position) {
return position(position);
}
/**
* Set the snippet of the Marker.
*
* @param snippet the snippet of the {@link Marker}.
* @return the object for which the method was called.
*/
public T setSnippet(String snippet) {
return snippet(snippet);
}
/**
* Set the title of the Marker.
*
* @param title the title of the {@link Marker}.
* @return the object for which the method was called.
*/
public T setTitle(String title) {
return title(title);
}
/**
* Get the instance of the object for which this method was called.
*
* @return the object for which the this method was called.
*/
public abstract T getThis();
/**
* Get the Marker.
*
* @return the Marker created from this builder.
*/
public abstract U getMarker();
}

View File

@@ -0,0 +1,82 @@
package com.mapbox.mapboxsdk.annotations;
import android.support.annotation.Keep;
import android.support.annotation.NonNull;
import com.mapbox.mapboxsdk.geometry.LatLng;
import java.util.ArrayList;
import java.util.List;
/**
* Multipoint is an abstract annotation for combining geographical locations.
*
* @deprecated As of 7.0.0,
* use <a href="https://github.com/mapbox/mapbox-plugins-android/tree/master/plugin-annotation">
* Mapbox Annotation Plugin</a> instead
*/
@Deprecated
public abstract class BasePointCollection extends Annotation {
@Keep
private List<LatLng> points;
@Keep
private float alpha = 1.0f;
protected BasePointCollection() {
super();
points = new ArrayList<>();
}
/**
* Returns a copy of the points.
*
* @return A {@link List} of points.
*/
@NonNull
public List<LatLng> 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 A {@link List} of {@link LatLng} points making up the polyline.
*/
public void setPoints(@NonNull List<LatLng> points) {
this.points = new ArrayList<>(points);
update();
}
/**
* Add a point to the polyline.
*
* @param point A {@link LatLng} point to be added.
*/
public void addPoint(LatLng point) {
points.add(point);
update();
}
/**
* Value between 0 and 1 defining the polyline alpha.
*
* @return float value between 0 and 1.
*/
public float getAlpha() {
return alpha;
}
/**
* Set this {@link BasePointCollection}s alpha.
*
* @param alpha float value between 0 and 1.
*/
public void setAlpha(float alpha) {
this.alpha = alpha;
update();
}
abstract void update();
}

View File

@@ -0,0 +1,315 @@
package com.mapbox.mapboxsdk.annotations;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
@Deprecated
class Bubble extends Drawable {
private RectF rect;
private float arrowWidth;
private float arrowHeight;
private float arrowPosition;
private float cornersRadius;
@NonNull
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private float strokeWidth;
private Paint strokePaint;
private Path strokePath;
@NonNull
private Path path = new Path();
Bubble(@NonNull RectF rect, @NonNull ArrowDirection arrowDirection, float arrowWidth, float arrowHeight,
float arrowPosition, float cornersRadius, int bubbleColor, float strokeWidth, int strokeColor) {
this.rect = rect;
this.arrowWidth = arrowWidth;
this.arrowHeight = arrowHeight;
this.arrowPosition = arrowPosition;
this.cornersRadius = cornersRadius;
paint.setColor(bubbleColor);
this.strokeWidth = strokeWidth;
if (strokeWidth > 0) {
strokePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
strokePaint.setColor(strokeColor);
strokePath = new Path();
initPath(arrowDirection, path, strokeWidth);
initPath(arrowDirection, strokePath, 0);
} else {
initPath(arrowDirection, path, 0);
}
}
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
}
@Override
public void draw(@NonNull Canvas canvas) {
if (strokeWidth > 0) {
canvas.drawPath(strokePath, strokePaint);
}
canvas.drawPath(path, paint);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public void setAlpha(int alpha) {
paint.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter cf) {
paint.setColorFilter(cf);
}
@Override
public int getIntrinsicWidth() {
return (int) rect.width();
}
@Override
public int getIntrinsicHeight() {
return (int) rect.height();
}
private void initPath(@NonNull ArrowDirection arrowDirection, @NonNull Path path, float strokeWidth) {
switch (arrowDirection.getValue()) {
case ArrowDirection.LEFT:
if (cornersRadius <= 0) {
initLeftSquarePath(rect, path, strokeWidth);
break;
}
if (strokeWidth > 0 && strokeWidth > cornersRadius) {
initLeftSquarePath(rect, path, strokeWidth);
break;
}
initLeftRoundedPath(rect, path, strokeWidth);
break;
case ArrowDirection.TOP:
if (cornersRadius <= 0) {
initTopSquarePath(rect, path, strokeWidth);
break;
}
if (strokeWidth > 0 && strokeWidth > cornersRadius) {
initTopSquarePath(rect, path, strokeWidth);
break;
}
initTopRoundedPath(rect, path, strokeWidth);
break;
case ArrowDirection.RIGHT:
if (cornersRadius <= 0) {
initRightSquarePath(rect, path, strokeWidth);
break;
}
if (strokeWidth > 0 && strokeWidth > cornersRadius) {
initRightSquarePath(rect, path, strokeWidth);
break;
}
initRightRoundedPath(rect, path, strokeWidth);
break;
case ArrowDirection.BOTTOM:
if (cornersRadius <= 0) {
initBottomSquarePath(rect, path, strokeWidth);
break;
}
if (strokeWidth > 0 && strokeWidth > cornersRadius) {
initBottomSquarePath(rect, path, strokeWidth);
break;
}
initBottomRoundedPath(rect, path, strokeWidth);
break;
}
}
private void initLeftSquarePath(@NonNull RectF rect, @NonNull Path path, float strokeWidth) {
path.moveTo(arrowWidth + rect.left + strokeWidth, rect.top + strokeWidth);
path.lineTo(rect.width() - strokeWidth, rect.top + strokeWidth);
path.lineTo(rect.right - strokeWidth, rect.bottom - strokeWidth);
path.lineTo(rect.left + arrowWidth + strokeWidth, rect.bottom - strokeWidth);
path.lineTo(rect.left + arrowWidth + strokeWidth, arrowHeight + arrowPosition - (strokeWidth / 2));
path.lineTo(rect.left + strokeWidth + strokeWidth, arrowPosition + arrowHeight / 2);
path.lineTo(rect.left + arrowWidth + strokeWidth, arrowPosition + (strokeWidth / 2));
path.lineTo(rect.left + arrowWidth + strokeWidth, rect.top + strokeWidth);
path.close();
}
private void initLeftRoundedPath(@NonNull RectF rect, @NonNull Path path, float strokeWidth) {
path.moveTo(arrowWidth + rect.left + cornersRadius + strokeWidth, rect.top + strokeWidth);
path.lineTo(rect.width() - cornersRadius - strokeWidth, rect.top + strokeWidth);
path.arcTo(new RectF(rect.right - cornersRadius, rect.top + strokeWidth, rect.right - strokeWidth,
cornersRadius + rect.top), 270, 90);
path.lineTo(rect.right - strokeWidth, rect.bottom - cornersRadius - strokeWidth);
path.arcTo(new RectF(rect.right - cornersRadius, rect.bottom - cornersRadius,
rect.right - strokeWidth, rect.bottom - strokeWidth), 0, 90);
path.lineTo(rect.left + arrowWidth + cornersRadius + strokeWidth, rect.bottom - strokeWidth);
path.arcTo(new RectF(rect.left + arrowWidth + strokeWidth, rect.bottom - cornersRadius,
cornersRadius + rect.left + arrowWidth, rect.bottom - strokeWidth), 90, 90);
path.lineTo(rect.left + arrowWidth + strokeWidth, arrowHeight + arrowPosition - (strokeWidth / 2));
path.lineTo(rect.left + strokeWidth + strokeWidth, arrowPosition + arrowHeight / 2);
path.lineTo(rect.left + arrowWidth + strokeWidth, arrowPosition + (strokeWidth / 2));
path.lineTo(rect.left + arrowWidth + strokeWidth, rect.top + cornersRadius + strokeWidth);
path.arcTo(new RectF(rect.left + arrowWidth + strokeWidth, rect.top + strokeWidth, cornersRadius
+ rect.left + arrowWidth, cornersRadius + rect.top), 180, 90);
path.close();
}
private void initTopSquarePath(@NonNull RectF rect, @NonNull Path path, float strokeWidth) {
path.moveTo(rect.left + arrowPosition + strokeWidth, rect.top + arrowHeight + strokeWidth);
path.lineTo(rect.left + arrowPosition + (strokeWidth / 2), rect.top + arrowHeight + strokeWidth);
path.lineTo(rect.left + arrowWidth / 2 + arrowPosition, rect.top + strokeWidth + strokeWidth);
path.lineTo(rect.left + arrowWidth + arrowPosition - (strokeWidth / 2), rect.top + arrowHeight + strokeWidth);
path.lineTo(rect.right - strokeWidth, rect.top + arrowHeight + strokeWidth);
path.lineTo(rect.right - strokeWidth, rect.bottom - strokeWidth);
path.lineTo(rect.left + strokeWidth, rect.bottom - strokeWidth);
path.lineTo(rect.left + strokeWidth, rect.top + arrowHeight + strokeWidth);
path.lineTo(rect.left + arrowPosition + strokeWidth, rect.top + arrowHeight + strokeWidth);
path.close();
}
private void initTopRoundedPath(@NonNull RectF rect, @NonNull Path path, float strokeWidth) {
path.moveTo(rect.left + Math.min(arrowPosition, cornersRadius) + strokeWidth, rect.top + arrowHeight
+ strokeWidth);
path.lineTo(rect.left + arrowPosition + (strokeWidth / 2), rect.top + arrowHeight + strokeWidth);
path.lineTo(rect.left + arrowWidth / 2 + arrowPosition, rect.top + strokeWidth + strokeWidth);
path.lineTo(rect.left + arrowWidth + arrowPosition - (strokeWidth / 2), rect.top + arrowHeight + strokeWidth);
path.lineTo(rect.right - cornersRadius - strokeWidth, rect.top + arrowHeight + strokeWidth);
path.arcTo(new RectF(rect.right - cornersRadius,
rect.top + arrowHeight + strokeWidth, rect.right - strokeWidth, cornersRadius + rect.top + arrowHeight),
270, 90);
path.lineTo(rect.right - strokeWidth, rect.bottom - cornersRadius - strokeWidth);
path.arcTo(new RectF(rect.right - cornersRadius, rect.bottom - cornersRadius,
rect.right - strokeWidth, rect.bottom - strokeWidth), 0, 90);
path.lineTo(rect.left + cornersRadius + strokeWidth, rect.bottom - strokeWidth);
path.arcTo(new RectF(rect.left + strokeWidth, rect.bottom - cornersRadius,
cornersRadius + rect.left, rect.bottom - strokeWidth), 90, 90);
path.lineTo(rect.left + strokeWidth, rect.top + arrowHeight + cornersRadius + strokeWidth);
path.arcTo(new RectF(rect.left + strokeWidth, rect.top + arrowHeight + strokeWidth, cornersRadius
+ rect.left, cornersRadius + rect.top + arrowHeight), 180, 90);
path.close();
}
private void initRightSquarePath(@NonNull RectF rect, @NonNull Path path, float strokeWidth) {
path.moveTo(rect.left + strokeWidth, rect.top + strokeWidth);
path.lineTo(rect.width() - arrowWidth - strokeWidth, rect.top + strokeWidth);
path.lineTo(rect.right - arrowWidth - strokeWidth, arrowPosition + (strokeWidth / 2));
path.lineTo(rect.right - strokeWidth - strokeWidth, arrowPosition + arrowHeight / 2);
path.lineTo(rect.right - arrowWidth - strokeWidth, arrowPosition + arrowHeight - (strokeWidth / 2));
path.lineTo(rect.right - arrowWidth - strokeWidth, rect.bottom - strokeWidth);
path.lineTo(rect.left + strokeWidth, rect.bottom - strokeWidth);
path.lineTo(rect.left + strokeWidth, rect.top + strokeWidth);
path.close();
}
private void initRightRoundedPath(@NonNull RectF rect, @NonNull Path path, float strokeWidth) {
path.moveTo(rect.left + cornersRadius + strokeWidth, rect.top + strokeWidth);
path.lineTo(rect.width() - cornersRadius - arrowWidth - strokeWidth, rect.top + strokeWidth);
path.arcTo(new RectF(rect.right - cornersRadius - arrowWidth,
rect.top + strokeWidth, rect.right - arrowWidth - strokeWidth, cornersRadius + rect.top), 270, 90);
path.lineTo(rect.right - arrowWidth - strokeWidth, arrowPosition + (strokeWidth / 2));
path.lineTo(rect.right - strokeWidth - strokeWidth, arrowPosition + arrowHeight / 2);
path.lineTo(rect.right - arrowWidth - strokeWidth, arrowPosition + arrowHeight - (strokeWidth / 2));
path.lineTo(rect.right - arrowWidth - strokeWidth, rect.bottom - cornersRadius - strokeWidth);
path.arcTo(new RectF(rect.right - cornersRadius - arrowWidth, rect.bottom - cornersRadius,
rect.right - arrowWidth - strokeWidth, rect.bottom - strokeWidth), 0, 90);
path.lineTo(rect.left + arrowWidth + strokeWidth, rect.bottom - strokeWidth);
path.arcTo(new RectF(rect.left + strokeWidth, rect.bottom - cornersRadius,
cornersRadius + rect.left, rect.bottom - strokeWidth), 90, 90);
path.arcTo(new RectF(rect.left + strokeWidth, rect.top + strokeWidth, cornersRadius
+ rect.left, cornersRadius + rect.top), 180, 90);
path.close();
}
private void initBottomSquarePath(@NonNull RectF rect, @NonNull Path path, float strokeWidth) {
path.moveTo(rect.left + strokeWidth, rect.top + strokeWidth);
path.lineTo(rect.right - strokeWidth, rect.top + strokeWidth);
path.lineTo(rect.right - strokeWidth, rect.bottom - arrowHeight - strokeWidth);
path.lineTo(rect.left + arrowWidth + arrowPosition - (strokeWidth / 2), rect.bottom - arrowHeight - strokeWidth);
path.lineTo(rect.left + arrowPosition + arrowWidth / 2, rect.bottom - strokeWidth - strokeWidth);
path.lineTo(rect.left + arrowPosition + (strokeWidth / 2), rect.bottom - arrowHeight - strokeWidth);
path.lineTo(rect.left + arrowPosition + strokeWidth, rect.bottom - arrowHeight - strokeWidth);
path.lineTo(rect.left + strokeWidth, rect.bottom - arrowHeight - strokeWidth);
path.lineTo(rect.left + strokeWidth, rect.top + strokeWidth);
path.close();
}
private void initBottomRoundedPath(@NonNull RectF rect, @NonNull Path path, float strokeWidth) {
path.moveTo(rect.left + cornersRadius + strokeWidth, rect.top + strokeWidth);
path.lineTo(rect.width() - cornersRadius - strokeWidth, rect.top + strokeWidth);
path.arcTo(new RectF(rect.right - cornersRadius,
rect.top + strokeWidth, rect.right - strokeWidth, cornersRadius + rect.top), 270, 90);
path.lineTo(rect.right - strokeWidth, rect.bottom - arrowHeight - cornersRadius - strokeWidth);
path.arcTo(new RectF(rect.right - cornersRadius, rect.bottom - cornersRadius - arrowHeight,
rect.right - strokeWidth, rect.bottom - arrowHeight - strokeWidth), 0, 90);
path.lineTo(rect.left + arrowWidth + arrowPosition - (strokeWidth / 2), rect.bottom - arrowHeight - strokeWidth);
path.lineTo(rect.left + arrowPosition + arrowWidth / 2, rect.bottom - strokeWidth - strokeWidth);
path.lineTo(rect.left + arrowPosition + (strokeWidth / 2), rect.bottom - arrowHeight - strokeWidth);
path.lineTo(rect.left + Math.min(cornersRadius, arrowPosition) + strokeWidth, rect.bottom - arrowHeight
- strokeWidth);
path.arcTo(new RectF(rect.left + strokeWidth, rect.bottom - cornersRadius - arrowHeight,
cornersRadius + rect.left, rect.bottom - arrowHeight - strokeWidth), 90, 90);
path.lineTo(rect.left + strokeWidth, rect.top + cornersRadius + strokeWidth);
path.arcTo(new RectF(rect.left + strokeWidth, rect.top + strokeWidth, cornersRadius
+ rect.left, cornersRadius + rect.top), 180, 90);
path.close();
}
}

View File

@@ -0,0 +1,350 @@
package com.mapbox.mapboxsdk.annotations;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.RectF;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.widget.LinearLayout;
import com.mapbox.mapboxsdk.R;
/**
* Bubble View for Android with custom stroke width and color, arrow size, position and direction.
* @deprecated As of 7.0.0,
* use <a href="https://github.com/mapbox/mapbox-plugins-android/tree/master/plugin-annotation">
* Mapbox Annotation Plugin</a> instead
*/
@Deprecated
public class BubbleLayout extends LinearLayout {
public static final float DEFAULT_STROKE_WIDTH = -1;
private ArrowDirection arrowDirection;
private float arrowWidth;
private float arrowHeight;
private float arrowPosition;
private float cornersRadius;
private Bubble bubble;
private int bubbleColor;
private float strokeWidth;
private int strokeColor;
/**
* Creates an instance of bubble layout.
*
* @param context The context used to inflate this bubble layout
*/
public BubbleLayout(@NonNull Context context) {
this(context, null, 0);
}
/**
* Creates an instance of bubble layout.
*
* @param context The context used to inflate this bubble layout
* @param attrs The attribute set to initialise this bubble layout from
*/
public BubbleLayout(@NonNull Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
/**
* Creates an instance of bubble layout.
*
* @param context The context used to inflate this bubble layout
* @param attrs The attribute set to initialise this bubble layout from
* @param defStyleAttr The default style to apply this bubble layout with
*/
public BubbleLayout(@NonNull Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.mapbox_BubbleLayout);
@ArrowDirection.Value
int location = a.getInt(R.styleable.mapbox_BubbleLayout_mapbox_bl_arrowDirection,
ArrowDirection.LEFT);
arrowDirection = new ArrowDirection(location);
arrowWidth = a.getDimension(R.styleable.mapbox_BubbleLayout_mapbox_bl_arrowWidth,
convertDpToPixel(8, context));
arrowHeight = a.getDimension(R.styleable.mapbox_BubbleLayout_mapbox_bl_arrowHeight,
convertDpToPixel(8, context));
arrowPosition = a.getDimension(R.styleable.mapbox_BubbleLayout_mapbox_bl_arrowPosition,
convertDpToPixel(12, context));
cornersRadius = a.getDimension(R.styleable.mapbox_BubbleLayout_mapbox_bl_cornersRadius, 0);
bubbleColor = a.getColor(R.styleable.mapbox_BubbleLayout_mapbox_bl_bubbleColor, Color.WHITE);
strokeWidth =
a.getDimension(R.styleable.mapbox_BubbleLayout_mapbox_bl_strokeWidth, DEFAULT_STROKE_WIDTH);
strokeColor = a.getColor(R.styleable.mapbox_BubbleLayout_mapbox_bl_strokeColor, Color.GRAY);
a.recycle();
initPadding();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
initDrawable(0, getWidth(), 0, getHeight());
}
@Override
protected void dispatchDraw(@NonNull Canvas canvas) {
if (bubble != null) {
bubble.draw(canvas);
}
super.dispatchDraw(canvas);
}
static float convertDpToPixel(float dp, Context context) {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return dp * (metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT);
}
/**
* Get the arrow direction.
*
* @return the arrow direction
*/
public ArrowDirection getArrowDirection() {
return arrowDirection;
}
/**
* Set the arrow direction.
*
* @param arrowDirection The direction of the arrow
* @return this
*/
@NonNull
public BubbleLayout setArrowDirection(ArrowDirection arrowDirection) {
resetPadding();
this.arrowDirection = arrowDirection;
initPadding();
return this;
}
/**
* Get the arrow width.
*
* @return the width of the arrow
*/
public float getArrowWidth() {
return arrowWidth;
}
/**
* Set the arrow width.
*
* @param arrowWidth The width of the arrow
* @return this
*/
@NonNull
public BubbleLayout setArrowWidth(float arrowWidth) {
resetPadding();
this.arrowWidth = arrowWidth;
initPadding();
return this;
}
/**
* Get the arrow height
*
* @return the height of the arrow
*/
public float getArrowHeight() {
return arrowHeight;
}
/**
* Set the arrow height.
*
* @param arrowHeight The height of the arrow
* @return this
*/
@NonNull
public BubbleLayout setArrowHeight(float arrowHeight) {
resetPadding();
this.arrowHeight = arrowHeight;
initPadding();
return this;
}
/**
* Get the arrow position.
*
* @return the arrow position
*/
public float getArrowPosition() {
return arrowPosition;
}
/**
* Get the arrow position.
*
* @param arrowPosition The arrow position
* @return this
*/
@NonNull
public BubbleLayout setArrowPosition(float arrowPosition) {
resetPadding();
this.arrowPosition = arrowPosition;
initPadding();
return this;
}
/**
* Get the corner radius
*
* @return the corner radius
*/
public float getCornersRadius() {
return cornersRadius;
}
/**
* Set the corner radius
*
* @param cornersRadius The corner radius
* @return this
*/
@NonNull
public BubbleLayout setCornersRadius(float cornersRadius) {
this.cornersRadius = cornersRadius;
requestLayout();
return this;
}
/**
* Get the bubble color.
*
* @return the bubble color
*/
public int getBubbleColor() {
return bubbleColor;
}
/**
* Set the bubble color.
*
* @param bubbleColor The buble color
* @return this
*/
@NonNull
public BubbleLayout setBubbleColor(int bubbleColor) {
this.bubbleColor = bubbleColor;
requestLayout();
return this;
}
/**
* Get stroke width.
*
* @return the stroke width
*/
public float getStrokeWidth() {
return strokeWidth;
}
/**
* Set the stroke width.
*
* @param strokeWidth The stroke width
* @return this
*/
@NonNull
public BubbleLayout setStrokeWidth(float strokeWidth) {
resetPadding();
this.strokeWidth = strokeWidth;
initPadding();
return this;
}
/**
* Get the stroke color.
*
* @return the stroke color
*/
public int getStrokeColor() {
return strokeColor;
}
/**
* Set the stroke color.
*
* @param strokeColor The stroke color
* @return this
*/
@NonNull
public BubbleLayout setStrokeColor(int strokeColor) {
this.strokeColor = strokeColor;
requestLayout();
return this;
}
private void initPadding() {
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
switch (arrowDirection.getValue()) {
case ArrowDirection.LEFT:
paddingLeft += arrowWidth;
break;
case ArrowDirection.RIGHT:
paddingRight += arrowWidth;
break;
case ArrowDirection.TOP:
paddingTop += arrowHeight;
break;
case ArrowDirection.BOTTOM:
paddingBottom += arrowHeight;
break;
}
if (strokeWidth > 0) {
paddingLeft += strokeWidth;
paddingRight += strokeWidth;
paddingTop += strokeWidth;
paddingBottom += strokeWidth;
}
setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
}
private void initDrawable(int left, int right, int top, int bottom) {
if (right < left || bottom < top) {
return;
}
RectF rectF = new RectF(left, top, right, bottom);
bubble = new Bubble(rectF, arrowDirection, arrowWidth, arrowHeight, arrowPosition, cornersRadius,
bubbleColor, strokeWidth, strokeColor);
}
private void resetPadding() {
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
switch (arrowDirection.getValue()) {
case ArrowDirection.LEFT:
paddingLeft -= arrowWidth;
break;
case ArrowDirection.RIGHT:
paddingRight -= arrowWidth;
break;
case ArrowDirection.TOP:
paddingTop -= arrowHeight;
break;
case ArrowDirection.BOTTOM:
paddingBottom -= arrowHeight;
break;
}
if (strokeWidth > 0) {
paddingLeft -= strokeWidth;
paddingRight -= strokeWidth;
paddingTop -= strokeWidth;
paddingBottom -= strokeWidth;
}
setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
}
}

View File

@@ -0,0 +1,35 @@
package com.mapbox.mapboxsdk.annotations;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.NonNull;
import android.view.ViewGroup;
import android.widget.PopupWindow;
import com.mapbox.mapboxsdk.R;
@Deprecated
class BubblePopupHelper {
@NonNull
static PopupWindow create(@NonNull Context context, @NonNull BubbleLayout bubbleLayout) {
PopupWindow popupWindow = new PopupWindow(context);
popupWindow.setContentView(bubbleLayout);
popupWindow.setOutsideTouchable(true);
popupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
popupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
popupWindow.setAnimationStyle(android.R.style.Animation_Dialog);
// change background color to transparent
Drawable drawable;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
drawable = context.getDrawable(R.drawable.mapbox_popup_window_transparent);
} else {
drawable = context.getResources().getDrawable(R.drawable.mapbox_popup_window_transparent);
}
popupWindow.setBackgroundDrawable(drawable);
return popupWindow;
}
}

View File

@@ -0,0 +1,124 @@
package com.mapbox.mapboxsdk.annotations;
import android.graphics.Bitmap;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.DisplayMetrics;
import java.nio.ByteBuffer;
/**
* Icon is the visual representation of a Marker on a MapView.
*
* @deprecated As of 7.0.0,
* use <a href="https://github.com/mapbox/mapbox-plugins-android/tree/master/plugin-annotation">
* Mapbox Annotation Plugin</a> instead
*/
@Deprecated
public class Icon {
private Bitmap mBitmap;
private String mId;
Icon(String id, Bitmap bitmap) {
mId = id;
mBitmap = bitmap;
}
/**
* String identifier for this icon.
*
* @return String identifier for this icon.
*/
public String getId() {
return mId;
}
/**
* Get the bitmap being used for this icon.
*
* @return The bitmap being used for the icon.
*/
public Bitmap getBitmap() {
if (mBitmap != null && mBitmap.getConfig() != Bitmap.Config.ARGB_8888) {
mBitmap = mBitmap.copy(Bitmap.Config.ARGB_8888, false);
}
return mBitmap;
}
/**
* Get the icon bitmap scale.
* <p>
* Requires the bitmap to be set before calling this method.
* </p>
*
* @return the scale of the bitmap
*/
public float getScale() {
if (mBitmap == null) {
throw new IllegalStateException("Required to set a Icon before calling getScale");
}
float density = mBitmap.getDensity();
if (density == Bitmap.DENSITY_NONE) {
density = DisplayMetrics.DENSITY_DEFAULT;
}
return density / DisplayMetrics.DENSITY_DEFAULT;
}
/**
* Get the icon bitmap bytes.
* <p>
* Requires the bitmap to be set before calling this method.
* </p>
*
* @return the bytes of the bitmap
*/
@NonNull
public byte[] toBytes() {
if (mBitmap == null) {
throw new IllegalStateException("Required to set a Icon before calling toBytes");
}
ByteBuffer buffer = ByteBuffer.allocate(mBitmap.getRowBytes() * mBitmap.getHeight());
mBitmap.copyPixelsToBuffer(buffer);
return buffer.array();
}
/**
* Compares this icon object with another icon and determines if they match.
*
* @param object Another iconi to compare with this object.
* @return True if the icon being passed in matches this icon object. Else, false.
*/
@Override
public boolean equals(@Nullable Object object) {
if (this == object) {
return true;
}
if (object == null || getClass() != object.getClass()) {
return false;
}
Icon icon = (Icon) object;
return mBitmap.equals(icon.mBitmap) && mId.equals(icon.mId);
}
/**
* Gives an integer which can be used as the bucket number for storing elements of the set/map.
* This bucket number is the address of the element inside the set/map. There's no guarantee
* that this hash value will be consistent between different Java implementations, or even
* between different execution runs of the same program.
*
* @return integer value you can use for storing element.
*/
@Override
public int hashCode() {
int result = 0;
if (mBitmap != null) {
result = mBitmap.hashCode();
}
if (mId != null) {
result = 31 * result + mId.hashCode();
}
return result;
}
}

View File

@@ -0,0 +1,188 @@
package com.mapbox.mapboxsdk.annotations;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.util.DisplayMetrics;
import android.view.WindowManager;
import com.mapbox.mapboxsdk.MapStrictMode;
import com.mapbox.mapboxsdk.R;
import com.mapbox.mapboxsdk.exceptions.TooManyIconsException;
import com.mapbox.mapboxsdk.utils.BitmapUtils;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
/**
* Factory for creating Icons from bitmap images.
* <p>
* icon is used to display bitmaps on top of the map using {@link Marker}.
* </p>
*
* @deprecated As of 7.0.0,
* use <a href="https://github.com/mapbox/mapbox-plugins-android/tree/master/plugin-annotation">
* Mapbox Annotation Plugin</a> instead
*/
@Deprecated
public final class IconFactory {
private static final String ICON_ID_PREFIX = "com.mapbox.icons.icon_";
private Context context;
@SuppressLint("StaticFieldLeak")
private static IconFactory instance;
private Icon defaultMarker;
private BitmapFactory.Options options;
private int nextId = 0;
/**
* Get a single instance of IconFactory.
*
* @param context the context to derive the application context from
* @return the single instance of IconFactory
*/
public static synchronized IconFactory getInstance(@NonNull Context context) {
if (instance == null) {
instance = new IconFactory(context.getApplicationContext());
}
return instance;
}
private IconFactory(@NonNull Context context) {
this.context = context;
DisplayMetrics realMetrics = null;
DisplayMetrics metrics = new DisplayMetrics();
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
realMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getRealMetrics(realMetrics);
}
wm.getDefaultDisplay().getMetrics(metrics);
options = new BitmapFactory.Options();
options.inScaled = true;
options.inDensity = DisplayMetrics.DENSITY_DEFAULT;
options.inTargetDensity = metrics.densityDpi;
if (realMetrics != null) {
options.inScreenDensity = realMetrics.densityDpi;
}
}
/**
* Creates an icon from a given Bitmap image.
*
* @param bitmap image used for creating the Icon.
* @return The icon using the given Bitmap image.
*/
public Icon fromBitmap(@NonNull Bitmap bitmap) {
if (nextId < 0) {
throw new TooManyIconsException();
}
String id = ICON_ID_PREFIX + ++nextId;
return new Icon(id, bitmap);
}
/**
* Creates an icon using the resource ID of a Bitmap image.
*
* @param resourceId The resource ID of a Bitmap image.
* @return The icon that was loaded from the asset or {@code null} if failed to load.
*/
public Icon fromResource(@DrawableRes int resourceId) {
Drawable drawable = BitmapUtils.getDrawableFromRes(context, resourceId);
if (drawable instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
return fromBitmap(bitmapDrawable.getBitmap());
} else {
throw new IllegalArgumentException("Failed to decode image. The resource provided must be a Bitmap.");
}
}
/**
* Provides an icon using the default marker icon used for {@link Marker}.
*
* @return An icon with the default {@link Marker} icon.
*/
public Icon defaultMarker() {
if (defaultMarker == null) {
defaultMarker = fromResource(R.drawable.mapbox_marker_icon_default);
}
return defaultMarker;
}
private Icon fromInputStream(@NonNull InputStream is) {
Bitmap bitmap = BitmapFactory.decodeStream(is, null, options);
return fromBitmap(bitmap);
}
/**
* Creates an Icon using the name of a Bitmap image in the assets directory.
*
* @param assetName The name of a Bitmap image in the assets directory.
* @return The Icon that was loaded from the asset or null if failed to load.
*/
public Icon fromAsset(@NonNull String assetName) {
InputStream is;
try {
is = context.getAssets().open(assetName);
} catch (IOException ioException) {
MapStrictMode.strictModeViolation(ioException);
return null;
}
return fromInputStream(is);
}
/**
* Creates an Icon using the absolute file path of a Bitmap image.
*
* @param absolutePath The absolute path of the Bitmap image.
* @return The Icon that was loaded from the absolute path or null if failed to load.
*/
public Icon fromPath(@NonNull String absolutePath) {
Bitmap bitmap = BitmapFactory.decodeFile(absolutePath, options);
return fromBitmap(bitmap);
}
/**
* Create an Icon using the name of a Bitmap image file located in the internal storage.
* In particular, this calls Context#openFileInput(String).
*
* @param fileName The name of the Bitmap image file.
* @return The Icon that was loaded from the asset or null if failed to load.
* @see <a href="https://developer.android.com/guide/topics/data/data-storage.html#filesInternal">
* Using the Internal Storage</a>
*/
public Icon fromFile(@NonNull String fileName) {
FileInputStream is;
try {
is = context.openFileInput(fileName);
} catch (FileNotFoundException fileNotFoundException) {
MapStrictMode.strictModeViolation(fileNotFoundException);
return null;
}
return fromInputStream(is);
}
/**
* Create an Icon using a previously created icon identifier along with a provided Bitmap.
*
* @param iconId The Icon identifier you'd like to recreate.
* @param bitmap a Bitmap used to replace the current one.
* @return The Icon using the new Bitmap.
*/
public static Icon recreate(@NonNull String iconId, @NonNull Bitmap bitmap) {
return new Icon(iconId, bitmap);
}
}

View File

@@ -0,0 +1,351 @@
package com.mapbox.mapboxsdk.annotations;
import android.content.res.Resources;
import android.graphics.PointF;
import android.os.Build;
import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.TextView;
import com.mapbox.mapboxsdk.R;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import java.lang.ref.WeakReference;
/**
* {@code InfoWindow} is a tooltip shown when a {@link Marker} is tapped. Only
* one info window is displayed at a time. When the user clicks on a marker, the currently open info
* window will be closed and the new info window will be displayed. If the user clicks the same
* marker while its info window is currently open, the info window will be closed.
* <p>
* The info window is drawn oriented against the device's screen, centered above its associated
* marker by default. The default info window contains the title in bold and snippet text below the title.
* While either the title and
* snippet are optional, at least one is required to open the info window.
* </p>
* @deprecated As of 7.0.0,
* use <a href="https://github.com/mapbox/mapbox-plugins-android/tree/master/plugin-annotation">
* Mapbox Annotation Plugin</a> instead
*/
@Deprecated
public class InfoWindow {
private WeakReference<Marker> boundMarker;
private WeakReference<MapboxMap> mapboxMap;
protected WeakReference<View> view;
private float markerHeightOffset;
private float markerWidthOffset;
private float viewWidthOffset;
private float viewHeightOffset;
private PointF coordinates;
private boolean isVisible;
@LayoutRes
private int layoutRes;
InfoWindow(MapView mapView, int layoutResId, MapboxMap mapboxMap) {
layoutRes = layoutResId;
View view = LayoutInflater.from(mapView.getContext()).inflate(layoutResId, mapView, false);
initialize(view, mapboxMap);
}
InfoWindow(@NonNull View view, MapboxMap mapboxMap) {
initialize(view, mapboxMap);
}
private void initialize(@NonNull View view, MapboxMap mapboxMap) {
this.mapboxMap = new WeakReference<>(mapboxMap);
isVisible = false;
this.view = new WeakReference<>(view);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MapboxMap mapboxMap = InfoWindow.this.mapboxMap.get();
if (mapboxMap != null) {
MapboxMap.OnInfoWindowClickListener onInfoWindowClickListener = mapboxMap.getOnInfoWindowClickListener();
boolean handledDefaultClick = false;
if (onInfoWindowClickListener != null) {
handledDefaultClick = onInfoWindowClickListener.onInfoWindowClick(getBoundMarker());
}
if (!handledDefaultClick) {
// default behavior: close it when clicking on the tooltip:
closeInfoWindow();
}
}
}
});
view.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
MapboxMap mapboxMap = InfoWindow.this.mapboxMap.get();
if (mapboxMap != null) {
MapboxMap.OnInfoWindowLongClickListener listener = mapboxMap.getOnInfoWindowLongClickListener();
if (listener != null) {
listener.onInfoWindowLongClick(getBoundMarker());
}
}
return true;
}
});
}
private void closeInfoWindow() {
MapboxMap mapbox = mapboxMap.get();
Marker marker = boundMarker.get();
if (marker != null && mapbox != null) {
mapbox.deselectMarker(marker);
}
close();
}
/**
* Open the info window at the specified position.
*
* @param boundMarker The marker on which is hooked the view.
* @param position to place the window on the map.
* @param offsetX The offset of the view to the position, in pixels. This allows to offset
* the view from the object position.
* @param offsetY The offset of the view to the position, in pixels. This allows to offset
* the view from the object position.
* @return this {@link InfoWindow}.
*/
@NonNull
InfoWindow open(@NonNull MapView mapView, Marker boundMarker, @NonNull LatLng position, int offsetX, int offsetY) {
setBoundMarker(boundMarker);
MapView.LayoutParams lp = new MapView.LayoutParams(MapView.LayoutParams.WRAP_CONTENT,
MapView.LayoutParams.WRAP_CONTENT);
MapboxMap mapboxMap = this.mapboxMap.get();
View view = this.view.get();
if (view != null && mapboxMap != null) {
view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
markerHeightOffset = offsetY;
markerWidthOffset = -offsetX;
// Calculate default Android x,y coordinate
coordinates = mapboxMap.getProjection().toScreenLocation(position);
float x = coordinates.x - (view.getMeasuredWidth() / 2) + offsetX;
float y = coordinates.y - view.getMeasuredHeight() + offsetY;
if (view instanceof BubbleLayout) {
// only apply repositioning/margin for InfoWindowView
Resources resources = mapView.getContext().getResources();
// get right/left popup window
float rightSideInfowWindow = x + view.getMeasuredWidth();
float leftSideInfoWindow = x;
// get right/left map view
final float mapRight = mapView.getRight();
final float mapLeft = mapView.getLeft();
float marginHorizontal = resources.getDimension(R.dimen.mapbox_infowindow_margin);
float tipViewOffset = resources.getDimension(R.dimen.mapbox_infowindow_tipview_width) / 2;
float tipViewMarginLeft = view.getMeasuredWidth() / 2 - tipViewOffset;
boolean outOfBoundsLeft = false;
boolean outOfBoundsRight = false;
// only optimise margins if view is inside current viewport
if (coordinates.x >= 0 && coordinates.x <= mapView.getWidth()
&& coordinates.y >= 0 && coordinates.y <= mapView.getHeight()) {
// if out of bounds right
if (rightSideInfowWindow > mapRight) {
outOfBoundsRight = true;
x -= rightSideInfowWindow - mapRight;
tipViewMarginLeft += rightSideInfowWindow - mapRight + tipViewOffset;
rightSideInfowWindow = x + view.getMeasuredWidth();
}
// fit screen left
if (leftSideInfoWindow < mapLeft) {
outOfBoundsLeft = true;
x += mapLeft - leftSideInfoWindow;
tipViewMarginLeft -= mapLeft - leftSideInfoWindow + tipViewOffset;
leftSideInfoWindow = x;
}
// Add margin right
if (outOfBoundsRight && mapRight - rightSideInfowWindow < marginHorizontal) {
x -= marginHorizontal - (mapRight - rightSideInfowWindow);
tipViewMarginLeft += marginHorizontal - (mapRight - rightSideInfowWindow) - tipViewOffset;
leftSideInfoWindow = x;
}
// Add margin left
if (outOfBoundsLeft && leftSideInfoWindow - mapLeft < marginHorizontal) {
x += marginHorizontal - (leftSideInfoWindow - mapLeft);
tipViewMarginLeft -= (marginHorizontal - (leftSideInfoWindow - mapLeft)) - tipViewOffset;
}
}
// Adjust tipView
((BubbleLayout) view).setArrowPosition(tipViewMarginLeft);
}
// set anchor popupwindowview
view.setX(x);
view.setY(y);
// Calculate x-offset and y-offset for update method
viewWidthOffset = x - coordinates.x - offsetX;
viewHeightOffset = -view.getMeasuredHeight() + offsetY;
close(); // if it was already opened
mapView.addView(view, lp);
isVisible = true;
}
return this;
}
/**
* Close this {@link InfoWindow} if it is visible, otherwise calling this will do nothing.
*
* @return This {@link InfoWindow}
*/
@NonNull
InfoWindow close() {
MapboxMap mapboxMap = this.mapboxMap.get();
if (isVisible && mapboxMap != null) {
isVisible = false;
View view = this.view.get();
if (view != null && view.getParent() != null) {
((ViewGroup) view.getParent()).removeView(view);
}
Marker marker = getBoundMarker();
MapboxMap.OnInfoWindowCloseListener listener = mapboxMap.getOnInfoWindowCloseListener();
if (listener != null) {
listener.onInfoWindowClose(marker);
}
setBoundMarker(null);
}
return this;
}
/**
* 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
*/
void adaptDefaultMarker(@NonNull Marker overlayItem, MapboxMap mapboxMap, @NonNull MapView mapView) {
View view = this.view.get();
if (view == null) {
view = LayoutInflater.from(mapView.getContext()).inflate(layoutRes, mapView, false);
initialize(view, mapboxMap);
}
this.mapboxMap = new WeakReference<>(mapboxMap);
String title = overlayItem.getTitle();
TextView titleTextView = ((TextView) view.findViewById(R.id.infowindow_title));
if (!TextUtils.isEmpty(title)) {
titleTextView.setText(title);
titleTextView.setVisibility(View.VISIBLE);
} else {
titleTextView.setVisibility(View.GONE);
}
String snippet = overlayItem.getSnippet();
TextView snippetTextView = ((TextView) view.findViewById(R.id.infowindow_description));
if (!TextUtils.isEmpty(snippet)) {
snippetTextView.setText(snippet);
snippetTextView.setVisibility(View.VISIBLE);
} else {
snippetTextView.setVisibility(View.GONE);
}
}
@NonNull
InfoWindow setBoundMarker(Marker boundMarker) {
this.boundMarker = new WeakReference<>(boundMarker);
return this;
}
@Nullable
Marker getBoundMarker() {
if (boundMarker == null) {
return null;
}
return boundMarker.get();
}
/**
* Will result in getting this {@link InfoWindow} and updating the view being displayed.
*/
public void update() {
MapboxMap mapboxMap = this.mapboxMap.get();
Marker marker = boundMarker.get();
View view = this.view.get();
if (mapboxMap != null && marker != null && view != null) {
coordinates = mapboxMap.getProjection().toScreenLocation(marker.getPosition());
if (view instanceof BubbleLayout) {
view.setX(coordinates.x + viewWidthOffset - markerWidthOffset);
} else {
view.setX(coordinates.x - (view.getMeasuredWidth() / 2) - markerWidthOffset);
}
view.setY(coordinates.y + viewHeightOffset);
}
}
void onContentUpdate() {
//recalculate y-offset and update position
View view = this.view.get();
if (view != null) {
ViewTreeObserver viewTreeObserver = view.getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
viewTreeObserver.addOnGlobalLayoutListener(contentUpdateListener);
}
}
}
@Nullable
private final ViewTreeObserver.OnGlobalLayoutListener contentUpdateListener =
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
View view = InfoWindow.this.view.get();
if (view != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
} else {
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
viewHeightOffset = -view.getMeasuredHeight() + markerHeightOffset;
update();
}
}
};
/**
* Retrieve this {@link InfoWindow}'s current view being used.
*
* @return This {@link InfoWindow}'s current View.
*/
@Nullable
public View getView() {
return view != null ? view.get() : null;
}
boolean isVisible() {
return isVisible;
}
}

View File

@@ -0,0 +1,278 @@
package com.mapbox.mapboxsdk.annotations;
import android.support.annotation.Keep;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View;
import com.mapbox.mapboxsdk.R;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
/**
* Marker is an annotation that shows an icon image at a geographical location. The default marker
* uses a provided icon. This icon can be customized using {@link IconFactory} to generate an
* {@link Icon} using a provided image. Markers are added to the map by first giving a
* {@link LatLng} and using {@link MapboxMap#addMarker(MarkerOptions)}. The marker icon will be
* centered at this position so it is common to add padding to the icon image before usage.
* <p>
* Markers are designed to be interactive. They receive click events by default, and are often used
* with event listeners to bring up info windows. An {@link InfoWindow} is displayed by default when
* either a title or snippet is provided.
* </p>
* @deprecated As of 7.0.0,
* use <a href="https://github.com/mapbox/mapbox-plugins-android/tree/master/plugin-annotation">
* Mapbox Annotation Plugin</a> instead
*/
@Deprecated
public class Marker extends Annotation {
@Keep
private LatLng position;
private String snippet;
@Nullable
private Icon icon;
//Redundantly stored for JNI access
@Nullable
@Keep
private String iconId;
private String title;
@Nullable
private InfoWindow infoWindow;
private boolean infoWindowShown;
private int topOffsetPixels;
private int rightOffsetPixels;
/**
* Constructor
*/
Marker() {
super();
}
/**
* Creates a instance of {@link Marker} using the builder of Marker.
*
* @param baseMarkerOptions The builder used to construct the Marker.
*/
public Marker(BaseMarkerOptions baseMarkerOptions) {
this(baseMarkerOptions.position, baseMarkerOptions.icon, baseMarkerOptions.title, baseMarkerOptions.snippet);
}
Marker(LatLng position, Icon icon, String title, String snippet) {
this.position = position;
this.title = title;
this.snippet = snippet;
setIcon(icon);
}
/**
* Returns the position of the marker.
*
* @return A {@link LatLng} object specifying the marker's current position.
*/
public LatLng getPosition() {
return position;
}
/**
* Gets the snippet of the marker.
*
* @return A string containing the marker's snippet.
*/
public String getSnippet() {
return snippet;
}
/**
* Gets the snippet of the marker.
*
* @return A string containing the marker's snippet.
*/
public String getTitle() {
return title;
}
/**
* Do not use this method, used internally by the SDK.
*/
public void hideInfoWindow() {
if (infoWindow != null) {
infoWindow.close();
}
infoWindowShown = false;
}
/**
* Do not use this method, used internally by the SDK.
*
* @return true if the infoWindow is shown
*/
public boolean isInfoWindowShown() {
return infoWindowShown;
}
/**
* Sets the location of the marker.
*
* @param position A {@link LatLng} defining the marker position.
*/
public void setPosition(LatLng position) {
this.position = position;
MapboxMap map = getMapboxMap();
if (map != null) {
map.updateMarker(this);
}
}
/**
* Sets the snippet of the marker.
*
* @param snippet A String used in the marker info window. If {@code null}, the snippet is
* cleared.
*/
public void setSnippet(String snippet) {
this.snippet = snippet;
refreshInfoWindowContent();
}
/**
* Sets the icon of the marker.
*
* @param icon The {@link Icon} to be used as Marker image
*/
public void setIcon(@Nullable Icon icon) {
this.icon = icon;
this.iconId = icon != null ? icon.getId() : null;
MapboxMap map = getMapboxMap();
if (map != null) {
map.updateMarker(this);
}
}
/**
* Gets the {@link Icon} currently used for the marker. If no Icon was set for the marker, the
* default icon will be returned.
*
* @return The {@link Icon} the marker is using.
*/
@Nullable
public Icon getIcon() {
return icon;
}
/**
* Sets the title of the marker.
*
* @param title A String used in the marker info window. If {@code null}, the title is
* cleared.
*/
public void setTitle(String title) {
this.title = title;
refreshInfoWindowContent();
}
/**
* Gets the {@link InfoWindow} the marker is using. If the marker hasn't had an info window
* defined, this will return {@code null}.
*
* @return The info window the marker is using.
*/
@Nullable
public InfoWindow getInfoWindow() {
return infoWindow;
}
/**
* Update only for default Marker's InfoWindow content for Title and Snippet
*/
private void refreshInfoWindowContent() {
if (isInfoWindowShown() && mapView != null && mapboxMap != null && mapboxMap.getInfoWindowAdapter() == null) {
InfoWindow infoWindow = getInfoWindow(mapView);
if (mapView.getContext() != null) {
infoWindow.adaptDefaultMarker(this, mapboxMap, mapView);
}
MapboxMap map = getMapboxMap();
if (map != null) {
map.updateMarker(this);
}
infoWindow.onContentUpdate();
}
}
/**
* Do not use this method, used internally by the SDK. Use {@link MapboxMap#selectMarker(Marker)}
* if you want to programmatically display the markers info window.
*
* @param mapboxMap The hosting mapbox map.
* @param mapView The hosting map view.
* @return The info window that was shown.
*/
@Nullable
public InfoWindow showInfoWindow(@NonNull MapboxMap mapboxMap, @NonNull MapView mapView) {
setMapboxMap(mapboxMap);
setMapView(mapView);
MapboxMap.InfoWindowAdapter infoWindowAdapter = getMapboxMap().getInfoWindowAdapter();
if (infoWindowAdapter != null) {
// end developer is using a custom InfoWindowAdapter
View content = infoWindowAdapter.getInfoWindow(this);
if (content != null) {
infoWindow = new InfoWindow(content, mapboxMap);
showInfoWindow(infoWindow, mapView);
return infoWindow;
}
}
InfoWindow infoWindow = getInfoWindow(mapView);
if (mapView.getContext() != null) {
infoWindow.adaptDefaultMarker(this, mapboxMap, mapView);
}
return showInfoWindow(infoWindow, mapView);
}
@NonNull
private InfoWindow showInfoWindow(InfoWindow iw, MapView mapView) {
iw.open(mapView, this, getPosition(), rightOffsetPixels, topOffsetPixels);
infoWindowShown = true;
return iw;
}
@Nullable
private InfoWindow getInfoWindow(@NonNull MapView mapView) {
if (infoWindow == null && mapView.getContext() != null) {
infoWindow = new InfoWindow(mapView, R.layout.mapbox_infowindow_content, getMapboxMap());
}
return infoWindow;
}
/**
* Do not use this method, used internally by the SDK.
*
* @param topOffsetPixels the top offset pixels.
*/
public void setTopOffsetPixels(int topOffsetPixels) {
this.topOffsetPixels = topOffsetPixels;
}
/**
* Do not use this method, used internally by the SDK.
*
* @param rightOffsetPixels the right offset pixels.
*/
public void setRightOffsetPixels(int rightOffsetPixels) {
this.rightOffsetPixels = rightOffsetPixels;
}
/**
* Returns a String with the marker position.
*
* @return A String with the marker position.
*/
@Override
public String toString() {
return "Marker [position[" + getPosition() + "]]";
}
}

View File

@@ -0,0 +1,195 @@
package com.mapbox.mapboxsdk.annotations;
import android.graphics.Bitmap;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.mapbox.mapboxsdk.exceptions.InvalidMarkerPositionException;
import com.mapbox.mapboxsdk.geometry.LatLng;
/**
* <p>
* Builder for composing {@link Marker} objects. See {@link Marker} for additional information.
* </p>
* <h3>Example</h3>
* <pre>
* mapView.addMarker(new MarkerOptions()
* .title("Intersection")
* .snippet("H St NW with 15th St NW")
* .position(new LatLng(38.9002073, -77.03364419)));
* </pre>
* @deprecated As of 7.0.0,
* use <a href="https://github.com/mapbox/mapbox-plugins-android/tree/master/plugin-annotation">
* Mapbox Annotation Plugin</a> instead
*/
@Deprecated
public final class MarkerOptions extends BaseMarkerOptions<Marker, MarkerOptions> implements Parcelable {
/**
* Defines options for a Marker.
*/
public MarkerOptions() {
}
protected MarkerOptions(Parcel in) {
position((LatLng) in.readParcelable(LatLng.class.getClassLoader()));
snippet(in.readString());
title(in.readString());
if (in.readByte() != 0) {
// this means we have an icon
String iconId = in.readString();
Bitmap iconBitmap = in.readParcelable(Bitmap.class.getClassLoader());
Icon icon = new Icon(iconId, iconBitmap);
icon(icon);
}
}
@NonNull
@Override
public MarkerOptions getThis() {
return this;
}
/**
* Describe the kinds of special objects contained in this Parcelable's
* marshalled representation.
*
* @return integer 0.
*/
@Override
public int describeContents() {
return 0;
}
/**
* Flatten this object in to a Parcel.
*
* @param out The Parcel in which the object should be written.
* @param flags Additional flags about how the object should be written. May be 0 or
* {@link #PARCELABLE_WRITE_RETURN_VALUE}.
*/
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeParcelable(getPosition(), flags);
out.writeString(getSnippet());
out.writeString(getTitle());
Icon icon = getIcon();
out.writeByte((byte) (icon != null ? 1 : 0));
if (icon != null) {
out.writeString(getIcon().getId());
out.writeParcelable(getIcon().getBitmap(), flags);
}
}
/**
* Do not use this method. Used internally by the SDK.
*
* @return Marker The build marker
*/
public Marker getMarker() {
if (position == null) {
throw new InvalidMarkerPositionException();
}
return new Marker(position, icon, title, snippet);
}
/**
* Returns the position set for this {@link MarkerOptions} object.
*
* @return A {@link LatLng} object specifying the marker's current position.
*/
public LatLng getPosition() {
return position;
}
/**
* Gets the snippet set for this {@link MarkerOptions} object.
*
* @return A string containing the marker's snippet.
*/
public String getSnippet() {
return snippet;
}
/**
* Gets the title set for this {@link MarkerOptions} object.
*
* @return A string containing the marker's title.
*/
public String getTitle() {
return title;
}
/**
* Gets the custom icon set for this {@link MarkerOptions} object.
*
* @return A {@link Icon} object that the marker is using. If the icon wasn't set, default icon
* will return.
*/
public Icon getIcon() {
return icon;
}
public static final Parcelable.Creator<MarkerOptions> CREATOR =
new Parcelable.Creator<MarkerOptions>() {
public MarkerOptions createFromParcel(@NonNull Parcel in) {
return new MarkerOptions(in);
}
public MarkerOptions[] newArray(int size) {
return new MarkerOptions[size];
}
};
/**
* Compares this {@link MarkerOptions} object with another {@link MarkerOptions} and
* determines if their properties match.
*
* @param o Another {@link MarkerOptions} to compare with this object.
* @return True if marker properties match this {@link MarkerOptions} object.
* Else, false.
*/
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MarkerOptions marker = (MarkerOptions) o;
if (getPosition() != null ? !getPosition().equals(marker.getPosition()) : marker.getPosition() != null) {
return false;
}
if (getSnippet() != null ? !getSnippet().equals(marker.getSnippet()) : marker.getSnippet() != null) {
return false;
}
if (getIcon() != null ? !getIcon().equals(marker.getIcon()) : marker.getIcon() != null) {
return false;
}
return !(getTitle() != null ? !getTitle().equals(marker.getTitle()) : marker.getTitle() != null);
}
/**
* Gives an integer which can be used as the bucket number for storing elements of the set/map.
* This bucket number is the address of the element inside the set/map. There's no guarantee
* that this hash value will be consistent between different Java implementations, or even
* between different execution runs of the same program.
*
* @return integer value you can use for storing element.
*/
@Override
public int hashCode() {
int result = 1;
result = 31 * result + (getPosition() != null ? getPosition().hashCode() : 0);
result = 31 * result + (getSnippet() != null ? getSnippet().hashCode() : 0);
result = 31 * result + (getIcon() != null ? getIcon().hashCode() : 0);
result = 31 * result + (getTitle() != null ? getTitle().hashCode() : 0);
return result;
}
}

View File

@@ -0,0 +1,109 @@
package com.mapbox.mapboxsdk.annotations;
import android.graphics.Color;
import android.support.annotation.Keep;
import android.support.annotation.NonNull;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import java.util.ArrayList;
import java.util.List;
/**
* Polygon is a geometry annotation that's a closed loop of coordinates.
* @deprecated As of 7.0.0,
* use <a href="https://github.com/mapbox/mapbox-plugins-android/tree/master/plugin-annotation">
* Mapbox Annotation Plugin</a> instead
*/
@Deprecated
public final class Polygon extends BasePointCollection {
@Keep
private int fillColor = Color.BLACK; // default fillColor is black
@Keep
private int strokeColor = Color.BLACK; // default strokeColor is black
@Keep
private List<List<LatLng>> holes;
Polygon() {
super();
holes = new ArrayList<>();
}
/**
* Get the color of the fill region of the polygon.
*
* @return The color of the fill.
*/
public int getFillColor() {
return fillColor;
}
/**
* Get the color of the stroke of the polygon.
*
* @return The color of the stroke.
*/
public int getStrokeColor() {
return strokeColor;
}
/**
* Returns a copy of the holes.
*
* @return A {@link List} of {@link List} of {@link LatLng} points making up the holes.
*/
public List<List<LatLng>> getHoles() {
return new ArrayList<>(holes);
}
/**
* Sets the color of the fill region of the polygon.
*
* @param color The color in ARGB format.
*/
public void setFillColor(int color) {
fillColor = color;
update();
}
/**
* Sets the color of the stroke of the polygon.
*
* @param color The color in ARGB format.
*/
public void setStrokeColor(int color) {
strokeColor = color;
update();
}
/**
* Sets the holes of this polygon. This method will take a copy of the holes, so further
* mutations to holes will have no effect on this polygon.
*
* @param holes A {@link List} of {@link List} of {@link LatLng} points making up the holes.
*/
public void setHoles(@NonNull List<? extends List<LatLng>> holes) {
this.holes = new ArrayList<>(holes);
update();
}
/**
* Add a hole to the polygon.
*
* @param hole A {@link List} of {@link List} of {@link LatLng} points making up the hole to be added.
*/
void addHole(List<LatLng> hole) {
holes.add(hole);
update();
}
@Override
void update() {
MapboxMap mapboxMap = getMapboxMap();
if (mapboxMap != null) {
mapboxMap.updatePolygon(this);
}
}
}

View File

@@ -0,0 +1,308 @@
package com.mapbox.mapboxsdk.annotations;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.mapbox.mapboxsdk.geometry.LatLng;
import java.util.ArrayList;
import java.util.List;
/**
* Builder for composing {@link Polygon} objects.
* @deprecated As of 7.0.0,
* use <a href="https://github.com/mapbox/mapbox-plugins-android/tree/master/plugin-annotation">
* Mapbox Annotation Plugin</a> instead
*/
@Deprecated
public final class PolygonOptions implements Parcelable {
public static final Parcelable.Creator<PolygonOptions> CREATOR =
new Parcelable.Creator<PolygonOptions>() {
public PolygonOptions createFromParcel(@NonNull Parcel in) {
return new PolygonOptions(in);
}
public PolygonOptions[] newArray(int size) {
return new PolygonOptions[size];
}
};
private PolygonOptions(Parcel in) {
polygon = new Polygon();
List<LatLng> pointsList = new ArrayList<>();
in.readList(pointsList, LatLng.class.getClassLoader());
addAll(pointsList);
List<List<LatLng>> holes = new ArrayList<>();
in.readList(holes, LatLng.class.getClassLoader());
addAllHoles(holes);
alpha(in.readFloat());
fillColor(in.readInt());
strokeColor(in.readInt());
}
/**
* Describe the kinds of special objects contained in this Parcelable's
* marshalled representation.
*
* @return integer 0.
*/
@Override
public int describeContents() {
return 0;
}
/**
* Flatten this object in to a Parcel.
*
* @param out The Parcel in which the object should be written.
* @param flags Additional flags about how the object should be written. May be 0 or
* {@link #PARCELABLE_WRITE_RETURN_VALUE}.
*/
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeList(getPoints());
out.writeList(getHoles());
out.writeFloat(getAlpha());
out.writeInt(getFillColor());
out.writeInt(getStrokeColor());
}
private Polygon polygon;
/**
* Defines options for a polygon.
*/
public PolygonOptions() {
polygon = new Polygon();
}
/**
* Adds a vertex to the outline of the polygon being built.
*
* @param point {@link LatLng} point to be added to polygon geometry.
* @return This {@link PolygonOptions} object with the given point added to the outline.
*/
@NonNull
public PolygonOptions add(LatLng point) {
polygon.addPoint(point);
return this;
}
/**
* Adds vertices to the outline of the polygon being built.
*
* @param points {@link LatLng} points to be added to polygon geometry.
* @return This {@link PolygonOptions} object with the given points added to the outline.
*/
@NonNull
public PolygonOptions add(LatLng... points) {
for (LatLng point : points) {
add(point);
}
return this;
}
/**
* Adds vertices to the outline of the polygon being built.
*
* @param points {@link Iterable} list made up of {@link LatLng} points defining the polygon
* geometry
* @return This {@link PolygonOptions} object with the given points added to the outline.
*/
@NonNull
public PolygonOptions addAll(Iterable<LatLng> points) {
for (LatLng point : points) {
add(point);
}
return this;
}
/**
* Adds a hole to the outline of the polygon being built.
*
* @param hole {@link List} list made up of {@link LatLng} points defining the hole
* @return This {@link PolygonOptions} object with the given hole added to the outline.
*/
@NonNull
public PolygonOptions addHole(List<LatLng> hole) {
polygon.addHole(hole);
return this;
}
/**
* Adds holes to the outline of the polygon being built.
*
* @param holes {@link List} list made up of {@link LatLng} holes to be added to polygon geometry
* @return This {@link PolygonOptions} object with the given holes added to the outline.
*/
@NonNull
public PolygonOptions addHole(List<LatLng>... holes) {
for (List<LatLng> hole : holes) {
addHole(hole);
}
return this;
}
/**
* Adds holes to the outline of the polygon being built.
*
* @param holes {@link Iterable} list made up of {@link List} list of {@link LatLng} holes defining the hole geometry
* @return This {@link PolygonOptions} object with the given holes added to the outline.
*/
@NonNull
public PolygonOptions addAllHoles(Iterable<List<LatLng>> holes) {
for (List<LatLng> hole : holes) {
addHole(hole);
}
return this;
}
/**
* Set the alpha value of the polyline.
*
* @param alpha float value between 0 (not visible) and 1.
* @return This {@link PolygonOptions} object with the given polygon alpha value.
*/
@NonNull
public PolygonOptions alpha(float alpha) {
polygon.setAlpha(alpha);
return this;
}
/**
* Gets the alpha set for this {@link PolygonOptions} object.
*
* @return float value between 0 and 1 defining the alpha.
*/
public float getAlpha() {
return polygon.getAlpha();
}
/**
* Specifies the polygon's fill color, as 32-bit ARGB. The default color is black.
*
* @param color 32-bit ARGB color.
* @return This {@link PolylineOptions} object with a new color set.
*/
@NonNull
public PolygonOptions fillColor(int color) {
polygon.setFillColor(color);
return this;
}
/**
* Gets the fill color set for this {@link PolygonOptions} object.
*
* @return The fill color of the polygon in ARGB format.
*/
public int getFillColor() {
return polygon.getFillColor();
}
/**
* Do not use this method. Used internally by the SDK.
*
* @return Polygon the Polygon to return
*/
public Polygon getPolygon() {
return polygon;
}
/**
* Specifies the polygon's stroke color, as 32-bit ARGB. The default color is black.
*
* @param color 32-bit ARGB color.
* @return This {@link PolygonOptions} object with a new stroke color set.
*/
@NonNull
public PolygonOptions strokeColor(int color) {
polygon.setStrokeColor(color);
return this;
}
/**
* Gets the stroke color set for this {@link PolygonOptions} object.
*
* @return The stroke color of the polygon in ARGB format.
*/
public int getStrokeColor() {
return polygon.getStrokeColor();
}
/**
* Gets the points set for this {@link PolygonOptions} object.
*
* @return The list made up of {@link LatLng} points defining the polygon.
*/
public List<LatLng> getPoints() {
// the getter gives us a copy, which is the safe thing to do...
return polygon.getPoints();
}
/**
* Gets the holes set for this {@link PolygonOptions} object.
*
* @return The list made up of {@link List} of {@link List} of {@link LatLng} points defining the holes.
*/
public List<List<LatLng>> getHoles() {
return polygon.getHoles();
}
/**
* Compares this {@link PolygonOptions} object with another {@link PolygonOptions} and
* determines if their color, alpha, stroke color, and vertices match.
*
* @param o Another {@link PolygonOptions} to compare with this object.
* @return True if color, alpha, stroke color, vertices and holes match this {@link PolygonOptions}
* {@link PolygonOptions} object. Else, false.
*/
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
PolygonOptions polygon = (PolygonOptions) o;
if (Float.compare(polygon.getAlpha(), getAlpha()) != 0) {
return false;
}
if (getFillColor() != polygon.getFillColor()) {
return false;
}
if (getStrokeColor() != polygon.getStrokeColor()) {
return false;
}
if (getPoints() != null ? !getPoints().equals(polygon.getPoints()) : polygon.getPoints() != null) {
return false;
}
return !(getHoles() != null ? !getHoles().equals(polygon.getHoles()) : polygon.getHoles() != null);
}
/**
* Gives an integer which can be used as the bucket number for storing elements of the set/map.
* This bucket number is the address of the element inside the set/map. There's no guarantee
* that this hash value will be consistent between different Java implementations, or even
* between different execution runs of the same program.
*
* @return integer value you can use for storing element.
*/
@Override
public int hashCode() {
int result = 1;
result = 31 * result + (getAlpha() != +0.0f ? Float.floatToIntBits(getAlpha()) : 0);
result = 31 * result + getFillColor();
result = 31 * result + getStrokeColor();
result = 31 * result + (getPoints() != null ? getPoints().hashCode() : 0);
result = 31 * result + (getHoles() != null ? getHoles().hashCode() : 0);
return result;
}
}

View File

@@ -0,0 +1,71 @@
package com.mapbox.mapboxsdk.annotations;
import android.graphics.Color;
import android.support.annotation.Keep;
import com.mapbox.mapboxsdk.maps.MapboxMap;
/**
* Polyline is a geometry feature with an unclosed list of coordinates drawn as a line
* @deprecated As of 7.0.0,
* use <a href="https://github.com/mapbox/mapbox-plugins-android/tree/master/plugin-annotation">
* Mapbox Annotation Plugin</a> instead
*/
@Deprecated
public final class Polyline extends BasePointCollection {
@Keep
private int color = Color.BLACK; // default color is black
@Keep
private float width = 10; // As specified by Google API Docs (in pixels)
Polyline() {
super();
}
/**
* Gets the color of this polyline.
*
* @return The color in ARGB format.
*/
public int getColor() {
return color;
}
/**
* Gets the width of this polyline.
*
* @return The width in screen pixels.
*/
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;
update();
}
/**
* Sets the width of the polyline.
*
* @param width in pixels
*/
public void setWidth(float width) {
this.width = width;
update();
}
@Override
void update() {
MapboxMap mapboxMap = getMapboxMap();
if (mapboxMap != null) {
mapboxMap.updatePolyline(this);
}
}
}

View File

@@ -0,0 +1,250 @@
package com.mapbox.mapboxsdk.annotations;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.mapbox.mapboxsdk.geometry.LatLng;
import java.util.ArrayList;
import java.util.List;
/**
* Builder for composing {@link Polyline} objects.
* @deprecated As of 7.0.0,
* use <a href="https://github.com/mapbox/mapbox-plugins-android/tree/master/plugin-annotation">
* Mapbox Annotation Plugin</a> instead
*/
@Deprecated
public final class PolylineOptions implements Parcelable {
public static final Parcelable.Creator<PolylineOptions> CREATOR =
new Parcelable.Creator<PolylineOptions>() {
public PolylineOptions createFromParcel(@NonNull Parcel in) {
return new PolylineOptions(in);
}
public PolylineOptions[] newArray(int size) {
return new PolylineOptions[size];
}
};
private PolylineOptions(Parcel in) {
polyline = new Polyline();
ArrayList<LatLng> pointsList = new ArrayList<>();
in.readList(pointsList, LatLng.class.getClassLoader());
addAll(pointsList);
alpha(in.readFloat());
color(in.readInt());
width(in.readFloat());
}
/**
* Describe the kinds of special objects contained in this Parcelable's
* marshalled representation.
*
* @return integer 0.
*/
@Override
public int describeContents() {
return 0;
}
/**
* Flatten this object in to a Parcel.
*
* @param out The Parcel in which the object should be written.
* @param flags Additional flags about how the object should be written. May be 0 or
* {@link #PARCELABLE_WRITE_RETURN_VALUE}.
*/
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeList(getPoints());
out.writeFloat(getAlpha());
out.writeInt(getColor());
out.writeFloat(getWidth());
}
private Polyline polyline;
/**
* Defines options for a polyline.
*/
public PolylineOptions() {
polyline = new Polyline();
}
/**
* Adds a vertex to the end of the polyline being built.
*
* @param point {@link LatLng} point to be added to polyline geometry.
* @return This {@link PolylineOptions} object with the given point on the end.
*/
@NonNull
public PolylineOptions add(LatLng point) {
polyline.addPoint(point);
return this;
}
/**
* Adds vertices to the end of the polyline being built.
*
* @param points {@link LatLng} points defining the polyline geometry.
* @return This {@link PolylineOptions} object with the given point on the end.
*/
@NonNull
public PolylineOptions add(LatLng... points) {
for (LatLng point : points) {
add(point);
}
return this;
}
/**
* Adds vertices to the end of the polyline being built.
*
* @param points {@link Iterable} list made up of {@link LatLng} points defining the polyline
* geometry
* @return This {@link PolylineOptions} object with the given points on the end.
*/
@NonNull
public PolylineOptions addAll(Iterable<LatLng> points) {
for (LatLng point : points) {
add(point);
}
return this;
}
/**
* Set the alpha value of the polyline.
*
* @param alpha float value between 0 (not visible) and 1.
* @return This {@link PolylineOptions} object with the given polyline alpha value.
*/
@NonNull
public PolylineOptions alpha(float alpha) {
polyline.setAlpha(alpha);
return this;
}
/**
* Gets the alpha set for this {@link PolylineOptions} object.
*
* @return float value between 0 and 1 defining the alpha.
*/
public float getAlpha() {
return polyline.getAlpha();
}
/**
* Sets the color of the polyline as a 32-bit ARGB color. The default color is black.
*
* @param color 32-bit ARGB color.
* @return This {@link PolylineOptions} object with a new color set.
*/
@NonNull
public PolylineOptions color(int color) {
polyline.setColor(color);
return this;
}
/**
* Gets the color set for this {@link PolylineOptions} object.
*
* @return The color of the polyline in ARGB format.
*/
public int getColor() {
return polyline.getColor();
}
/**
* Do not use this method. Used internally by the SDK.
*
* @return PolyLine The polyline build by this class.
*/
public Polyline getPolyline() {
return polyline;
}
/**
* Gets the width set for this {@link PolylineOptions} object.
*
* @return The width of the polyline in screen pixels.
*/
public float getWidth() {
return polyline.getWidth();
}
/**
* Sets the width of the polyline in screen pixels. The default is 10.
*
* @param width float value defining width of polyline using unit pixels.
* @return This {@link PolylineOptions} object with a new width set.
*/
@NonNull
public PolylineOptions width(float width) {
polyline.setWidth(width);
return this;
}
/**
* Gets the points set for this {@link PolylineOptions} object.
*
* @return a {@link List} of {@link LatLng}s specifying the vertices of the polyline.
*/
public List<LatLng> getPoints() {
// the getter gives us a copy, which is the safe thing to do...
return polyline.getPoints();
}
/**
* Compares this {@link PolylineOptions} object with another {@link PolylineOptions} and
* determines if their color, alpha, width, and vertices match.
*
* @param o Another {@link PolylineOptions} to compare with this object.
* @return True if color, alpha, width, and vertices match this {@link PolylineOptions} object.
* Else, false.
*/
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
PolylineOptions polyline = (PolylineOptions) o;
if (Float.compare(polyline.getAlpha(), getAlpha()) != 0) {
return false;
}
if (getColor() != polyline.getColor()) {
return false;
}
if (Float.compare(polyline.getWidth(), getWidth()) != 0) {
return false;
}
return !(getPoints() != null ? !getPoints().equals(polyline.getPoints()) : polyline.getPoints() != null);
}
/**
* Gives an integer which can be used as the bucket number for storing elements of the set/map.
* This bucket number is the address of the element inside the set/map. There's no guarantee
* that this hash value will be consistent between different Java implementations, or even
* between different execution runs of the same program.
*
* @return integer value you can use for storing element.
*/
@Override
public int hashCode() {
int result = 1;
result = 31 * result + (getAlpha() != +0.0f ? Float.floatToIntBits(getAlpha()) : 0);
result = 31 * result + getColor();
result = 31 * result + (getWidth() != +0.0f ? Float.floatToIntBits(getWidth()) : 0);
result = 31 * result + (getPoints() != null ? getPoints().hashCode() : 0);
return result;
}
}

View File

@@ -0,0 +1,4 @@
/**
* Contains the Mapbox Maps Android Annotation API classes.
*/
package com.mapbox.mapboxsdk.annotations;

View File

@@ -0,0 +1,70 @@
package com.mapbox.mapboxsdk.attribution;
import android.support.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
public class Attribution {
private static final String OPENSTREETMAP = "OpenStreetMap";
private static final String OPENSTREETMAP_ABBR = "OSM";
static final String TELEMETRY = "Telemetry Settings";
static final String MAPBOX_URL = "https://www.mapbox.com/about/maps/";
static final String TELEMETRY_URL = "https://www.mapbox.com/telemetry/";
static final List<String> IMPROVE_MAP_URLS = new ArrayList<>();
static {
// Using a List makes URL backwards compatible
IMPROVE_MAP_URLS.add("https://www.mapbox.com/feedback/");
IMPROVE_MAP_URLS.add("https://www.mapbox.com/map-feedback/");
IMPROVE_MAP_URLS.add("https://apps.mapbox.com/feedback/");
}
private String title;
private String url;
Attribution(String title, String url) {
this.title = title;
this.url = url;
}
public String getTitle() {
return title;
}
public String getTitleAbbreviated() {
if (title.equals(OPENSTREETMAP)) {
return OPENSTREETMAP_ABBR;
}
return title;
}
public String getUrl() {
return url;
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Attribution that = (Attribution) o;
if (title != null ? !title.equals(that.title) : that.title != null) {
return false;
}
return url != null ? url.equals(that.url) : that.url == null;
}
@Override
public int hashCode() {
int result = title != null ? title.hashCode() : 0;
result = 31 * result + (url != null ? url.hashCode() : 0);
return result;
}
}

View File

@@ -0,0 +1,68 @@
package com.mapbox.mapboxsdk.attribution;
import android.graphics.Bitmap;
import android.graphics.PointF;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
public class AttributionLayout {
@Nullable
private Bitmap logo;
@Nullable
private PointF anchorPoint;
private boolean shortText;
public AttributionLayout(@Nullable Bitmap logo, @Nullable PointF anchorPoint, boolean shortText) {
this.logo = logo;
this.anchorPoint = anchorPoint;
this.shortText = shortText;
}
@Nullable
public Bitmap getLogo() {
return logo;
}
@Nullable
public PointF getAnchorPoint() {
return anchorPoint;
}
public boolean isShortText() {
return shortText;
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
AttributionLayout that = (AttributionLayout) o;
if (logo != null ? !logo.equals(that.logo) : that.logo != null) {
return false;
}
return anchorPoint != null ? anchorPoint.equals(that.anchorPoint) : that.anchorPoint == null;
}
@Override
public int hashCode() {
int result = logo != null ? logo.hashCode() : 0;
result = 31 * result + (anchorPoint != null ? anchorPoint.hashCode() : 0);
return result;
}
@NonNull
@Override
public String toString() {
return "AttributionLayout{"
+ "logo=" + logo
+ ", anchorPoint=" + anchorPoint
+ '}';
}
}

View File

@@ -0,0 +1,249 @@
package com.mapbox.mapboxsdk.attribution;
import android.graphics.Bitmap;
import android.graphics.PointF;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.widget.TextView;
import java.util.Arrays;
import java.util.List;
public class AttributionMeasure {
private Bitmap logo;
private Bitmap logoSmall;
private Bitmap snapshot;
private TextView textView;
private TextView textViewShort;
private float margin;
private boolean shorterText;
AttributionMeasure(Bitmap snapshot, Bitmap logo, Bitmap logoSmall, TextView tv, TextView tvShort, float margin) {
this.snapshot = snapshot;
this.logo = logo;
this.logoSmall = logoSmall;
this.textView = tv;
this.textViewShort = tvShort;
this.margin = margin;
}
@Nullable
public AttributionLayout measure() {
Chain chain = new Chain(
new FullLogoLongTextCommand(),
new FullLogoShortTextCommand(),
new SmallLogoLongTextCommand(),
new SmallLogoShortTextCommand(),
new LongTextCommand(),
new ShortTextCommand(),
new NoTextCommand()
);
AttributionLayout attributionLayout = chain.start(this);
shorterText = attributionLayout.isShortText();
return attributionLayout;
}
private static class FullLogoLongTextCommand implements Command {
@Nullable
public AttributionLayout execute(@NonNull AttributionMeasure measure) {
float width = measure.getLogoContainerWidth() + measure.getTextViewContainerWidth();
boolean fitBounds = width <= measure.getMaxSize();
if (fitBounds) {
PointF anchor = calculateAnchor(measure.snapshot, measure.textView, measure.margin);
return new AttributionLayout(measure.logo, anchor, false);
}
return null;
}
}
private static class FullLogoShortTextCommand implements Command {
@Nullable
@Override
public AttributionLayout execute(@NonNull AttributionMeasure measure) {
float width = measure.getLogoContainerWidth() + measure.getTextViewShortContainerWidth();
boolean fitBounds = width <= measure.getMaxSizeShort();
if (fitBounds) {
PointF anchor = calculateAnchor(measure.snapshot, measure.textViewShort, measure.margin);
return new AttributionLayout(measure.logo, anchor, true);
}
return null;
}
}
private static class SmallLogoLongTextCommand implements Command {
@Nullable
@Override
public AttributionLayout execute(@NonNull AttributionMeasure measure) {
float width = measure.getLogoSmallContainerWidth() + measure.getTextViewContainerWidth();
boolean fitBounds = width <= measure.getMaxSize();
if (fitBounds) {
PointF anchor = calculateAnchor(measure.snapshot, measure.textView, measure.margin);
return new AttributionLayout(measure.logoSmall, anchor, false);
}
return null;
}
}
private static class SmallLogoShortTextCommand implements Command {
@Nullable
@Override
public AttributionLayout execute(@NonNull AttributionMeasure measure) {
float width = measure.getLogoContainerWidth() + measure.getTextViewShortContainerWidth();
boolean fitBounds = width <= measure.getMaxSizeShort();
if (fitBounds) {
PointF anchor = calculateAnchor(measure.snapshot, measure.textViewShort, measure.margin);
return new AttributionLayout(measure.logoSmall, anchor, true);
}
return null;
}
}
private static class LongTextCommand implements Command {
@Nullable
@Override
public AttributionLayout execute(@NonNull AttributionMeasure measure) {
float width = measure.getTextViewContainerWidth() + measure.margin;
boolean fitBounds = width <= measure.getMaxSize();
if (fitBounds) {
return new AttributionLayout(null, calculateAnchor(measure.snapshot, measure.textView, measure.margin), false);
}
return null;
}
}
private static class ShortTextCommand implements Command {
@Nullable
@Override
public AttributionLayout execute(@NonNull AttributionMeasure measure) {
float width = measure.getTextViewShortContainerWidth() + measure.margin;
boolean fitBounds = width <= measure.getMaxSizeShort();
if (fitBounds) {
PointF anchor = calculateAnchor(measure.snapshot, measure.textViewShort, measure.margin);
return new AttributionLayout(null, anchor, true);
}
return null;
}
}
private static class NoTextCommand implements Command {
@NonNull
@Override
public AttributionLayout execute(AttributionMeasure measure) {
return new AttributionLayout(null, null, false);
}
}
private static PointF calculateAnchor(Bitmap snapshot, TextView textView, float margin) {
return new PointF(
snapshot.getWidth() - textView.getMeasuredWidth() - margin,
snapshot.getHeight() - margin - textView.getMeasuredHeight()
);
}
public TextView getTextView() {
return shorterText ? textViewShort : textView;
}
private class Chain {
public List<Command> commands;
Chain(Command... commands) {
this.commands = Arrays.asList(commands);
}
@Nullable
public AttributionLayout start(AttributionMeasure measure) {
AttributionLayout attributionLayout = null;
for (Command command : commands) {
attributionLayout = command.execute(measure);
if (attributionLayout != null) {
break;
}
}
return attributionLayout;
}
}
public interface Command {
@Nullable
AttributionLayout execute(AttributionMeasure measure);
}
private float getTextViewContainerWidth() {
return textView.getMeasuredWidth() + margin;
}
private float getLogoContainerWidth() {
return logo.getWidth() + (2 * margin);
}
private float getTextViewShortContainerWidth() {
return textViewShort.getMeasuredWidth() + margin;
}
private float getLogoSmallContainerWidth() {
return logoSmall.getWidth() + (2 * margin);
}
private float getMaxSize() {
return snapshot.getWidth() * 8 / 10;
}
private float getMaxSizeShort() {
return snapshot.getWidth();
}
public static class Builder {
private Bitmap snapshot;
private Bitmap logo;
private Bitmap logoSmall;
private TextView textView;
private TextView textViewShort;
private float marginPadding;
@NonNull
public Builder setSnapshot(Bitmap snapshot) {
this.snapshot = snapshot;
return this;
}
@NonNull
public Builder setLogo(Bitmap logo) {
this.logo = logo;
return this;
}
@NonNull
public Builder setLogoSmall(Bitmap logoSmall) {
this.logoSmall = logoSmall;
return this;
}
@NonNull
public Builder setTextView(TextView textView) {
this.textView = textView;
return this;
}
@NonNull
public Builder setTextViewShort(TextView textViewShort) {
this.textViewShort = textViewShort;
return this;
}
@NonNull
public Builder setMarginPadding(float marginPadding) {
this.marginPadding = marginPadding;
return this;
}
@NonNull
public AttributionMeasure build() {
return new AttributionMeasure(snapshot, logo, logoSmall, textView, textViewShort, marginPadding);
}
}
}

View File

@@ -0,0 +1,314 @@
package com.mapbox.mapboxsdk.attribution;
import android.content.Context;
import android.support.annotation.NonNull;
import android.text.Html;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.URLSpan;
import com.mapbox.mapboxsdk.R;
import java.lang.ref.WeakReference;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Responsible for parsing attribution data coming from Sources and MapSnapshot.
* <p>
* Exposes multiple configuration options to manipulate data being parsed.
* Use the Options object to build these configurations.
* </p>
*/
public class AttributionParser {
private static final String IMPROVE_THIS_MAP = "Improve this map";
private final WeakReference<Context> context;
private final Set<Attribution> attributions = new LinkedHashSet<>();
private final String attributionData;
private final boolean withImproveMap;
private final boolean withCopyrightSign;
private final boolean withTelemetryAttribution;
private final boolean withMapboxAttribution;
AttributionParser(WeakReference<Context> context, String attributionData, boolean withImproveMap,
boolean withCopyrightSign, boolean withTelemetryAttribution, boolean withMapboxAttribution) {
this.context = context;
this.attributionData = attributionData;
this.withImproveMap = withImproveMap;
this.withCopyrightSign = withCopyrightSign;
this.withTelemetryAttribution = withTelemetryAttribution;
this.withMapboxAttribution = withMapboxAttribution;
}
/**
* Get parsed attributions.
*
* @return the attributions
*/
@NonNull
public Set<Attribution> getAttributions() {
return attributions;
}
/**
* Get parsed attribution string.
*
* @return the parsed attribution string
*/
@NonNull
public String createAttributionString() {
return createAttributionString(false);
}
/**
* Get parsed attribution string.
*
* @param shortenedOutput if attribution string should contain shortened output
* @return the parsed attribution string
*/
@NonNull
public String createAttributionString(boolean shortenedOutput) {
StringBuilder stringBuilder = new StringBuilder(withCopyrightSign ? "" : "© ");
int counter = 0;
for (Attribution attribution : attributions) {
counter++;
stringBuilder.append(!shortenedOutput ? attribution.getTitle() : attribution.getTitleAbbreviated());
if (counter != attributions.size()) {
stringBuilder.append(" / ");
}
}
return stringBuilder.toString();
}
/**
* Main attribution for configuration
*/
protected void parse() {
parseAttributions();
addAdditionalAttributions();
}
/**
* Parse attributions
*/
private void parseAttributions() {
SpannableStringBuilder htmlBuilder = (SpannableStringBuilder) fromHtml(attributionData);
URLSpan[] urlSpans = htmlBuilder.getSpans(0, htmlBuilder.length(), URLSpan.class);
for (URLSpan urlSpan : urlSpans) {
parseUrlSpan(htmlBuilder, urlSpan);
}
}
/**
* Parse an URLSpan containing an attribution.
*
* @param htmlBuilder the html builder
* @param urlSpan the url span to be parsed
*/
private void parseUrlSpan(@NonNull SpannableStringBuilder htmlBuilder, @NonNull URLSpan urlSpan) {
String url = urlSpan.getURL();
if (isUrlValid(url)) {
String anchor = parseAnchorValue(htmlBuilder, urlSpan);
if (isImproveThisMapAnchor(anchor)) {
anchor = translateImproveThisMapAnchor(anchor);
}
attributions.add(new Attribution(anchor, url));
}
}
/**
* Invoked to validate if an url is valid to be included in the final attribution.
*
* @param url the url to be validated
* @return if the url is valid
*/
private boolean isUrlValid(@NonNull String url) {
return isValidForImproveThisMap(url) && isValidForMapbox(url);
}
/**
* Invoked to validate if an anchor is equal to Improve this map coming from tilesets.
*
* @param anchor the anchor to be validated
* @return if the url is valid
*/
private boolean isImproveThisMapAnchor(String anchor) {
return anchor.equals(IMPROVE_THIS_MAP);
}
/**
* Invoked to replace the english Improve this map with localized variant.
*
* @param anchor the anchor to be translated
* @return the translated anchor
*/
private String translateImproveThisMapAnchor(String anchor) {
Context context = this.context.get();
if (context != null) {
anchor = context.getString(R.string.mapbox_telemetryImproveMap);
}
return anchor;
}
/**
* Invoked to validate if an url is valid for the improve map configuration.
*
* @param url the url to be validated
* @return if the url is valid for improve this map
*/
private boolean isValidForImproveThisMap(@NonNull String url) {
return withImproveMap || !(Attribution.IMPROVE_MAP_URLS.contains(url));
}
/**
* Invoked to validate if an url is valid for the Mapbox configuration.
*
* @param url the url to be validated
* @return if the url is valid for Mapbox
*/
private boolean isValidForMapbox(@NonNull String url) {
return withMapboxAttribution || !url.equals(Attribution.MAPBOX_URL);
}
/**
* Parse the attribution by parsing the anchor value of html href tag.
*
* @param htmlBuilder the html builder
* @param urlSpan the current urlSpan
* @return the parsed anchor value
*/
@NonNull
private String parseAnchorValue(SpannableStringBuilder htmlBuilder, URLSpan urlSpan) {
int start = htmlBuilder.getSpanStart(urlSpan);
int end = htmlBuilder.getSpanEnd(urlSpan);
int length = end - start;
char[] charKey = new char[length];
htmlBuilder.getChars(start, end, charKey, 0);
return stripCopyright(String.valueOf(charKey));
}
/**
* Utility to strip the copyright sign from an attribution
*
* @param anchor the attribution string to strip
* @return the stripped attribution string without the copyright sign
*/
@NonNull
private String stripCopyright(@NonNull String anchor) {
if (!withCopyrightSign && anchor.startsWith("© ")) {
anchor = anchor.substring(2, anchor.length());
}
return anchor;
}
/**
* Invoked to manually add attributions
*/
private void addAdditionalAttributions() {
if (withTelemetryAttribution) {
Context context = this.context.get();
attributions.add(
new Attribution(
context != null ? context.getString(R.string.mapbox_telemetrySettings) : Attribution.TELEMETRY,
Attribution.TELEMETRY_URL
)
);
}
}
/**
* Convert a string to a spanned html representation.
*
* @param html the string to convert
* @return the spanned html representation
*/
private static Spanned fromHtml(String html) {
Spanned result;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
result = Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY);
} else {
result = Html.fromHtml(html);
}
return result;
}
/**
* Builder to configure using an AttributionParser.
* <p>
* AttributionData, set with {@link #withAttributionData(String...)}, is the only required property to build
* the underlying AttributionParser. Other properties include trimming the copyright sign, adding telemetry
* attribution or hiding attribution as improve this map and Mapbox.
* </p>
*/
public static class Options {
private final WeakReference<Context> context;
private boolean withImproveMap = true;
private boolean withCopyrightSign = true;
private boolean withTelemetryAttribution = false;
private boolean withMapboxAttribution = true;
private String[] attributionDataStringArray;
public Options(@NonNull Context context) {
this.context = new WeakReference<>(context);
}
@NonNull
public Options withAttributionData(String... attributionData) {
this.attributionDataStringArray = attributionData;
return this;
}
@NonNull
public Options withImproveMap(boolean withImproveMap) {
this.withImproveMap = withImproveMap;
return this;
}
@NonNull
public Options withCopyrightSign(boolean withCopyrightSign) {
this.withCopyrightSign = withCopyrightSign;
return this;
}
@NonNull
public Options withTelemetryAttribution(boolean withTelemetryAttribution) {
this.withTelemetryAttribution = withTelemetryAttribution;
return this;
}
@NonNull
public Options withMapboxAttribution(boolean withMapboxAttribution) {
this.withMapboxAttribution = withMapboxAttribution;
return this;
}
@NonNull
public AttributionParser build() {
if (attributionDataStringArray == null) {
throw new IllegalStateException("Using builder without providing attribution data");
}
String fullAttributionString = parseAttribution(attributionDataStringArray);
AttributionParser attributionParser = new AttributionParser(
context,
fullAttributionString,
withImproveMap,
withCopyrightSign,
withTelemetryAttribution,
withMapboxAttribution
);
attributionParser.parse();
return attributionParser;
}
private String parseAttribution(String[] attribution) {
StringBuilder builder = new StringBuilder();
for (String attr : attribution) {
if (!attr.isEmpty()) {
builder.append(attr);
}
}
return builder.toString();
}
}
}

View File

@@ -0,0 +1,4 @@
/**
* Contains the Mapbox Maps Android Attribution API classes.
*/
package com.mapbox.mapboxsdk.attribution;

View File

@@ -0,0 +1,397 @@
package com.mapbox.mapboxsdk.camera;
import android.content.res.TypedArray;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.FloatRange;
import android.support.annotation.Keep;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.Size;
import com.mapbox.mapboxsdk.R;
import com.mapbox.mapboxsdk.constants.MapboxConstants;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.utils.MathUtils;
import java.util.Arrays;
/**
* Resembles the position, angle, zoom and tilt of the user's viewpoint.
*/
public final class CameraPosition implements Parcelable {
public static final CameraPosition DEFAULT = new CameraPosition(new LatLng(), 0, 0, 0, new double[] {0, 0, 0, 0});
public static final Parcelable.Creator<CameraPosition> CREATOR =
new Parcelable.Creator<CameraPosition>() {
public CameraPosition createFromParcel(Parcel in) {
double bearing = in.readDouble();
LatLng target = in.readParcelable(LatLng.class.getClassLoader());
double tilt = in.readDouble();
double zoom = in.readDouble();
double[] padding = new double[4];
in.readDoubleArray(padding);
return new CameraPosition(target, zoom, tilt, bearing, padding);
}
public CameraPosition[] newArray(int size) {
return new CameraPosition[size];
}
};
/**
* Direction that the camera is pointing in, in degrees clockwise from north.
*/
@Keep
public final double bearing;
/**
* The location that the camera is pointing at.
*/
@Keep
public final LatLng target;
/**
* The angle, in degrees, of the camera angle from the nadir (directly facing the Earth).
* See {@link Builder#tilt(double)} for details of restrictions on the range of values.
*/
@Keep
public final double tilt;
/**
* Zoom level near the center of the screen. See {@link Builder#zoom(double)} for the definition of the camera's
* zoom level.
*/
@Keep
public final double zoom;
/**
* Padding in pixels. Specified in left, top, right, bottom order.
* See {@link Builder#padding(double[])} for the definition of the camera's padding.
*/
@Keep
public final double[] padding;
/**
* Constructs a CameraPosition.
*
* @param target The target location to align with the center of the screen.
* @param zoom Zoom level at target. See zoom(float) for details of restrictions.
* @param tilt The camera angle, in degrees, from the nadir (directly down). See tilt(float)
* for details of restrictions.
* @param bearing Direction that the camera is pointing in, in degrees clockwise from north.
* This value will be normalized to be within 0 degrees inclusive and 360 degrees
* exclusive.
* @throws NullPointerException if target is null
* @throws IllegalArgumentException if tilt is outside the range of 0 to 90 degrees inclusive.
* @deprecated use {@link CameraPosition#CameraPosition(LatLng, double, double, double, double[])} instead.
*/
@Deprecated
CameraPosition(LatLng target, double zoom, double tilt, double bearing) {
this(target, zoom, tilt, bearing, null);
}
/**
* Constructs a CameraPosition.
*
* @param target The target location to align with the center of the screen.
* @param zoom Zoom level at target. See zoom(float) for details of restrictions.
* @param tilt The camera angle, in degrees, from the nadir (directly down). See tilt(float)
* for details of restrictions.
* @param bearing Direction that the camera is pointing in, in degrees clockwise from north.
* This value will be normalized to be within 0 degrees inclusive and 360 degrees
* exclusive.
* @param padding Padding in pixels. Specified in left, top, right, bottom order.
* @throws NullPointerException if target is null
* @throws IllegalArgumentException if tilt is outside the range of 0 to 90 degrees inclusive.
*/
@Keep
CameraPosition(LatLng target, double zoom, double tilt, double bearing, double[] padding) {
this.target = target;
this.bearing = bearing;
this.tilt = tilt;
this.zoom = zoom;
this.padding = padding;
}
/**
* Describe the kinds of special objects contained in this Parcelable's
* marshalled representation.
*
* @return integer 0.
*/
@Override
public int describeContents() {
return 0;
}
/**
* Flatten this object in to a Parcel.
*
* @param out The Parcel in which the object should be written.
* @param flags Additional flags about how the object should be written. May be 0 or
* {@link #PARCELABLE_WRITE_RETURN_VALUE}.
*/
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeDouble(bearing);
out.writeParcelable(target, flags);
out.writeDouble(tilt);
out.writeDouble(zoom);
out.writeDoubleArray(padding);
}
/**
* Returns a String with the camera target, zoom, bearing and tilt.
*
* @return A String with CameraPosition information.
*/
@Override
public String toString() {
return "Target: " + target + ", Zoom:" + zoom + ", Bearing:" + bearing + ", Tilt:" + tilt
+ ", Padding:" + Arrays.toString(padding);
}
/**
* Compares this {@link CameraPosition} object with another {@link CameraPosition} and
* determines if their target, zoom, tilt, and bearing match.
*
* @param o Another {@link CameraPosition} to compare with this object.
* @return True if target, zoom, tilt, and bearing match this {@link CameraPosition} object.
* Else, false.
*/
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CameraPosition cameraPosition = (CameraPosition) o;
if (target != null && !target.equals(cameraPosition.target)) {
return false;
} else if (zoom != cameraPosition.zoom) {
return false;
} else if (tilt != cameraPosition.tilt) {
return false;
} else if (bearing != cameraPosition.bearing) {
return false;
} else if (!Arrays.equals(padding, cameraPosition.padding)) {
return false;
}
return true;
}
/**
* Gives an integer which can be used as the bucket number for storing elements of the set/map.
* This bucket number is the address of the element inside the set/map. There's no guarantee
* that this hash value will be consistent between different Java implementations, or even
* between different execution runs of the same program.
*
* @return integer value you can use for storing element.
*/
@Override
public int hashCode() {
int result;
long temp;
temp = Double.doubleToLongBits(bearing);
result = (int) (temp ^ (temp >>> 32));
result = 31 * result + (target != null ? target.hashCode() : 0);
temp = Double.doubleToLongBits(tilt);
result = 31 * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(zoom);
result = 31 * result + (int) (temp ^ (temp >>> 32));
result = 31 * result + Arrays.hashCode(padding);
return result;
}
/**
* Builder for composing CameraPosition objects.
*/
public static final class Builder {
private double bearing = -1;
@Nullable
private LatLng target = null;
private double tilt = -1;
private double zoom = -1;
private double[] padding = null;
/**
* Create an empty builder.
*/
public Builder() {
super();
}
/**
* Create a builder with an existing CameraPosition data.
*
* @param previous Existing CameraPosition values to use
*/
public Builder(@Nullable CameraPosition previous) {
super();
if (previous != null) {
this.bearing = previous.bearing;
this.target = previous.target;
this.tilt = previous.tilt;
this.zoom = previous.zoom;
this.padding = previous.padding;
}
}
/**
* Create a builder with an existing CameraPosition data.
*
* @param typedArray TypedArray containing attribute values
*/
public Builder(@Nullable TypedArray typedArray) {
super();
if (typedArray != null) {
this.bearing = typedArray.getFloat(R.styleable.mapbox_MapView_mapbox_cameraBearing, 0.0f);
double lat = typedArray.getFloat(R.styleable.mapbox_MapView_mapbox_cameraTargetLat, 0.0f);
double lng = typedArray.getFloat(R.styleable.mapbox_MapView_mapbox_cameraTargetLng, 0.0f);
this.target = new LatLng(lat, lng);
this.tilt = typedArray.getFloat(R.styleable.mapbox_MapView_mapbox_cameraTilt, 0.0f);
this.zoom = typedArray.getFloat(R.styleable.mapbox_MapView_mapbox_cameraZoom, 0.0f);
}
}
/**
* Create a builder from an existing CameraPositionUpdate update.
*
* @param update Update containing camera options
*/
public Builder(@Nullable CameraUpdateFactory.CameraPositionUpdate update) {
super();
if (update != null) {
bearing = update.getBearing();
target = update.getTarget();
tilt = update.getTilt();
zoom = update.getZoom();
padding = update.getPadding();
}
}
/**
* Create builder from an existing CameraPositionUpdate update.
*
* @param update Update containing camera options
*/
public Builder(@Nullable CameraUpdateFactory.ZoomUpdate update) {
super();
if (update != null) {
this.zoom = update.getZoom();
}
}
/**
* Sets the direction that the camera is pointing in, in degrees clockwise from north.
*
* @param bearing Bearing
* @return this
*/
@NonNull
public Builder bearing(double bearing) {
double direction = bearing;
while (direction >= 360) {
direction -= 360;
}
while (direction < 0) {
direction += 360;
}
this.bearing = direction;
return this;
}
/**
* Sets the location where the camera is pointing at.
*
* @param location target of the camera
* @return this
*/
@NonNull
public Builder target(LatLng location) {
this.target = location;
return this;
}
/**
* Set the tilt of the camera in degrees
* <p>
* value is clamped to {@link MapboxConstants#MINIMUM_TILT} and {@link MapboxConstants#MAXIMUM_TILT}.
* </p>
*
* @param tilt Tilt value of the camera
* @return this
*/
@NonNull
public Builder tilt(@FloatRange(from = MapboxConstants.MINIMUM_TILT,
to = MapboxConstants.MAXIMUM_TILT) double tilt) {
this.tilt = MathUtils.clamp(tilt, MapboxConstants.MINIMUM_TILT, MapboxConstants.MAXIMUM_TILT);
return this;
}
/**
* Set the zoom of the camera
* <p>
* Zoom ranges from {@link MapboxConstants#MINIMUM_ZOOM} to {@link MapboxConstants#MAXIMUM_ZOOM}
* </p>
*
* @param zoom Zoom value of the camera
* @return this
*/
@NonNull
public Builder zoom(@FloatRange(from = MapboxConstants.MINIMUM_ZOOM,
to = MapboxConstants.MAXIMUM_ZOOM) double zoom) {
this.zoom = zoom;
return this;
}
/**
* Padding in pixels that shifts the viewport by the specified amount.
* Applied padding is going to persist and impact following camera transformations.
* <p>
* Specified in left, top, right, bottom order.
* </p>
*
* @param padding Camera padding
* @return this
*/
@NonNull
public Builder padding(@Size(4) double[] padding) {
this.padding = padding;
return this;
}
/**
* Padding in pixels that shifts the viewport by the specified amount.
* Applied padding is going to persist and impact following camera transformations.
* <p>
* Specified in left, top, right, bottom order.
* </p>
*
* @return this
*/
@NonNull
public Builder padding(double left, double top, double right, double bottom) {
this.padding = new double[] {left, top, right, bottom};
return this;
}
/**
* Builds the CameraPosition.
*
* @return CameraPosition
*/
public CameraPosition build() {
return new CameraPosition(target, zoom, tilt, bearing, padding);
}
}
}

View File

@@ -0,0 +1,23 @@
package com.mapbox.mapboxsdk.camera;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.mapbox.mapboxsdk.maps.MapboxMap;
/**
* Interface definition for camera updates.
*/
public interface CameraUpdate {
/**
* Get the camera position from the camera update.
*
* @param mapboxMap Map object to build the position from
* @return the camera position from the implementing camera update
*/
@Nullable
CameraPosition getCameraPosition(@NonNull MapboxMap mapboxMap);
}

View File

@@ -0,0 +1,637 @@
package com.mapbox.mapboxsdk.camera;
import android.graphics.Point;
import android.graphics.PointF;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.geometry.LatLngBounds;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.Projection;
import com.mapbox.mapboxsdk.maps.UiSettings;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
/**
* Factory for creating CameraUpdate objects.
*/
public final class CameraUpdateFactory {
/**
* Returns a CameraUpdate that moves the camera to a specified CameraPosition.
*
* @param cameraPosition Camera Position to change to
* @return CameraUpdate Final Camera Position
*/
public static CameraUpdate newCameraPosition(@NonNull CameraPosition cameraPosition) {
return new CameraPositionUpdate(cameraPosition.bearing, cameraPosition.target, cameraPosition.tilt,
cameraPosition.zoom, cameraPosition.padding);
}
/**
* Returns a CameraUpdate that moves the center of the screen to a latitude and longitude
* specified by a LatLng object. This centers the camera on the LatLng object.
*
* @param latLng Target location to change to
* @return CameraUpdate Final Camera Position
*/
public static CameraUpdate newLatLng(@NonNull LatLng latLng) {
return new CameraPositionUpdate(-1, latLng, -1, -1, null);
}
/**
* Returns a CameraUpdate that transforms the camera such that the specified
* latitude/longitude bounds are centered on screen at the greatest possible zoom level while maintaining
* current camera position bearing and tilt values.
* <p>
* You can specify padding, in order to inset the bounding box from the map view's edges.
* The padding will not persist and impact following camera transformations.
* </p>
*
* @param bounds Bounds to match Camera position with
* @param padding Padding added to the bounds
* @return CameraUpdate Final Camera Position
*/
public static CameraUpdate newLatLngBounds(@NonNull LatLngBounds bounds, int padding) {
return newLatLngBounds(bounds, padding, padding, padding, padding);
}
/**
* Returns a CameraUpdate that transforms the camera such that the specified
* latitude/longitude bounds are centered on screen at the greatest possible zoom level while using
* provided bearing and tilt values.
* <p>
* You can specify padding, in order to inset the bounding box from the map view's edges.
* The padding will not persist and impact following camera transformations.
* </p>
*
* @param bounds Bounds to match Camera position with
* @param bearing Bearing to take in account when generating the bounds
* @param tilt Tilt to take in account when generating the bounds
* @param padding Padding added to the bounds
* @return CameraUpdate Final Camera Position
*/
public static CameraUpdate newLatLngBounds(@NonNull LatLngBounds bounds, double bearing, double tilt, int padding) {
return newLatLngBounds(bounds, bearing, tilt, padding, padding, padding, padding);
}
/**
* Returns a CameraUpdate that transforms the camera such that the specified
* latitude/longitude bounds are centered on screen at the greatest possible zoom level while maintaining
* current camera position bearing and tilt values.
* <p>
* You can specify padding, in order to inset the bounding box from the map view's edges.
* The padding will not persist and impact following camera transformations.
* </p>
*
* @param bounds Bounds to base the Camera position out of
* @param paddingLeft Padding left of the bounds
* @param paddingTop Padding top of the bounds
* @param paddingRight Padding right of the bounds
* @param paddingBottom Padding bottom of the bounds
* @return CameraUpdate Final Camera Position
*/
public static CameraUpdate newLatLngBounds(@NonNull LatLngBounds bounds, int paddingLeft, int paddingTop,
int paddingRight, int paddingBottom) {
return new CameraBoundsUpdate(bounds, null, null, paddingLeft, paddingTop, paddingRight, paddingBottom);
}
/**
* Returns a CameraUpdate that transforms the camera such that the specified
* latitude/longitude bounds are centered on screen at the greatest possible zoom level while using
* provided bearing and tilt values.
* <p>
* You can specify padding, in order to inset the bounding box from the map view's edges.
* The padding will not persist and impact following camera transformations.
* </p>
*
* @param bounds Bounds to base the Camera position out of
* @param bearing Bearing to take in account when generating the bounds
* @param tilt Tilt to take in account when generating the bounds
* @param paddingLeft Padding left of the bounds
* @param paddingTop Padding top of the bounds
* @param paddingRight Padding right of the bounds
* @param paddingBottom Padding bottom of the bounds
* @return CameraUpdate Final Camera Position
*/
public static CameraUpdate newLatLngBounds(@NonNull LatLngBounds bounds, double bearing, double tilt,
int paddingLeft, int paddingTop, int paddingRight, int paddingBottom) {
return new CameraBoundsUpdate(bounds, bearing, tilt, paddingLeft, paddingTop, paddingRight, paddingBottom);
}
/**
* Returns a CameraUpdate that moves the center of the screen to a latitude and longitude
* specified by a LatLng object taking the specified padding into account.
*
* @param latLng Target location to change to
* @param zoom Zoom level to change to
* @return CameraUpdate Final Camera Position
*/
public static CameraUpdate newLatLngZoom(@NonNull LatLng latLng, double zoom) {
return new CameraPositionUpdate(-1, latLng, -1, zoom, null);
}
/**
* Returns a CameraUpdate that moves the center of the screen to a latitude and longitude
* specified by a LatLng object, and moves to the given zoom level.
* Applied padding is going to persist and impact following camera transformations.
*
* @param latLng Target location to change to
* @param left Left padding
* @param top Top padding
* @param right Right padding
* @param bottom Bottom padding
* @return CameraUpdate Final Camera Position
*/
public static CameraUpdate newLatLngPadding(@NonNull LatLng latLng,
double left, double top, double right, double bottom) {
return new CameraPositionUpdate(-1, latLng, -1, -1, new double[] {left, top, right, bottom});
}
/**
* Returns a CameraUpdate that shifts the zoom level of the current camera viewpoint.
*
* @param amount Amount of zoom level to change with
* @param focus Focus point of zoom
* @return CameraUpdate Final Camera Position
*/
public static CameraUpdate zoomBy(double amount, Point focus) {
return new ZoomUpdate(amount, focus.x, focus.y);
}
/**
* Returns a CameraUpdate that shifts the zoom level of the current camera viewpoint.
*
* @param amount Amount of zoom level to change with
* @return CameraUpdate Final Camera Position
*/
public static CameraUpdate zoomBy(double amount) {
return new ZoomUpdate(ZoomUpdate.ZOOM_BY, amount);
}
/**
* Returns a CameraUpdate that zooms in on the map by moving the viewpoint's height closer to
* the Earth's surface. The zoom increment is 1.0.
*
* @return CameraUpdate Final Camera Position
*/
public static CameraUpdate zoomIn() {
return new ZoomUpdate(ZoomUpdate.ZOOM_IN);
}
/**
* Returns a CameraUpdate that zooms out on the map by moving the viewpoint's height farther
* away from the Earth's surface. The zoom increment is -1.0.
*
* @return CameraUpdate Final Camera Position
*/
public static CameraUpdate zoomOut() {
return new ZoomUpdate(ZoomUpdate.ZOOM_OUT);
}
/**
* Returns a CameraUpdate that moves the camera viewpoint to a particular zoom level.
*
* @param zoom Zoom level to change to
* @return CameraUpdate Final Camera Position
*/
public static CameraUpdate zoomTo(double zoom) {
return new ZoomUpdate(ZoomUpdate.ZOOM_TO, zoom);
}
/**
* Returns a CameraUpdate that moves the camera viewpoint to a particular bearing.
*
* @param bearing Bearing to change to
* @return CameraUpdate Final Camera Position
*/
public static CameraUpdate bearingTo(double bearing) {
return new CameraPositionUpdate(bearing, null, -1, -1, null);
}
/**
* Returns a CameraUpdate that moves the camera viewpoint to a particular tilt.
*
* @param tilt Tilt to change to
* @return CameraUpdate Final Camera Position
*/
public static CameraUpdate tiltTo(double tilt) {
return new CameraPositionUpdate(-1, null, tilt, -1, null);
}
/**
* Returns a CameraUpdate that when animated changes the camera padding.
* Applied padding is going to persist and impact following camera transformations.
* <p>
* Specified in left, top, right, bottom order.
* </p>
*
* @param padding Padding to change to
* @return CameraUpdate Final Camera Position
*/
public static CameraUpdate paddingTo(double[] padding) {
return new CameraPositionUpdate(-1, null, -1, -1, padding);
}
/**
* Returns a CameraUpdate that when animated changes the camera padding.
* Applied padding is going to persist and impact following camera transformations.
* <p>
* Specified in left, top, right, bottom order.
* </p>
*
* @return CameraUpdate Final Camera Position
*/
public static CameraUpdate paddingTo(double left, double top, double right, double bottom) {
return paddingTo(new double[] {left, top, right, bottom});
}
//
// CameraUpdate types
//
static final class CameraPositionUpdate implements CameraUpdate {
private final double bearing;
private final LatLng target;
private final double tilt;
private final double zoom;
private final double[] padding;
CameraPositionUpdate(double bearing, LatLng target, double tilt, double zoom, double[] padding) {
this.bearing = bearing;
this.target = target;
this.tilt = tilt;
this.zoom = zoom;
this.padding = padding;
}
public LatLng getTarget() {
return target;
}
public double getBearing() {
return bearing;
}
public double getTilt() {
return tilt;
}
public double getZoom() {
return zoom;
}
public double[] getPadding() {
return padding;
}
@Override
public CameraPosition getCameraPosition(@NonNull MapboxMap mapboxMap) {
if (target == null) {
CameraPosition previousPosition = mapboxMap.getCameraPosition();
return new CameraPosition.Builder(this)
.target(previousPosition.target)
.build();
}
return new CameraPosition.Builder(this).build();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CameraPositionUpdate that = (CameraPositionUpdate) o;
if (Double.compare(that.bearing, bearing) != 0) {
return false;
}
if (Double.compare(that.tilt, tilt) != 0) {
return false;
}
if (Double.compare(that.zoom, zoom) != 0) {
return false;
}
if (target != null ? !target.equals(that.target) : that.target != null) {
return false;
}
return Arrays.equals(padding, that.padding);
}
@Override
public int hashCode() {
int result;
long temp;
temp = Double.doubleToLongBits(bearing);
result = (int) (temp ^ (temp >>> 32));
result = 31 * result + (target != null ? target.hashCode() : 0);
temp = Double.doubleToLongBits(tilt);
result = 31 * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(zoom);
result = 31 * result + (int) (temp ^ (temp >>> 32));
result = 31 * result + Arrays.hashCode(padding);
return result;
}
@Override
public String toString() {
return "CameraPositionUpdate{"
+ "bearing=" + bearing
+ ", target=" + target
+ ", tilt=" + tilt
+ ", zoom=" + zoom
+ ", padding=" + Arrays.toString(padding)
+ '}';
}
}
static final class CameraBoundsUpdate implements CameraUpdate {
private final LatLngBounds bounds;
private final int[] padding;
private final Double bearing;
private final Double tilt;
CameraBoundsUpdate(LatLngBounds bounds, Double bearing, Double tilt, int[] padding) {
this.bounds = bounds;
this.padding = padding;
this.bearing = bearing;
this.tilt = tilt;
}
CameraBoundsUpdate(LatLngBounds bounds, Double bearing, Double tilt, int paddingLeft,
int paddingTop, int paddingRight, int paddingBottom) {
this(bounds, bearing, tilt, new int[] {paddingLeft, paddingTop, paddingRight, paddingBottom});
}
public LatLngBounds getBounds() {
return bounds;
}
public int[] getPadding() {
return padding;
}
@Override
public CameraPosition getCameraPosition(@NonNull MapboxMap mapboxMap) {
if (bearing == null && tilt == null) {
// use current camera position tilt and bearing
return mapboxMap.getCameraForLatLngBounds(bounds, padding);
} else {
// use provided tilt and bearing
assert bearing != null;
assert tilt != null;
return mapboxMap.getCameraForLatLngBounds(bounds, padding, bearing, tilt);
}
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CameraBoundsUpdate that = (CameraBoundsUpdate) o;
if (!bounds.equals(that.bounds)) {
return false;
}
return Arrays.equals(padding, that.padding);
}
@Override
public int hashCode() {
int result = bounds.hashCode();
result = 31 * result + Arrays.hashCode(padding);
return result;
}
@Override
public String toString() {
return "CameraBoundsUpdate{"
+ "bounds=" + bounds
+ ", padding=" + Arrays.toString(padding)
+ '}';
}
}
static final class CameraMoveUpdate implements CameraUpdate {
private float x;
private float y;
CameraMoveUpdate(float x, float y) {
this.x = x;
this.y = y;
}
@Override
public CameraPosition getCameraPosition(@NonNull MapboxMap mapboxMap) {
UiSettings uiSettings = mapboxMap.getUiSettings();
Projection projection = mapboxMap.getProjection();
// Calculate the new center point
float viewPortWidth = uiSettings.getWidth();
float viewPortHeight = uiSettings.getHeight();
int[] padding = mapboxMap.getPadding();
// we inverse the map padding, is reapplied when using moveTo/easeTo or animateTo
PointF targetPoint = new PointF(
(viewPortWidth - padding[0] + padding[1]) / 2 + x,
(viewPortHeight + padding[1] - padding[3]) / 2 + y
);
LatLng latLng = projection.fromScreenLocation(targetPoint);
CameraPosition previousPosition = mapboxMap.getCameraPosition();
CameraPosition position =
new CameraPosition.Builder()
.target(latLng)
.zoom(previousPosition.zoom)
.tilt(previousPosition.tilt)
.bearing(previousPosition.bearing)
.build();
return position;
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CameraMoveUpdate that = (CameraMoveUpdate) o;
if (Float.compare(that.x, x) != 0) {
return false;
}
return Float.compare(that.y, y) == 0;
}
@Override
public int hashCode() {
int result = (x != +0.0f ? Float.floatToIntBits(x) : 0);
result = 31 * result + (y != +0.0f ? Float.floatToIntBits(y) : 0);
return result;
}
@Override
public String toString() {
return "CameraMoveUpdate{"
+ "x=" + x
+ ", y=" + y
+ '}';
}
}
static final class ZoomUpdate implements CameraUpdate {
@IntDef( {ZOOM_IN, ZOOM_OUT, ZOOM_BY, ZOOM_TO, ZOOM_TO_POINT})
@Retention(RetentionPolicy.SOURCE)
@interface Type {
}
static final int ZOOM_IN = 0;
static final int ZOOM_OUT = 1;
static final int ZOOM_BY = 2;
static final int ZOOM_TO = 3;
static final int ZOOM_TO_POINT = 4;
@Type
private final int type;
private final double zoom;
private float x;
private float y;
ZoomUpdate(@Type int type) {
this.type = type;
this.zoom = 0;
}
ZoomUpdate(@Type int type, double zoom) {
this.type = type;
this.zoom = zoom;
}
ZoomUpdate(double zoom, float x, float y) {
this.type = ZOOM_TO_POINT;
this.zoom = zoom;
this.x = x;
this.y = y;
}
public double getZoom() {
return zoom;
}
@Type
public int getType() {
return type;
}
public float getX() {
return x;
}
public float getY() {
return y;
}
double transformZoom(double currentZoom) {
switch (getType()) {
case CameraUpdateFactory.ZoomUpdate.ZOOM_IN:
currentZoom++;
break;
case CameraUpdateFactory.ZoomUpdate.ZOOM_OUT:
currentZoom--;
if (currentZoom < 0) {
currentZoom = 0;
}
break;
case CameraUpdateFactory.ZoomUpdate.ZOOM_TO:
currentZoom = getZoom();
break;
case CameraUpdateFactory.ZoomUpdate.ZOOM_BY:
currentZoom = currentZoom + getZoom();
break;
case CameraUpdateFactory.ZoomUpdate.ZOOM_TO_POINT:
currentZoom = currentZoom + getZoom();
break;
}
return currentZoom;
}
@Override
public CameraPosition getCameraPosition(@NonNull MapboxMap mapboxMap) {
CameraPosition cameraPosition = mapboxMap.getCameraPosition();
if (getType() != CameraUpdateFactory.ZoomUpdate.ZOOM_TO_POINT) {
return new CameraPosition.Builder(cameraPosition)
.zoom(transformZoom(cameraPosition.zoom))
.build();
} else {
return new CameraPosition.Builder(cameraPosition)
.zoom(transformZoom(cameraPosition.zoom))
.target(mapboxMap.getProjection().fromScreenLocation(new PointF(getX(), getY())))
.build();
}
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ZoomUpdate that = (ZoomUpdate) o;
if (type != that.type) {
return false;
}
if (Double.compare(that.zoom, zoom) != 0) {
return false;
}
if (Float.compare(that.x, x) != 0) {
return false;
}
return Float.compare(that.y, y) == 0;
}
@Override
public int hashCode() {
int result;
long temp;
result = type;
temp = Double.doubleToLongBits(zoom);
result = 31 * result + (int) (temp ^ (temp >>> 32));
result = 31 * result + (x != +0.0f ? Float.floatToIntBits(x) : 0);
result = 31 * result + (y != +0.0f ? Float.floatToIntBits(y) : 0);
return result;
}
@Override
public String toString() {
return "ZoomUpdate{"
+ "type=" + type
+ ", zoom=" + zoom
+ ", x=" + x
+ ", y=" + y
+ '}';
}
}
}

View File

@@ -0,0 +1,4 @@
/**
* Contains the Mapbox Maps Android Camera API classes.
*/
package com.mapbox.mapboxsdk.camera;

View File

@@ -0,0 +1,67 @@
package com.mapbox.mapboxsdk.constants;
/**
* Contains constants used throughout the sdk classes.
*/
public class GeometryConstants {
/**
* The <a href='http://en.wikipedia.org/wiki/Earth_radius#Equatorial_radius'>equatorial radius</a>
* value in meters
*/
public static final int RADIUS_EARTH_METERS = 6378137;
/**
* This constant represents the lowest longitude value available to represent a wrapped geolocation.
*/
public static final double MIN_WRAP_LONGITUDE = -180;
/**
* This constant represents the highest longitude value available to represent a wrapped geolocation.
*/
public static final double MAX_WRAP_LONGITUDE = 180;
/**
* This constant represents the lowest longitude value available to represent a geolocation.
*/
public static final double MIN_LONGITUDE = -Double.MAX_VALUE;
/**
* This constant represents the highest longitude value available to represent a geolocation.
*/
public static final double MAX_LONGITUDE = Double.MAX_VALUE;
/**
* This constant represents the lowest latitude value available to represent a geolocation.
*/
public static final double MIN_LATITUDE = -90;
/**
* This constant represents the latitude span when representing a geolocation.
*/
public static final double LATITUDE_SPAN = 180;
/**
* This constant represents the longitude span when representing a geolocation.
*/
public static final double LONGITUDE_SPAN = 360;
/**
* This constant represents the highest latitude value available to represent a geolocation.
*/
public static final double MAX_LATITUDE = 90;
/**
* Maximum latitude value in Mercator projection.
*/
public static final double MAX_MERCATOR_LATITUDE = 85.05112877980659;
/**
* Minimum latitude value in Mercator projection.
*/
public static final double MIN_MERCATOR_LATITUDE = -85.05112877980659;
private GeometryConstants() {
// Private constructor to prevent initializing of this class.
}
}

View File

@@ -0,0 +1,232 @@
package com.mapbox.mapboxsdk.constants;
import java.util.Locale;
/**
* MapboxConstants exposes Mapbox related constants
*/
public class MapboxConstants {
/**
* Default Locale for data processing (ex: String.toLowerCase(MAPBOX_LOCALE, "foo"))
*/
public static final Locale MAPBOX_LOCALE = Locale.US;
/**
* The name of the desired preferences file for Android's SharedPreferences.
*/
public static final String MAPBOX_SHARED_PREFERENCES = "MapboxSharedPreferences";
/**
* Key used to switch storage to external in AndroidManifest.xml
*/
public static final String KEY_META_DATA_SET_STORAGE_EXTERNAL = "com.mapbox.SetStorageExternal";
/**
* Default value for KEY_META_DATA_SET_STORAGE_EXTERNAL (default is internal storage)
*/
public static final boolean DEFAULT_SET_STORAGE_EXTERNAL = false;
/**
* Key used to switch Tile Download Measuring on/off in AndroidManifest.xml
*/
public static final String KEY_META_DATA_MEASURE_TILE_DOWNLOAD_ON = "com.mapbox.MeasureTileDownloadOn";
/**
* Default value for KEY_META_DATA_MEASURE_TILE_DOWNLOAD_ON (default is off)
*/
public static final boolean DEFAULT_MEASURE_TILE_DOWNLOAD_ON = false;
public static final String KEY_PREFERENCE_SKU_TOKEN = "com.mapbox.mapboxsdk.accounts.skutoken";
public static final String KEY_META_DATA_MANAGE_SKU_TOKEN = "com.mapbox.ManageSkuToken";
public static final boolean DEFAULT_MANAGE_SKU_TOKEN = true;
/**
* Default value for font fallback for local ideograph fonts
*/
public static final String DEFAULT_FONT = "sans-serif";
/**
* Unmeasured state
*/
public static final float UNMEASURED = -1f;
/**
* Default animation time
*/
public static final int ANIMATION_DURATION = 300;
/**
* Default short animation time
*/
public static final int ANIMATION_DURATION_SHORT = 150;
/**
* Animation time of a fling gesture
*/
public static final long ANIMATION_DURATION_FLING_BASE = ANIMATION_DURATION_SHORT;
/**
* Velocity threshold for a fling gesture
*/
public static final long VELOCITY_THRESHOLD_IGNORE_FLING = 1000;
/**
* Value by which the default rotation threshold will be increased when scaling
*
* @deprecated unused, see {@link com.mapbox.mapboxsdk.maps.UiSettings#setDisableRotateWhenScaling(boolean)}
*/
@Deprecated
public static final float ROTATION_THRESHOLD_INCREASE_WHEN_SCALING = 25f;
/**
* Maximum absolute zoom change for multi-pointer scale velocity animation
*/
public static final double MAX_ABSOLUTE_SCALE_VELOCITY_CHANGE = 2.5;
/**
* Maximum possible zoom change during the quick zoom gesture executed across the whole screen
*/
public static final double QUICK_ZOOM_MAX_ZOOM_CHANGE = 4.0;
/**
* Scale velocity animation duration multiplier.
*/
public static final double SCALE_VELOCITY_ANIMATION_DURATION_MULTIPLIER = 150;
/**
* Minimum angular velocity for rotation animation
*
* @deprecated unused, see {@link #ROTATE_VELOCITY_RATIO_THRESHOLD}
*/
@Deprecated
public static final float MINIMUM_ANGULAR_VELOCITY = 1.5f;
/**
* Last scale span delta to XY velocity ratio required to execute scale velocity animation.
*/
public static final double SCALE_VELOCITY_RATIO_THRESHOLD = 4 * 1e-3;
/**
* Last rotation delta to XY velocity ratio required to execute rotation velocity animation.
*/
public static final double ROTATE_VELOCITY_RATIO_THRESHOLD = 2.2 * 1e-4;
/**
* Time within which user needs to lift fingers for velocity animation to start.
*/
public static final long SCHEDULED_ANIMATION_TIMEOUT = 150L;
/**
* Maximum angular velocity for rotation animation
*/
public static final float MAXIMUM_ANGULAR_VELOCITY = 30f;
/**
* Factor to calculate tilt change based on pixel change during shove gesture.
*/
public static final float SHOVE_PIXEL_CHANGE_FACTOR = 0.1f;
/**
* The currently supported minimum zoom level.
*/
public static final float MINIMUM_ZOOM = 0.0f;
/**
* The currently supported maximum zoom level.
*/
public static final float MAXIMUM_ZOOM = 25.5f;
/**
* The currently supported maximum tilt value.
*/
public static final double MAXIMUM_TILT = 60;
/**
* The currently supported minimum tilt value.
*/
public static final double MINIMUM_TILT = 0;
/**
* The currently supported maximum direction
*/
public static final double MAXIMUM_DIRECTION = 360;
/**
* The currently supported minimum direction
*/
public static final double MINIMUM_DIRECTION = 0;
/**
* The currently used minimum scale factor to clamp to when a quick zoom gesture occurs
*
* @deprecated unused
*/
@Deprecated
public static final float MINIMUM_SCALE_FACTOR_CLAMP = 0.00f;
/**
* The currently used maximum scale factor to clamp to when a quick zoom gesture occurs
*
* @deprecated unused
*/
@Deprecated
public static final float MAXIMUM_SCALE_FACTOR_CLAMP = 0.15f;
/**
* Zoom value multiplier for scale gestures.
*/
public static final float ZOOM_RATE = 0.65f;
/**
* Fragment Argument Key for MapboxMapOptions
*/
public static final String FRAG_ARG_MAPBOXMAPOPTIONS = "MapboxMapOptions";
/**
* Layer Id of annotations layer
*/
public static final String LAYER_ID_ANNOTATIONS = "com.mapbox.annotations.points";
// Save instance state keys
public static final String STATE_HAS_SAVED_STATE = "mapbox_savedState";
public static final String STATE_CAMERA_POSITION = "mapbox_cameraPosition";
public static final String STATE_ZOOM_ENABLED = "mapbox_zoomEnabled";
public static final String STATE_SCROLL_ENABLED = "mapbox_scrollEnabled";
public static final String STATE_ROTATE_ENABLED = "mapbox_rotateEnabled";
public static final String STATE_TILT_ENABLED = "mapbox_tiltEnabled";
public static final String STATE_DOUBLE_TAP_ENABLED = "mapbox_doubleTapEnabled";
public static final String STATE_QUICK_ZOOM_ENABLED = "mapbox_quickZoom";
public static final String STATE_ZOOM_RATE = "mapbox_zoomRate";
public static final String STATE_DEBUG_ACTIVE = "mapbox_debugActive";
public static final String STATE_COMPASS_ENABLED = "mapbox_compassEnabled";
public static final String STATE_COMPASS_GRAVITY = "mapbox_compassGravity";
public static final String STATE_COMPASS_MARGIN_LEFT = "mapbox_compassMarginLeft";
public static final String STATE_COMPASS_MARGIN_TOP = "mapbox_compassMarginTop";
public static final String STATE_COMPASS_MARGIN_RIGHT = "mapbox_compassMarginRight";
public static final String STATE_COMPASS_MARGIN_BOTTOM = "mapbox_compassMarginBottom";
public static final String STATE_COMPASS_FADE_WHEN_FACING_NORTH = "mapbox_compassFade";
public static final String STATE_COMPASS_IMAGE_BITMAP = "mapbox_compassImage";
public static final String STATE_LOGO_GRAVITY = "mapbox_logoGravity";
public static final String STATE_LOGO_MARGIN_LEFT = "mapbox_logoMarginLeft";
public static final String STATE_LOGO_MARGIN_TOP = "mapbox_logoMarginTop";
public static final String STATE_LOGO_MARGIN_RIGHT = "mapbox_logoMarginRight";
public static final String STATE_LOGO_MARGIN_BOTTOM = "mapbox_logoMarginBottom";
public static final String STATE_LOGO_ENABLED = "mapbox_logoEnabled";
public static final String STATE_ATTRIBUTION_GRAVITY = "mapbox_attrGravity";
public static final String STATE_ATTRIBUTION_MARGIN_LEFT = "mapbox_attrMarginLeft";
public static final String STATE_ATTRIBUTION_MARGIN_TOP = "mapbox_attrMarginTop";
public static final String STATE_ATTRIBUTION_MARGIN_RIGHT = "mapbox_attrMarginRight";
public static final String STATE_ATTRIBUTION_MARGIN_BOTTOM = "mapbox_atrrMarginBottom";
public static final String STATE_ATTRIBUTION_ENABLED = "mapbox_atrrEnabled";
public static final String STATE_DESELECT_MARKER_ON_TAP = "mapbox_deselectMarkerOnTap";
public static final String STATE_USER_FOCAL_POINT = "mapbox_userFocalPoint";
public static final String STATE_SCALE_ANIMATION_ENABLED = "mapbox_scaleAnimationEnabled";
public static final String STATE_ROTATE_ANIMATION_ENABLED = "mapbox_rotateAnimationEnabled";
public static final String STATE_FLING_ANIMATION_ENABLED = "mapbox_flingAnimationEnabled";
public static final String STATE_INCREASE_ROTATE_THRESHOLD = "mapbox_increaseRotateThreshold";
public static final String STATE_DISABLE_ROTATE_WHEN_SCALING = "mapbox_disableRotateWhenScaling";
public static final String STATE_INCREASE_SCALE_THRESHOLD = "mapbox_increaseScaleThreshold";
}

View File

@@ -0,0 +1,14 @@
package com.mapbox.mapboxsdk.constants;
@Deprecated
public class TelemetryConstants {
public static final String TWO_FINGER_TAP = "TwoFingerTap";
public static final String DOUBLE_TAP = "DoubleTap";
public static final String SINGLE_TAP = "SingleTap";
public static final String PAN = "Pan";
public static final String PINCH = "Pinch";
public static final String ROTATION = "Rotation";
public static final String PITCH = "Pitch";
}

View File

@@ -0,0 +1,4 @@
/**
* Contains the Mapbox Maps Android Constant API classes.
*/
package com.mapbox.mapboxsdk.constants;

View File

@@ -0,0 +1,8 @@
package com.mapbox.mapboxsdk.exceptions;
public class CalledFromWorkerThreadException extends RuntimeException {
public CalledFromWorkerThreadException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,11 @@
package com.mapbox.mapboxsdk.exceptions;
/**
* A ConversionException is thrown when a conversion failed to execute.
*/
public class ConversionException extends RuntimeException {
public ConversionException(String detailMessage) {
super(detailMessage);
}
}

View File

@@ -0,0 +1,25 @@
package com.mapbox.mapboxsdk.exceptions;
import com.mapbox.mapboxsdk.annotations.Icon;
import com.mapbox.mapboxsdk.annotations.Marker;
import com.mapbox.mapboxsdk.maps.MapView;
/**
* <p>
* An IconBitmapChangedException is thrown by MapView when a Marker is added
* that has an Icon with a Bitmap that has been modified since the creation of the Icon.
* </p>
* You cannot modify a {@code Icon} after it has been added to the map in a {@code Marker}
*
* @see MapView
* @see Icon
* @see Marker
*/
public class IconBitmapChangedException extends RuntimeException {
public IconBitmapChangedException() {
super("The added Marker has an Icon with a bitmap that has been modified. An Icon cannot be modified"
+ "after it has been added to the map in a Marker.");
}
}

View File

@@ -0,0 +1,12 @@
package com.mapbox.mapboxsdk.exceptions;
/**
* An InvalidLatLngBoundsException is thrown by LatLngBounds
* when there aren't enough LatLng to create a bounds.
*/
public class InvalidLatLngBoundsException extends RuntimeException {
public InvalidLatLngBoundsException(int latLngsListSize) {
super("Cannot create a LatLngBounds from " + latLngsListSize + " items");
}
}

View File

@@ -0,0 +1,16 @@
package com.mapbox.mapboxsdk.exceptions;
/**
* A InvalidMarkerPositionException is thrown when a Marker object is created with an invalid LatLng position.
*/
public class InvalidMarkerPositionException extends RuntimeException {
/**
* Creates a invalid marker position exception thrown when a Marker object is created with an invalid LatLng position.
*/
public InvalidMarkerPositionException() {
super("Adding an invalid Marker to a Map. "
+ "Missing the required position field. "
+ "Provide a non null LatLng as position to the Marker.");
}
}

View File

@@ -0,0 +1,33 @@
package com.mapbox.mapboxsdk.exceptions;
import android.content.Context;
import android.support.annotation.NonNull;
/**
* A MapboxConfigurationException is thrown by MapboxMap when the SDK hasn't been properly initialised.
* <p>
* This occurs either when {@link com.mapbox.mapboxsdk.Mapbox} is not correctly initialised or the provided access token
* through {@link com.mapbox.mapboxsdk.Mapbox#getInstance(Context, String)} isn't valid.
* </p>
*
* @see com.mapbox.mapboxsdk.Mapbox#getInstance(Context, String)
*/
public class MapboxConfigurationException extends RuntimeException {
/**
* Creates a Mapbox configuration exception thrown by MapboxMap when the SDK hasn't been properly initialised.
*/
public MapboxConfigurationException() {
super("\nUsing MapView requires calling Mapbox.getInstance(Context context, String accessToken) before "
+ "inflating or creating the view. The access token parameter is required when using a Mapbox service."
+ "\nPlease see https://www.mapbox.com/help/create-api-access-token/ to learn how to create one."
+ "\nMore information in this guide https://www.mapbox.com/help/first-steps-android-sdk/#access-tokens.");
}
/**
* Creates a Mapbox configuration exception thrown by MapboxMap when the SDK hasn't been properly initialised.
*/
public MapboxConfigurationException(@NonNull String message) {
super(message);
}
}

View File

@@ -0,0 +1,20 @@
package com.mapbox.mapboxsdk.exceptions;
import com.mapbox.mapboxsdk.annotations.IconFactory;
/**
* <p>
* A TooManyIconsException is thrown by IconFactory when it
* cannot create a Icon because there are already too many icons created.
* </p>
* You should try to reuse Icon objects whenever possible.
*
* @see IconFactory
*/
public class TooManyIconsException extends RuntimeException {
public TooManyIconsException() {
super("Cannot create an Icon because there are already too many. Try reusing Icon objects whenever possible.");
}
}

View File

@@ -0,0 +1,4 @@
/**
* Contains the Mapbox Maps Android Exception API classes.
*/
package com.mapbox.mapboxsdk.exceptions;

View File

@@ -0,0 +1,334 @@
package com.mapbox.mapboxsdk.geometry;
import android.location.Location;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.FloatRange;
import android.support.annotation.Keep;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.mapbox.geojson.Point;
import com.mapbox.mapboxsdk.constants.GeometryConstants;
import com.mapbox.turf.TurfMeasurement;
import static com.mapbox.turf.TurfConstants.UNIT_METRES;
/**
* A geographical location which contains a single latitude, longitude pair, with
* optional altitude.
* <p>
* Latitude and longitude are expressed as decimal degrees
* in the WGS84 datum. By default, altitude is 0.0, or sea level.
* </p>
* <p>
* Mapbox GL displays maps in the Mercator Projection and projects geographical
* data automatically, so all data enters in the WGS84 datum.
* </p>
*/
public class LatLng implements Parcelable {
/**
* Inner class responsible for recreating Parcels into objects.
*/
public static final Parcelable.Creator<LatLng> CREATOR = new Parcelable.Creator<LatLng>() {
public LatLng createFromParcel(@NonNull Parcel in) {
return new LatLng(in);
}
public LatLng[] newArray(int size) {
return new LatLng[size];
}
};
@Keep
private double latitude;
@Keep
private double longitude;
private double altitude = 0.0;
/**
* Construct a new latitude, longitude point at (0, 0)
*/
public LatLng() {
this.latitude = 0.0;
this.longitude = 0.0;
}
/**
* Construct a new latitude, longitude point given double arguments
*
* @param latitude Latitude in degrees
* @param longitude Longitude in degrees
*/
@Keep
public LatLng(double latitude, double longitude) {
setLatitude(latitude);
setLongitude(longitude);
}
/**
* Construct a new latitude, longitude, altitude point given double arguments
*
* @param latitude Latitude in degrees
* @param longitude Longitude in degress
* @param altitude Altitude in meters
*/
public LatLng(double latitude, double longitude, double altitude) {
setLatitude(latitude);
setLongitude(longitude);
setAltitude(altitude);
}
/**
* Construct a new latitude, longitude, altitude point given location argument
*
* @param location Android Location
*/
public LatLng(Location location) {
this(location.getLatitude(), location.getLongitude(), location.getAltitude());
}
/**
* Construct a new latitude, longitude, altitude point given another latitude, longitude, altitude point.
*
* @param latLng LatLng to be cloned.
*/
public LatLng(LatLng latLng) {
this.latitude = latLng.latitude;
this.longitude = latLng.longitude;
this.altitude = latLng.altitude;
}
/**
* Constructs a new latitude, longitude, altitude tuple given a parcel.
*
* @param in the parcel containing the latitude, longitude, altitude values
*/
protected LatLng(Parcel in) {
setLatitude(in.readDouble());
setLongitude(in.readDouble());
setAltitude(in.readDouble());
}
/**
* Set the latitude, in degrees.
* <p>
* This value is in the range of [-90, 90], see {@link GeometryConstants#MIN_LATITUDE} and
* {@link GeometryConstants#MAX_LATITUDE}
* </p>
*
* @param latitude the latitude value in degrees
* @see GeometryConstants#MIN_LATITUDE
* @see GeometryConstants#MAX_LATITUDE
*/
public void setLatitude(
@FloatRange(from = GeometryConstants.MIN_LATITUDE, to = GeometryConstants.MAX_LATITUDE) double latitude) {
if (Double.isNaN(latitude)) {
throw new IllegalArgumentException("latitude must not be NaN");
}
if (Math.abs(latitude) > GeometryConstants.MAX_LATITUDE) {
throw new IllegalArgumentException("latitude must be between -90 and 90");
}
this.latitude = latitude;
}
/**
* Get the latitude, in degrees.
* <p>
* This value is in the range of [-90, 90], see {@link GeometryConstants#MIN_LATITUDE} and
* {@link GeometryConstants#MAX_LATITUDE}
* </p>
*
* @return the latitude value in degrees
* @see GeometryConstants#MIN_LATITUDE
* @see GeometryConstants#MAX_LATITUDE
*/
public double getLatitude() {
return latitude;
}
/**
* Set the longitude, in degrees.
* <p>
* This value is in the range of [-180, 180], see {@link GeometryConstants#MIN_LONGITUDE} and
* {@link GeometryConstants#MAX_LONGITUDE}
* </p>
*
* @param longitude the longitude value in degrees
* @see GeometryConstants#MIN_LONGITUDE
* @see GeometryConstants#MAX_LONGITUDE
*/
public void setLongitude(@FloatRange(from = GeometryConstants.MIN_LONGITUDE, to = GeometryConstants.MAX_LONGITUDE)
double longitude) {
if (Double.isNaN(longitude)) {
throw new IllegalArgumentException("longitude must not be NaN");
}
if (Double.isInfinite(longitude)) {
throw new IllegalArgumentException("longitude must not be infinite");
}
this.longitude = longitude;
}
/**
* Get the longitude, in degrees.
* <p>
* This value is in the range of [-180, 180], see {@link GeometryConstants#MIN_LONGITUDE} and
* {@link GeometryConstants#MAX_LONGITUDE}
* </p>
*
* @return the longitude value in degrees
* @see GeometryConstants#MIN_LONGITUDE
* @see GeometryConstants#MAX_LONGITUDE
*/
public double getLongitude() {
return longitude;
}
/**
* Set the altitude, in meters.
*
* @param altitude the altitude in meters
*/
public void setAltitude(double altitude) {
this.altitude = altitude;
}
/**
* Get the altitude, in meters.
*
* @return the altitude value in meters
*/
public double getAltitude() {
return altitude;
}
/**
* Return a new LatLng object with a wrapped Longitude. This allows original data object
* to remain unchanged.
*
* @return new LatLng object with wrapped Longitude
*/
@NonNull
public LatLng wrap() {
return new LatLng(latitude, wrap(longitude,
GeometryConstants.MIN_WRAP_LONGITUDE, GeometryConstants.MAX_WRAP_LONGITUDE)
);
}
/**
* Constrains value to the given range (including min & max) via modular arithmetic.
* <p>
* Same formula as used in Core GL (wrap.hpp)
* std::fmod((std::fmod((value - min), d) + d), d) + min;
* </p>
* <p>
* Multiples of max value will be wrapped to max.
* </p>
*
* @param value Value to wrap
* @param min Minimum value
* @param max Maximum value
* @return Wrapped value
*/
static double wrap(double value, double min, double max) {
double delta = max - min;
double firstMod = (value - min) % delta;
double secondMod = (firstMod + delta) % delta;
if (value >= max && secondMod == 0) {
return max;
}
return secondMod + min;
}
/**
* Indicates whether some other object is "equal to" this one.
*
* @param object The object to compare
* @return True if equal, false if not
*/
@Override
public boolean equals(@Nullable Object object) {
if (this == object) {
return true;
}
if (object == null || getClass() != object.getClass()) {
return false;
}
LatLng latLng = (LatLng) object;
return Double.compare(latLng.altitude, altitude) == 0 && Double.compare(latLng.latitude, latitude) == 0
&& Double.compare(latLng.longitude, longitude) == 0;
}
/**
* Returns a hash code value for the object.
*
* @return the hash code value
*/
@Override
public int hashCode() {
int result;
long temp;
temp = Double.doubleToLongBits(latitude);
result = (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(longitude);
result = 31 * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(altitude);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
/**
* Returns a string representation of the object.
*
* @return the string representation
*/
@NonNull
@Override
public String toString() {
return "LatLng [latitude=" + latitude + ", longitude=" + longitude + ", altitude=" + altitude + "]";
}
/**
* Describe the kinds of special objects contained in this Parcelable instance's marshaled representation.
*
* @return a bitmask indicating the set of special object types marshaled by this Parcelable object instance.
*/
@Override
public int describeContents() {
return 0;
}
/**
* Flatten this object in to a Parcel.
*
* @param out The Parcel in which the object should be written.
* @param flags Additional flags about how the object should be written
*/
@Override
public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeDouble(latitude);
out.writeDouble(longitude);
out.writeDouble(altitude);
}
/**
* Calculate distance between two points
*
* @param other Other LatLng to compare to
* @return distance in meters
*/
public double distanceTo(@NonNull LatLng other) {
return TurfMeasurement.distance(
Point.fromLngLat(longitude, latitude),
Point.fromLngLat(other.getLongitude(), other.getLatitude()),
UNIT_METRES
);
}
}

View File

@@ -0,0 +1,608 @@
package com.mapbox.mapboxsdk.geometry;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.FloatRange;
import android.support.annotation.Keep;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.mapbox.mapboxsdk.constants.GeometryConstants;
import com.mapbox.mapboxsdk.exceptions.InvalidLatLngBoundsException;
import java.util.ArrayList;
import java.util.List;
import static com.mapbox.mapboxsdk.constants.GeometryConstants.MAX_LATITUDE;
import static com.mapbox.mapboxsdk.constants.GeometryConstants.MAX_LONGITUDE;
import static com.mapbox.mapboxsdk.constants.GeometryConstants.MAX_WRAP_LONGITUDE;
import static com.mapbox.mapboxsdk.constants.GeometryConstants.MIN_LATITUDE;
import static com.mapbox.mapboxsdk.constants.GeometryConstants.MIN_LONGITUDE;
import static com.mapbox.mapboxsdk.constants.GeometryConstants.MIN_WRAP_LONGITUDE;
/**
* A geographical area representing a latitude/longitude aligned rectangle.
* <p>
* This class does not wrap values to the world bounds.
* </p>
*/
public class LatLngBounds implements Parcelable {
@Keep
private final double latitudeNorth;
@Keep
private final double latitudeSouth;
@Keep
private final double longitudeEast;
@Keep
private final double longitudeWest;
/**
* Construct a new LatLngBounds based on its corners, given in NESW
* order.
* <p>
* @since 7.0.0 LatLngBounds cannot be wrapped any more, i.e longitudeWest has to be
* less or equal to longitudeEast.
*
* For example, to represent bounds spanning 20 degrees crossing antimeridian with
* the NE point as (10, -170) and the SW point as (-10, 170),
* use (10, -190) and (-10, -170), or (10, -170) and (-10, -150).
*
* @param northLatitude Northern Latitude
* @param eastLongitude Eastern Longitude
* @param southLatitude Southern Latitude
* @param westLongitude Western Longitude
*/
@Keep
LatLngBounds(final double northLatitude, final double eastLongitude,
final double southLatitude, final double westLongitude) {
this.latitudeNorth = northLatitude;
this.longitudeEast = eastLongitude;
this.latitudeSouth = southLatitude;
this.longitudeWest = westLongitude;
}
/**
* Returns the world bounds.
*
* @return the bounds representing the world
*/
public static LatLngBounds world() {
return LatLngBounds.from(
MAX_LATITUDE, MAX_WRAP_LONGITUDE,
MIN_LATITUDE, MIN_WRAP_LONGITUDE);
}
/**
* Calculates the centerpoint of this LatLngBounds by simple interpolation and returns
* it as a point. This is a non-geodesic calculation which is not the geographic center.
*
* @return LatLng center of this LatLngBounds
*/
@NonNull
public LatLng getCenter() {
double latCenter = (this.latitudeNorth + this.latitudeSouth) / 2.0;
double longCenter = (this.longitudeEast + this.longitudeWest) / 2.0;
return new LatLng(latCenter, longCenter);
}
/**
* Get the north latitude value of this bounds.
*
* @return double latitude value for north
*/
public double getLatNorth() {
return this.latitudeNorth;
}
/**
* Get the south latitude value of this bounds.
*
* @return double latitude value for south
*/
public double getLatSouth() {
return this.latitudeSouth;
}
/**
* Get the east longitude value of this bounds.
*
* @return double longitude value for east
*/
public double getLonEast() {
return this.longitudeEast;
}
/**
* Get the west longitude value of this bounds.
*
* @return double longitude value for west
*/
public double getLonWest() {
return this.longitudeWest;
}
/**
* Get the latitude-longitude pair of the south west corner of this bounds.
*
* @return LatLng of the south west corner
*/
@NonNull
public LatLng getSouthWest() {
return new LatLng(latitudeSouth, longitudeWest);
}
/**
* Get the latitude-longitude paur if the north east corner of this bounds.
*
* @return LatLng of the north east corner
*/
@NonNull
public LatLng getNorthEast() {
return new LatLng(latitudeNorth, longitudeEast);
}
/**
* Get the latitude-longitude pair of the south east corner of this bounds.
*
* @return LatLng of the south east corner
*/
@NonNull
public LatLng getSouthEast() {
return new LatLng(latitudeSouth, longitudeEast);
}
/**
* Get the latitude-longitude pair of the north west corner of this bounds.
*
* @return LatLng of the north west corner
*/
@NonNull
public LatLng getNorthWest() {
return new LatLng(latitudeNorth, longitudeWest);
}
/**
* Get the area spanned by this LatLngBounds
*
* @return LatLngSpan area
*/
@NonNull
public LatLngSpan getSpan() {
return new LatLngSpan(getLatitudeSpan(), getLongitudeSpan());
}
/**
* Get the absolute distance, in degrees, between the north and
* south boundaries of this LatLngBounds
*
* @return Span distance
*/
public double getLatitudeSpan() {
return Math.abs(this.latitudeNorth - this.latitudeSouth);
}
/**
* Get the absolute distance, in degrees, between the west and
* east boundaries of this LatLngBounds
*
* @return Span distance
*/
public double getLongitudeSpan() {
return Math.abs(this.longitudeEast - this.longitudeWest);
}
/**
* Validate if LatLngBounds is empty, determined if absolute distance is
*
* @return boolean indicating if span is empty
*/
public boolean isEmptySpan() {
return getLongitudeSpan() == 0.0 || getLatitudeSpan() == 0.0;
}
/**
* Returns a string representaton of the object.
*
* @return the string representation
*/
@NonNull
@Override
public String toString() {
return "N:" + this.latitudeNorth + "; E:" + this.longitudeEast + "; S:" + this.latitudeSouth
+ "; W:" + this.longitudeWest;
}
/**
* Constructs a LatLngBounds that contains all of a list of LatLng
* objects. Empty lists will yield invalid LatLngBounds.
*
* @param latLngs List of LatLng objects
* @return LatLngBounds
*/
static LatLngBounds fromLatLngs(final List<? extends LatLng> latLngs) {
double minLat = MAX_LATITUDE;
double minLon = MAX_LONGITUDE;
double maxLat = MIN_LATITUDE;
double maxLon = MIN_LONGITUDE;
for (final LatLng gp : latLngs) {
final double latitude = gp.getLatitude();
final double longitude = gp.getLongitude();
minLat = Math.min(minLat, latitude);
minLon = Math.min(minLon, longitude);
maxLat = Math.max(maxLat, latitude);
maxLon = Math.max(maxLon, longitude);
}
return new LatLngBounds(maxLat, maxLon, minLat, minLon);
}
/**
* Return an array of LatLng objects resembling this bounds.
*
* @return an array of 2 LatLng objects.
*/
@NonNull
public LatLng[] toLatLngs() {
return new LatLng[] {getNorthEast(), getSouthWest()};
}
/**
* Constructs a LatLngBounds from doubles representing a LatLng pair.
* <p>
* This values of latNorth and latSouth should be in the range of [-90, 90],
* see {@link GeometryConstants#MIN_LATITUDE} and {@link GeometryConstants#MAX_LATITUDE},
* otherwise IllegalArgumentException will be thrown.
* latNorth should be greater or equal latSouth, otherwise IllegalArgumentException will be thrown.
* <p>
* This method doesn't recalculate most east or most west boundaries.
* Note @since 7.0.0 lonEast and lonWest will NOT be wrapped to be in the range of [-180, 180],
* see {@link GeometryConstants#MIN_LONGITUDE} and {@link GeometryConstants#MAX_LONGITUDE}
* lonEast should be greater or equal lonWest, otherwise IllegalArgumentException will be thrown.
* </p>
*/
public static LatLngBounds from(
@FloatRange(from = MIN_LATITUDE, to = MAX_LATITUDE) double latNorth,
double lonEast,
@FloatRange(from = MIN_LATITUDE, to = MAX_LATITUDE) double latSouth,
double lonWest) {
checkParams(latNorth, lonEast, latSouth, lonWest);
return new LatLngBounds(latNorth, lonEast, latSouth, lonWest);
}
private static void checkParams(
@FloatRange(from = MIN_LATITUDE, to = MAX_LATITUDE) double latNorth,
double lonEast,
@FloatRange(from = MIN_LATITUDE, to = MAX_LATITUDE) double latSouth,
double lonWest) {
if (Double.isNaN(latNorth) || Double.isNaN(latSouth)) {
throw new IllegalArgumentException("latitude must not be NaN");
}
if (Double.isNaN(lonEast) || Double.isNaN(lonWest)) {
throw new IllegalArgumentException("longitude must not be NaN");
}
if (Double.isInfinite(lonEast) || Double.isInfinite(lonWest)) {
throw new IllegalArgumentException("longitude must not be infinite");
}
if (latNorth > MAX_LATITUDE || latNorth < MIN_LATITUDE
|| latSouth > MAX_LATITUDE || latSouth < MIN_LATITUDE) {
throw new IllegalArgumentException("latitude must be between -90 and 90");
}
if (latNorth < latSouth) {
throw new IllegalArgumentException("latNorth cannot be less than latSouth");
}
if (lonEast < lonWest) {
throw new IllegalArgumentException("lonEast cannot be less than lonWest");
}
}
private static double lat_(int z, int y) {
double n = Math.PI - 2.0 * Math.PI * y / Math.pow(2.0, z);
return Math.toDegrees(Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))));
}
private static double lon_(int z, int x) {
return x / Math.pow(2.0, z) * 360.0 - GeometryConstants.MAX_WRAP_LONGITUDE;
}
/**
* Constructs a LatLngBounds from a Tile identifier.
* <p>
* Returned bounds will have latitude in the range of Mercator projection.
*
* @param z Tile zoom level.
* @param x Tile X coordinate.
* @param y Tile Y coordinate.
* @see GeometryConstants#MIN_MERCATOR_LATITUDE
* @see GeometryConstants#MAX_MERCATOR_LATITUDE
*/
public static LatLngBounds from(int z, int x, int y) {
return new LatLngBounds(lat_(z, y), lon_(z, x + 1), lat_(z, y + 1), lon_(z, x));
}
/**
* Constructs a LatLngBounds from current bounds with an additional latitude-longitude pair.
*
* @param latLng the latitude lognitude pair to include in the bounds.
* @return the newly constructed bounds
*/
@NonNull
public LatLngBounds include(@NonNull LatLng latLng) {
return new LatLngBounds.Builder()
.include(getNorthEast())
.include(getSouthWest())
.include(latLng)
.build();
}
/**
* Determines whether this LatLngBounds matches another one via LatLng.
*
* @param o another object
* @return a boolean indicating whether the LatLngBounds are equal
*/
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o instanceof LatLngBounds) {
LatLngBounds other = (LatLngBounds) o;
return latitudeNorth == other.getLatNorth()
&& latitudeSouth == other.getLatSouth()
&& longitudeEast == other.getLonEast()
&& longitudeWest == other.getLonWest();
}
return false;
}
private boolean containsLatitude(final double latitude) {
return (latitude <= this.latitudeNorth)
&& (latitude >= this.latitudeSouth);
}
private boolean containsLongitude(final double longitude) {
return (longitude <= this.longitudeEast)
&& (longitude >= this.longitudeWest);
}
/**
* Determines whether this LatLngBounds contains a point.
*
* @param latLng the point which may be contained
* @return true, if the point is contained within the bounds
*/
public boolean contains(@NonNull final LatLng latLng) {
return containsLatitude(latLng.getLatitude())
&& containsLongitude(latLng.getLongitude());
}
/**
* Determines whether this LatLngBounds contains another bounds.
*
* @param other the bounds which may be contained
* @return true, if the bounds is contained within the bounds
*/
public boolean contains(@NonNull final LatLngBounds other) {
return contains(other.getNorthEast())
&& contains(other.getSouthWest());
}
/**
* Returns a new LatLngBounds that stretches to contain both this and another LatLngBounds.
*
* @param bounds LatLngBounds to add
* @return LatLngBounds
*/
@NonNull
public LatLngBounds union(@NonNull LatLngBounds bounds) {
return unionNoParamCheck(bounds.getLatNorth(), bounds.getLonEast(),
bounds.getLatSouth(), bounds.getLonWest());
}
/**
* Returns a new LatLngBounds that stretches to contain both this and another LatLngBounds.
*
* <p>
* This values of northLat and southLat should be in the range of [-90, 90],
* see {@link GeometryConstants#MIN_LATITUDE} and {@link GeometryConstants#MAX_LATITUDE},
* otherwise IllegalArgumentException will be thrown.
* northLat should be greater or equal southLat, otherwise IllegalArgumentException will be thrown.
*
* <p>
* eastLon should be greater or equal westLon, otherwise IllegalArgumentException will be thrown.
*
* @param northLat Northern Latitude corner point
* @param eastLon Eastern Longitude corner point
* @param southLat Southern Latitude corner point
* @param westLon Western Longitude corner point
* @return LatLngBounds
*/
@NonNull
public LatLngBounds union(final double northLat, final double eastLon,
final double southLat, final double westLon) {
checkParams(northLat, eastLon, southLat, westLon);
return unionNoParamCheck(northLat, eastLon, southLat, westLon);
}
private LatLngBounds unionNoParamCheck(final double northLat, final double eastLon,
final double southLat, final double westLon) {
return new LatLngBounds((this.latitudeNorth < northLat) ? northLat : this.latitudeNorth,
(this.longitudeEast < eastLon) ? eastLon : this.longitudeEast,
(this.latitudeSouth > southLat) ? southLat : this.latitudeSouth,
(this.longitudeWest > westLon) ? westLon : this.longitudeWest);
}
/**
* Returns a new LatLngBounds that is the intersection of this with another LatLngBounds,
*
* @param box LatLngBounds to intersect with
* @return LatLngBounds
*/
@Nullable
public LatLngBounds intersect(@NonNull LatLngBounds box) {
return intersectNoParamCheck(box.getLatNorth(), box.getLonEast(), box.getLatSouth(), box.getLonWest());
}
/**
* Returns a new LatLngBounds that is the intersection of this with another box.
*
* <p>
* This values of northLat and southLat should be in the range of [-90, 90],
* see {@link GeometryConstants#MIN_LATITUDE} and {@link GeometryConstants#MAX_LATITUDE},
* otherwise IllegalArgumentException will be thrown.
* northLat should be greater or equal southLat, otherwise IllegalArgumentException will be thrown.
*
* <p>
* eastLon should be greater or equal westLon, otherwise IllegalArgumentException will be thrown.
*
* @param northLat Northern Latitude corner point
* @param eastLon Eastern Longitude corner point
* @param southLat Southern Latitude corner point
* @param westLon Western Longitude corner point
* @return LatLngBounds
*/
@NonNull
public LatLngBounds intersect(final double northLat, final double eastLon,
final double southLat, final double westLon) {
checkParams(northLat, eastLon, southLat, westLon);
return intersectNoParamCheck(northLat, eastLon, southLat, westLon);
}
private LatLngBounds intersectNoParamCheck(final double northLat, final double eastLon,
final double southLat, final double westLon) {
double minLonWest = Math.max(this.longitudeWest, westLon);
double maxLonEast = Math.min(this.longitudeEast, eastLon);
if (maxLonEast >= minLonWest) {
double minLatSouth = Math.max(this.latitudeSouth, southLat);
double maxLatNorth = Math.min(this.latitudeNorth, northLat);
if (maxLatNorth >= minLatSouth) {
return new LatLngBounds(maxLatNorth, maxLonEast, minLatSouth, minLonWest);
}
}
return null;
}
/**
* Inner class responsible for recreating Parcels into objects.
*/
public static final Parcelable.Creator<LatLngBounds> CREATOR =
new Parcelable.Creator<LatLngBounds>() {
@Override
public LatLngBounds createFromParcel(@NonNull final Parcel in) {
return readFromParcel(in);
}
@Override
public LatLngBounds[] newArray(final int size) {
return new LatLngBounds[size];
}
};
/**
* Returns a hash code value for the object.
*
* @return the hash code
*/
@Override
public int hashCode() {
return (int) ((latitudeNorth + 90)
+ ((latitudeSouth + 90) * 1000)
+ ((longitudeEast + 180) * 1000000)
+ ((longitudeWest + 180) * 1000000000));
}
/**
* Describe the kinds of special objects contained in this Parcelable instance's marshaled representation.
*
* @return a bitmask indicating the set of special object types marshaled by this Parcelable object instance.
*/
@Override
public int describeContents() {
return 0;
}
/**
* Flatten this object in to a Parcel.
*
* @param out The Parcel in which the object should be written.
* @param flags Additional flags about how the object should be written
*/
@Override
public void writeToParcel(@NonNull final Parcel out, final int flags) {
out.writeDouble(this.latitudeNorth);
out.writeDouble(this.longitudeEast);
out.writeDouble(this.latitudeSouth);
out.writeDouble(this.longitudeWest);
}
private static LatLngBounds readFromParcel(final Parcel in) {
final double northLat = in.readDouble();
final double eastLon = in.readDouble();
final double southLat = in.readDouble();
final double westLon = in.readDouble();
return new LatLngBounds(northLat, eastLon, southLat, westLon);
}
/**
* Builder for composing LatLngBounds objects.
*/
public static final class Builder {
private final List<LatLng> latLngList = new ArrayList<>();
/**
* Builds a new LatLngBounds.
* <p>
* Throws an {@link InvalidLatLngBoundsException} when no LatLngBounds can be created.
* </p>
*
* @return the build LatLngBounds
*/
public LatLngBounds build() {
if (latLngList.size() < 2) {
throw new InvalidLatLngBoundsException(latLngList.size());
}
return LatLngBounds.fromLatLngs(latLngList);
}
/**
* Adds a LatLng object to the LatLngBounds.Builder.
*
* @param latLngs the List of LatLng objects to be added
* @return this
*/
@NonNull
public Builder includes(@NonNull List<LatLng> latLngs) {
latLngList.addAll(latLngs);
return this;
}
/**
* Adds a LatLng object to the LatLngBounds.Builder.
*
* @param latLng the LatLng to be added
* @return this
*/
@NonNull
public Builder include(@NonNull LatLng latLng) {
latLngList.add(latLng);
return this;
}
}
}

View File

@@ -0,0 +1,94 @@
package com.mapbox.mapboxsdk.geometry;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.Keep;
import android.support.annotation.NonNull;
/**
* A geographical area representing a non-aligned quadrilateral
* <p>
* This class does not wrap values to the world bounds
* </p>
*/
public class LatLngQuad implements Parcelable {
@Keep
private final LatLng topLeft;
@Keep
private final LatLng topRight;
@Keep
private final LatLng bottomRight;
@Keep
private final LatLng bottomLeft;
/**
* Construct a new LatLngQuad based on its corners,
* in order top left, top right, bottom left, bottom right
*/
@Keep
public LatLngQuad(final LatLng topLeft, final LatLng topRight, final LatLng bottomRight, final LatLng bottomLeft) {
this.topLeft = topLeft;
this.topRight = topRight;
this.bottomRight = bottomRight;
this.bottomLeft = bottomLeft;
}
public LatLng getTopLeft() {
return this.topLeft;
}
public LatLng getTopRight() {
return this.topRight;
}
public LatLng getBottomRight() {
return this.bottomRight;
}
public LatLng getBottomLeft() {
return this.bottomLeft;
}
public static final Parcelable.Creator<LatLngQuad> CREATOR = new Parcelable.Creator<LatLngQuad>() {
@Override
public LatLngQuad createFromParcel(@NonNull final Parcel in) {
return readFromParcel(in);
}
@Override
public LatLngQuad[] newArray(final int size) {
return new LatLngQuad[size];
}
};
@Override
public int hashCode() {
int code = topLeft.hashCode();
code = (code ^ code >>> 31) + topRight.hashCode();
code = (code ^ code >>> 31) + bottomRight.hashCode();
code = (code ^ code >>> 31) + bottomLeft.hashCode();
return code;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull final Parcel out, final int arg1) {
topLeft.writeToParcel(out, arg1);
topRight.writeToParcel(out, arg1);
bottomRight.writeToParcel(out, arg1);
bottomLeft.writeToParcel(out, arg1);
}
private static LatLngQuad readFromParcel(@NonNull final Parcel in) {
final LatLng topLeft = new LatLng(in);
final LatLng topRight = new LatLng(in);
final LatLng bottomRight = new LatLng(in);
final LatLng bottomLeft = new LatLng(in);
return new LatLngQuad(topLeft, topRight, bottomRight, bottomLeft);
}
}

View File

@@ -0,0 +1,139 @@
package com.mapbox.mapboxsdk.geometry;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
/**
* A geographical span defined by its latitude and longitude span.
*/
public class LatLngSpan implements Parcelable {
private double mLatitudeSpan;
private double mLongitudeSpan;
private LatLngSpan(@NonNull Parcel in) {
mLatitudeSpan = in.readDouble();
mLongitudeSpan = in.readDouble();
}
/**
* Creates a LatLgnSpan.
*
* @param latitudeSpan The span used for latitude.
* @param longitudeSpan The span used for longitude.
*/
public LatLngSpan(double latitudeSpan, double longitudeSpan) {
mLatitudeSpan = latitudeSpan;
mLongitudeSpan = longitudeSpan;
}
/**
* Returns the latitude span.
*
* @return The latitude span.
*/
public double getLatitudeSpan() {
return mLatitudeSpan;
}
/**
* Sets the latitude span.
*
* @param latitudeSpan The latitude span to set.
*/
public void setLatitudeSpan(double latitudeSpan) {
mLatitudeSpan = latitudeSpan;
}
/**
* Returns to longitude span.
*
* @return The longitude span.
*/
public double getLongitudeSpan() {
return mLongitudeSpan;
}
/**
* Sets the longitude span.
*
* @param longitudeSpan The longitude span to set.
*/
public void setLongitudeSpan(double longitudeSpan) {
mLongitudeSpan = longitudeSpan;
}
/**
* Indicates whether some other object is "equal to" this one.
*
* @param object The object to compare
* @return True if equal, false if not
*/
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object instanceof LatLngSpan) {
LatLngSpan other = (LatLngSpan) object;
return mLongitudeSpan == other.getLongitudeSpan()
&& mLatitudeSpan == other.getLatitudeSpan();
}
return false;
}
/**
* Inner class responsible for recreating Parcels into objects.
*/
public static final Parcelable.Creator<LatLngSpan> CREATOR =
new Parcelable.Creator<LatLngSpan>() {
@Override
public LatLngSpan createFromParcel(@NonNull Parcel in) {
return new LatLngSpan(in);
}
@Override
public LatLngSpan[] newArray(int size) {
return new LatLngSpan[size];
}
};
/**
* Describe the kinds of special objects contained in this Parcelable instance's marshaled representation.
*
* @return a bitmask indicating the set of special object types marshaled by this Parcelable object instance.
*/
@Override
public int describeContents() {
return 0;
}
/**
* Flatten this object in to a Parcel.
*
* @param out Parcel in which the object should be written
* @param flags Additional flags about how the object should be written
*/
@Override
public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeDouble(mLatitudeSpan);
out.writeDouble(mLongitudeSpan);
}
/**
* Returns a hash code value for the object.
*
* @return hash code value of this
*/
@Override
public int hashCode() {
int result;
long temp;
temp = Double.doubleToLongBits(mLatitudeSpan);
result = (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(mLongitudeSpan);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
}

View File

@@ -0,0 +1,155 @@
package com.mapbox.mapboxsdk.geometry;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.Keep;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
/**
* ProjectedMeters is a projection of longitude, latitude points in Mercator meters.
* <p>
* these have been projected into Mapbox GL's Mercator projection. Instead of decimal
* degrees, it uses Mercator meters (which are notably not equivalent to SI meters)
* except at the equator.
* </p>
*/
public class ProjectedMeters implements Parcelable {
/**
* Inner class responsible for recreating Parcels into objects.
*/
public static final Creator<ProjectedMeters> CREATOR = new Creator<ProjectedMeters>() {
public ProjectedMeters createFromParcel(@NonNull Parcel in) {
return new ProjectedMeters(in);
}
public ProjectedMeters[] newArray(int size) {
return new ProjectedMeters[size];
}
};
private double northing;
private double easting;
/**
* Creates a ProjectedMeters based on projected meters in north and east direction.
*
* @param northing the northing in meters
* @param easting the easting in meters
*/
@Keep
public ProjectedMeters(double northing, double easting) {
this.northing = northing;
this.easting = easting;
}
/**
* Creates a ProjecteMeters based on another set of projected meters.
*
* @param projectedMeters The projected meters to be based on.
*/
public ProjectedMeters(ProjectedMeters projectedMeters) {
this.northing = projectedMeters.northing;
this.easting = projectedMeters.easting;
}
/**
* Creates a ProjectedMeters from a Parcel.
*
* @param in The parcel to create from
* @return a bitmask indicating the set of special object types marshaled by this Parcelable object instance.
*/
private ProjectedMeters(Parcel in) {
northing = in.readDouble();
easting = in.readDouble();
}
/**
* Get projected meters in north direction.
*
* @return Projected meters in north.
*/
public double getNorthing() {
return northing;
}
/**
* Get projected meters in east direction.
*
* @return Projected meters in east.
*/
public double getEasting() {
return easting;
}
/**
* Indicates whether some other object is "equal to" this one.
*
* @param other The object to compare this to
* @return true if equal, false if not
*/
@Override
public boolean equals(@Nullable Object other) {
if (this == other) {
return true;
}
if (other == null || getClass() != other.getClass()) {
return false;
}
ProjectedMeters projectedMeters = (ProjectedMeters) other;
return Double.compare(projectedMeters.easting, easting) == 0
&& Double.compare(projectedMeters.northing, northing) == 0;
}
/**
* Returns a hash code value for the object.
*
* @return the hash code of this
*/
@Override
public int hashCode() {
int result;
long temp;
temp = Double.doubleToLongBits(easting);
result = (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(northing);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
/**
* Returns a string representation of the object.
*
* @return the string representation of this
*/
@NonNull
@Override
public String toString() {
return "ProjectedMeters [northing=" + northing + ", easting=" + easting + "]";
}
/**
* Describe the kinds of special objects contained in this Parcelable instance's marshaled representation.
*
* @return a bitmask indicating the set of special object types marshaled by this Parcelable object instance.
*/
@Override
public int describeContents() {
return 0;
}
/**
* Flatten this object in to a Parcel.
*
* @param out The Parcel in which the object should be written.
* @param flags Additional flags about how the object should be written
*/
@Override
public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeDouble(northing);
out.writeDouble(easting);
}
}

View File

@@ -0,0 +1,161 @@
package com.mapbox.mapboxsdk.geometry;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
/**
* Contains the four points defining the four-sided polygon that is visible in a map's camera.
* This polygon can be a trapezoid instead of a rectangle, because a camera can have tilt.
* If the camera is directly over the center of the camera, the shape is rectangular,
* but if the camera is tilted, the shape will appear to be a trapezoid whose
* smallest side is closest to the point of view.
*/
public class VisibleRegion implements Parcelable {
/**
* LatLng object that defines the far left corner of the camera.
*/
public final LatLng farLeft;
/**
* LatLng object that defines the far right corner of the camera.
*/
public final LatLng farRight;
/**
* LatLng object that defines the bottom left corner of the camera.
*/
public final LatLng nearLeft;
/**
* LatLng object that defines the bottom right corner of the camera.
*/
public final LatLng nearRight;
/**
* The smallest bounding box that includes the visible region defined in this class.
*/
public final LatLngBounds latLngBounds;
/**
* Creates a VisibleRegion from a Parcel.
*
* @param in The Parcel to create this from
*/
private VisibleRegion(Parcel in) {
this.farLeft = in.readParcelable(LatLng.class.getClassLoader());
this.farRight = in.readParcelable(LatLng.class.getClassLoader());
this.nearLeft = in.readParcelable(LatLng.class.getClassLoader());
this.nearRight = in.readParcelable(LatLng.class.getClassLoader());
this.latLngBounds = in.readParcelable(LatLngBounds.class.getClassLoader());
}
/**
* Creates a VisibleRegion given the four corners of the camera.
*
* @param farLeft A LatLng object containing the latitude and longitude of the near left corner of the region.
* @param farRight A LatLng object containing the latitude and longitude of the near left corner of the region.
* @param nearLeft A LatLng object containing the latitude and longitude of the near left corner of the region.
* @param nearRight A LatLng object containing the latitude and longitude of the near left corner of the region.
* @param latLngBounds The smallest bounding box that includes the visible region defined in this class.
*/
public VisibleRegion(LatLng farLeft, LatLng farRight, LatLng nearLeft, LatLng nearRight, LatLngBounds latLngBounds) {
this.farLeft = farLeft;
this.farRight = farRight;
this.nearLeft = nearLeft;
this.nearRight = nearRight;
this.latLngBounds = latLngBounds;
}
/**
* Compares this VisibleRegion to another object.
* If the other object is actually a pointer to this object,
* or if all four corners and the bounds of the two objects are the same,
* this method returns true. Otherwise, this method returns false.
*
* @param o The Object to compare with.
* @return true if both objects are the same object.
*/
@Override
public boolean equals(Object o) {
if (!(o instanceof VisibleRegion)) {
return false;
}
if (o == this) {
return true;
}
VisibleRegion visibleRegion = (VisibleRegion) o;
return farLeft.equals(visibleRegion.farLeft)
&& farRight.equals(visibleRegion.farRight)
&& nearLeft.equals(visibleRegion.nearLeft)
&& nearRight.equals(visibleRegion.nearRight)
&& latLngBounds.equals(visibleRegion.latLngBounds);
}
/**
* The string representation of the object.
*
* @return the string representation of this
*/
@NonNull
@Override
public String toString() {
return "[farLeft [" + farLeft + "], farRight [" + farRight + "], nearLeft [" + nearLeft + "], nearRight ["
+ nearRight + "], latLngBounds [" + latLngBounds + "]]";
}
/**
* Returns a hash code value for the object.
*
* @return the hash code
*/
@Override
public int hashCode() {
return ((farLeft.hashCode() + 90)
+ ((farRight.hashCode() + 90) * 1000)
+ ((nearLeft.hashCode() + 180) * 1000000)
+ ((nearRight.hashCode() + 180) * 1000000000));
}
/**
* Describe the kinds of special objects contained in this Parcelable instance's marshaled representation.
*
* @return a bitmask indicating the set of special object types marshaled by this Parcelable object instance.
*/
@Override
public int describeContents() {
return 0;
}
/**
* Flatten this object in to a Parcel.
*
* @param out The Parcel in which the object should be written.
* @param flags Additional flags about how the object should be written
*/
@Override
public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeParcelable(farLeft, flags);
out.writeParcelable(farRight, flags);
out.writeParcelable(nearLeft, flags);
out.writeParcelable(nearRight, flags);
out.writeParcelable(latLngBounds, flags);
}
/**
* Inner class responsible for recreating Parcels into objects.
*/
public static final Parcelable.Creator<VisibleRegion> CREATOR =
new Parcelable.Creator<VisibleRegion>() {
public VisibleRegion createFromParcel(@NonNull Parcel in) {
return new VisibleRegion(in);
}
public VisibleRegion[] newArray(int size) {
return new VisibleRegion[size];
}
};
}

View File

@@ -0,0 +1,4 @@
/**
* Contains the Mapbox Maps Android Geometry API classes.
*/
package com.mapbox.mapboxsdk.geometry;

View File

@@ -0,0 +1,39 @@
package com.mapbox.mapboxsdk.http;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.support.annotation.NonNull;
import com.mapbox.mapboxsdk.MapStrictMode;
import com.mapbox.mapboxsdk.Mapbox;
public class HttpIdentifier {
private HttpIdentifier() {
}
/**
* Returns the application identifier, consisting out the package name, version name and version code.
*
* @return the application identifier
*/
public static String getIdentifier() {
return getIdentifier(Mapbox.getApplicationContext());
}
/**
* Returns the application identifier, consisting out the package name, version name and version code.
*
* @param context the context used to retrieve the package manager from
* @return the application identifier
*/
private static String getIdentifier(@NonNull Context context) {
try {
PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
return String.format("%s/%s (%s)", context.getPackageName(), packageInfo.versionName, packageInfo.versionCode);
} catch (Exception exception) {
MapStrictMode.strictModeViolation(exception);
return "";
}
}
}

View File

@@ -0,0 +1,35 @@
package com.mapbox.mapboxsdk.http;
import android.util.Log;
import com.mapbox.mapboxsdk.log.Logger;
import static com.mapbox.mapboxsdk.http.HttpRequest.CONNECTION_ERROR;
import static com.mapbox.mapboxsdk.http.HttpRequest.TEMPORARY_ERROR;
public class HttpLogger {
private static final String TAG = "Mbgl-HttpRequest";
public static boolean logRequestUrl;
public static boolean logEnabled = true;
private HttpLogger(){
}
public static void logFailure(int type, String errorMessage, String requestUrl) {
log(type == TEMPORARY_ERROR ? Log.DEBUG : type == CONNECTION_ERROR ? Log.INFO : Log.WARN,
String.format(
"Request failed due to a %s error: %s %s",
type == TEMPORARY_ERROR ? "temporary" : type == CONNECTION_ERROR ? "connection" : "permanent",
errorMessage,
logRequestUrl ? requestUrl : ""
)
);
}
public static void log(int type, String errorMessage) {
if (logEnabled) {
Logger.log(type, TAG, errorMessage);
}
}
}

View File

@@ -0,0 +1,32 @@
package com.mapbox.mapboxsdk.http;
/**
* Interface definition for performing http requests.
* <p>
* This allows to provide alternative implementations for the http interaction of this library.
* </p>
*/
public interface HttpRequest {
int CONNECTION_ERROR = 0;
int TEMPORARY_ERROR = 1;
int PERMANENT_ERROR = 2;
/**
* Executes the request.
*
* @param httpRequest callback to be invoked when we receive a response
* @param nativePtr the pointer associated to the request
* @param resourceUrl the resource url to download
* @param etag http header, identifier for a specific version of a resource
* @param modified http header, used to determine if a resource hasn't been modified since
* @param offlineUsage flag to indicate a resource will be used for offline, appends offline=true as a query parameter
*/
void executeRequest(HttpResponder httpRequest, long nativePtr, String resourceUrl,
String etag, String modified, boolean offlineUsage);
/**
* Cancels the request.
*/
void cancelRequest();
}

View File

@@ -0,0 +1,53 @@
package com.mapbox.mapboxsdk.http;
import android.support.annotation.NonNull;
import com.mapbox.mapboxsdk.Mapbox;
public class HttpRequestUrl {
private HttpRequestUrl() {
}
/**
* Adapts a resource request url based on the host, query size, and offline requirement.
* Mapbox resources downloaded for offline use are subject to separate Vector Tile and
* Raster Tile API pricing and are not included in the Maps SDK’s “unlimited” requests.
* See <a href="https://www.mapbox.com/pricing">our pricing page</a> for more information.
*
* @param host the host used as endpoint
* @param resourceUrl the resource to download
* @param querySize the query size of the resource request
* @param offline the type of resource, either offline or online
* @return the adapted resource url
*/
public static String buildResourceUrl(@NonNull String host, String resourceUrl, int querySize, boolean offline) {
if (isValidMapboxEndpoint(host)) {
if (querySize == 0) {
resourceUrl = resourceUrl + "?";
} else {
resourceUrl = resourceUrl + "&";
}
// Only add SKU token to requests not tagged as "offline" usage.
if (offline) {
resourceUrl = resourceUrl + "offline=true";
} else {
resourceUrl = resourceUrl + "sku=" + Mapbox.getSkuToken();
}
}
return resourceUrl;
}
/**
* Validates if the host used as endpoint is a valid Mapbox endpoint.
*
* @param host the host used as endpoint
* @return true if a valid Mapbox endpoint
*/
private static boolean isValidMapboxEndpoint(String host) {
return host.equals("mapbox.com")
|| host.endsWith(".mapbox.com")
|| host.equals("mapbox.cn")
|| host.endsWith(".mapbox.cn");
}
}

View File

@@ -0,0 +1,32 @@
package com.mapbox.mapboxsdk.http;
/**
* Interface definition for a callback to be invoked when either a response was returned for a requested resource or
* when an error occurred when requesting the resource.
*/
public interface HttpResponder {
/**
* Invoked when a resource has finished.
*
* @param responseCode http response code
* @param eTag http header, identifier for a specific version of a resource
* @param lastModified http header, used to determine if a resource hasn't been modified since
* @param cacheControl http header, used to determine cache strategy of a resource
* @param expires http header, used to determine when a resource is stale
* @param retryAfter http header, used to indicate when the service is expected to be unavailable to the client
* @param xRateLimitReset http header, used to determine the remaining window before the rate limit resets
* @param body http response body, in an array of bytes representation
*/
void onResponse(int responseCode, String eTag, String lastModified, String cacheControl, String expires,
String retryAfter, String xRateLimitReset, byte[] body);
/**
* Invoked when a resource failed to be retrieved.
*
* @param type the error type, either one of {@link HttpRequest#CONNECTION_ERROR},
* {@link HttpRequest#TEMPORARY_ERROR} or {@link HttpRequest#PERMANENT_ERROR}
* @param errorMessage the error message associated with the failure
*/
void handleFailure(int type, String errorMessage);
}

View File

@@ -0,0 +1,75 @@
package com.mapbox.mapboxsdk.http;
import android.content.res.AssetManager;
import android.os.AsyncTask;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.mapbox.mapboxsdk.MapStrictMode;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.log.Logger;
import java.io.IOException;
import java.io.InputStream;
class LocalRequestTask extends AsyncTask<String, Void, byte[]> {
private static final String TAG = "Mbgl-LocalRequestTask";
private OnLocalRequestResponse requestResponse;
LocalRequestTask(OnLocalRequestResponse requestResponse) {
this.requestResponse = requestResponse;
}
@Nullable
@Override
protected byte[] doInBackground(String... strings) {
return loadFile(Mapbox.getApplicationContext().getAssets(),
"integration/" + strings[0]
.substring(8)
.replaceAll("%20", " ")
.replaceAll("%2c", ","));
}
@Override
protected void onPostExecute(@Nullable byte[] bytes) {
super.onPostExecute(bytes);
if (bytes != null && requestResponse != null) {
requestResponse.onResponse(bytes);
}
}
@Nullable
private static byte[] loadFile(AssetManager assets, @NonNull String path) {
byte[] buffer = null;
InputStream input = null;
try {
input = assets.open(path);
int size = input.available();
buffer = new byte[size];
input.read(buffer);
} catch (IOException exception) {
logFileError(exception);
} finally {
if (input != null) {
try {
input.close();
} catch (IOException exception) {
logFileError(exception);
}
}
}
return buffer;
}
private static void logFileError(Exception exception) {
String message = "Load file failed";
Logger.e(TAG, message, exception);
MapStrictMode.strictModeViolation(message, exception);
}
public interface OnLocalRequestResponse {
void onResponse(byte[] bytes);
}
}

View File

@@ -0,0 +1,90 @@
package com.mapbox.mapboxsdk.http;
import android.support.annotation.Keep;
import android.support.annotation.Nullable;
import com.mapbox.mapboxsdk.Mapbox;
import java.util.concurrent.locks.ReentrantLock;
@Keep
public class NativeHttpRequest implements HttpResponder {
private final HttpRequest httpRequest = Mapbox.getModuleProvider().createHttpRequest();
// Reentrancy is not needed, but "Lock" is an abstract class.
private final ReentrantLock lock = new ReentrantLock();
@Keep
private long nativePtr;
@Keep
private NativeHttpRequest(long nativePtr, String resourceUrl, String etag, String modified, boolean offlineUsage) {
this.nativePtr = nativePtr;
if (resourceUrl.startsWith("local://")) {
// used by render test to serve files from assets
executeLocalRequest(resourceUrl);
return;
}
httpRequest.executeRequest(this, nativePtr, resourceUrl, etag, modified, offlineUsage);
}
public void cancel() {
httpRequest.cancelRequest();
// TODO: We need a lock here because we can try
// to cancel at the same time the request is getting
// answered on the OkHTTP thread. We could get rid of
// this lock by using Runnable when we move Android
// implementation of mbgl::RunLoop to Looper.
lock.lock();
nativePtr = 0;
lock.unlock();
}
public void onResponse(int responseCode, String etag, String lastModified, String cacheControl, String expires,
String retryAfter, String xRateLimitReset, byte[] body) {
lock.lock();
if (nativePtr != 0) {
nativeOnResponse(responseCode,
etag,
lastModified,
cacheControl,
expires,
retryAfter,
xRateLimitReset,
body);
}
lock.unlock();
}
private void executeLocalRequest(String resourceUrl) {
new LocalRequestTask(new LocalRequestTask.OnLocalRequestResponse() {
@Override
public void onResponse(@Nullable byte[] bytes) {
if (bytes != null) {
lock.lock();
if (nativePtr != 0) {
NativeHttpRequest.this.nativeOnResponse(200, null, null, null, null, null, null, bytes);
}
lock.unlock();
}
}
}).execute(resourceUrl);
}
public void handleFailure(int type, String errorMessage) {
lock.lock();
if (nativePtr != 0) {
nativeOnFailure(type, errorMessage);
}
lock.unlock();
}
@Keep
private native void nativeOnFailure(int type, String message);
@Keep
private native void nativeOnResponse(int code, String etag, String modified, String cacheControl, String expires,
String retryAfter, String xRateLimitReset, byte[] body);
}

View File

@@ -0,0 +1,4 @@
/**
* Do not use this package. Internal use only.
*/
package com.mapbox.mapboxsdk.http;

View File

@@ -0,0 +1,45 @@
package com.mapbox.mapboxsdk.location;
class AnimatorListenerHolder {
@MapboxAnimator.Type
private final int animatorType;
private final MapboxAnimator.AnimationsValueChangeListener listener;
AnimatorListenerHolder(@MapboxAnimator.Type int animatorType, MapboxAnimator.AnimationsValueChangeListener listener) {
this.animatorType = animatorType;
this.listener = listener;
}
@MapboxAnimator.Type
public int getAnimatorType() {
return animatorType;
}
public MapboxAnimator.AnimationsValueChangeListener getListener() {
return listener;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
AnimatorListenerHolder that = (AnimatorListenerHolder) o;
if (animatorType != that.animatorType) {
return false;
}
return listener != null ? listener.equals(that.listener) : that.listener == null;
}
@Override
public int hashCode() {
int result = animatorType;
result = 31 * result + (listener != null ? listener.hashCode() : 0);
return result;
}
}

View File

@@ -0,0 +1,55 @@
package com.mapbox.mapboxsdk.location;
import android.support.annotation.NonNull;
import com.mapbox.mapboxsdk.location.modes.CameraMode;
import com.mapbox.mapboxsdk.location.modes.RenderMode;
/**
* Interface defining the source of compass heading data that is
* consumed by the {@link LocationComponent} when in compass related
* {@link RenderMode} or
* {@link CameraMode}s.
*/
public interface CompassEngine {
/**
* Adds a {@link CompassListener} that can be used to
* receive heading and state changes.
*
* @param compassListener to be added
*/
void addCompassListener(@NonNull CompassListener compassListener);
/**
* Removes a {@link CompassListener} that can be used to
* receive heading and state changes.
*
* @param compassListener to be removed
*/
void removeCompassListener(@NonNull CompassListener compassListener);
/**
* Returns the last heading value produced and pushed via
* a compass listener.
*
* @return last heading value
*/
float getLastHeading();
/**
* Provides the last know accuracy status from the sensor manager.
* <p>
* An integer value which is identical to the {@code SensorManager} class constants:
* <ul>
* <li>{@link android.hardware.SensorManager#SENSOR_STATUS_NO_CONTACT}</li>
* <li>{@link android.hardware.SensorManager#SENSOR_STATUS_UNRELIABLE}</li>
* <li>{@link android.hardware.SensorManager#SENSOR_STATUS_ACCURACY_LOW}</li>
* <li>{@link android.hardware.SensorManager#SENSOR_STATUS_ACCURACY_MEDIUM}</li>
* <li>{@link android.hardware.SensorManager#SENSOR_STATUS_ACCURACY_HIGH}</li>
* </ul>
*
* @return last accuracy status
*/
int getLastAccuracySensorStatus();
}

View File

@@ -0,0 +1,33 @@
package com.mapbox.mapboxsdk.location;
/**
* Callbacks related to the compass
*/
public interface CompassListener {
/**
* Callback's invoked when a new compass update occurs. You can listen into the compass updates
* using {@link CompassEngine#addCompassListener(CompassListener)} and implementing these
* callbacks. Note that this interface is also used internally to to update the UI chevron/arrow.
*
* @param userHeading the new compass heading
*/
void onCompassChanged(float userHeading);
/**
* This gets invoked when the compass accuracy status changes from one value to another. It
* provides an integer value which is identical to the {@code SensorManager} class constants:
* <ul>
* <li>{@link android.hardware.SensorManager#SENSOR_STATUS_NO_CONTACT}</li>
* <li>{@link android.hardware.SensorManager#SENSOR_STATUS_UNRELIABLE}</li>
* <li>{@link android.hardware.SensorManager#SENSOR_STATUS_ACCURACY_LOW}</li>
* <li>{@link android.hardware.SensorManager#SENSOR_STATUS_ACCURACY_MEDIUM}</li>
* <li>{@link android.hardware.SensorManager#SENSOR_STATUS_ACCURACY_HIGH}</li>
* </ul>
*
* @param compassStatus the new accuracy of this sensor, one of
* {@code SensorManager.SENSOR_STATUS_*}
*/
void onCompassAccuracyChange(int compassStatus);
}

View File

@@ -0,0 +1,21 @@
package com.mapbox.mapboxsdk.location;
import android.animation.TypeEvaluator;
import android.support.annotation.NonNull;
import com.mapbox.mapboxsdk.geometry.LatLng;
class LatLngEvaluator implements TypeEvaluator<LatLng> {
private final LatLng latLng = new LatLng();
@NonNull
@Override
public LatLng evaluate(float fraction, @NonNull LatLng startValue, @NonNull LatLng endValue) {
latLng.setLatitude(startValue.getLatitude()
+ ((endValue.getLatitude() - startValue.getLatitude()) * fraction));
latLng.setLongitude(startValue.getLongitude()
+ ((endValue.getLongitude() - startValue.getLongitude()) * fraction));
return latLng;
}
}

View File

@@ -0,0 +1,32 @@
package com.mapbox.mapboxsdk.location;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.support.annotation.ColorInt;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import com.mapbox.mapboxsdk.R;
import com.mapbox.mapboxsdk.utils.BitmapUtils;
import static com.mapbox.mapboxsdk.location.Utils.generateShadow;
class LayerBitmapProvider {
private final Context context;
LayerBitmapProvider(Context context) {
this.context = context;
}
Bitmap generateBitmap(@DrawableRes int drawableRes, @ColorInt Integer tintColor) {
Drawable drawable = BitmapUtils.getDrawableFromRes(context, drawableRes, tintColor);
return BitmapUtils.getBitmapFromDrawable(drawable);
}
Bitmap generateShadowBitmap(@NonNull LocationComponentOptions options) {
Drawable shadowDrawable = BitmapUtils.getDrawableFromRes(context, R.drawable.mapbox_user_icon_shadow);
return generateShadow(shadowDrawable, options.elevation());
}
}

View File

@@ -0,0 +1,26 @@
package com.mapbox.mapboxsdk.location;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.mapbox.geojson.Feature;
import com.mapbox.geojson.Point;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_COMPASS_BEARING;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_GPS_BEARING;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_LOCATION_STALE;
class LayerFeatureProvider {
@NonNull
Feature generateLocationFeature(@Nullable Feature locationFeature, @NonNull LocationComponentOptions options) {
if (locationFeature != null) {
return locationFeature;
}
locationFeature = Feature.fromGeometry(Point.fromLngLat(0.0, 0.0));
locationFeature.addNumberProperty(PROPERTY_GPS_BEARING, 0f);
locationFeature.addNumberProperty(PROPERTY_COMPASS_BEARING, 0f);
locationFeature.addBooleanProperty(PROPERTY_LOCATION_STALE, options.enableStaleState());
return locationFeature;
}
}

View File

@@ -0,0 +1,111 @@
package com.mapbox.mapboxsdk.location;
import android.support.annotation.NonNull;
import com.mapbox.geojson.Feature;
import com.mapbox.mapboxsdk.style.layers.CircleLayer;
import com.mapbox.mapboxsdk.style.layers.Layer;
import com.mapbox.mapboxsdk.style.layers.Property;
import com.mapbox.mapboxsdk.style.layers.SymbolLayer;
import com.mapbox.mapboxsdk.style.sources.GeoJsonOptions;
import com.mapbox.mapboxsdk.style.sources.GeoJsonSource;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.ACCURACY_LAYER;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.BACKGROUND_LAYER;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.BEARING_LAYER;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.FOREGROUND_LAYER;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.LOCATION_SOURCE;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_ACCURACY_ALPHA;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_ACCURACY_COLOR;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_ACCURACY_RADIUS;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_BACKGROUND_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_BACKGROUND_STALE_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_BEARING_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_COMPASS_BEARING;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_FOREGROUND_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_FOREGROUND_ICON_OFFSET;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_FOREGROUND_STALE_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_GPS_BEARING;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_LOCATION_STALE;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_SHADOW_ICON_OFFSET;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.SHADOW_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.SHADOW_LAYER;
import static com.mapbox.mapboxsdk.style.expressions.Expression.get;
import static com.mapbox.mapboxsdk.style.expressions.Expression.literal;
import static com.mapbox.mapboxsdk.style.expressions.Expression.match;
import static com.mapbox.mapboxsdk.style.expressions.Expression.stop;
import static com.mapbox.mapboxsdk.style.expressions.Expression.switchCase;
import static com.mapbox.mapboxsdk.style.layers.Property.ICON_ROTATION_ALIGNMENT_MAP;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleColor;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleOpacity;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circlePitchAlignment;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleRadius;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleStrokeColor;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconAllowOverlap;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconIgnorePlacement;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconImage;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconOffset;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconRotate;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconRotationAlignment;
class LayerSourceProvider {
private static final String EMPTY_STRING = "";
@NonNull
GeoJsonSource generateSource(Feature locationFeature) {
return new GeoJsonSource(
LOCATION_SOURCE,
locationFeature,
new GeoJsonOptions().withMaxZoom(16)
);
}
@NonNull
Layer generateLayer(@NonNull String layerId) {
SymbolLayer layer = new SymbolLayer(layerId, LOCATION_SOURCE);
layer.setProperties(
iconAllowOverlap(true),
iconIgnorePlacement(true),
iconRotationAlignment(ICON_ROTATION_ALIGNMENT_MAP),
iconRotate(
match(literal(layerId), literal(0f),
stop(FOREGROUND_LAYER, get(PROPERTY_GPS_BEARING)),
stop(BACKGROUND_LAYER, get(PROPERTY_GPS_BEARING)),
stop(SHADOW_LAYER, get(PROPERTY_GPS_BEARING)),
stop(BEARING_LAYER, get(PROPERTY_COMPASS_BEARING))
)
),
iconImage(
match(literal(layerId), literal(EMPTY_STRING),
stop(FOREGROUND_LAYER, switchCase(
get(PROPERTY_LOCATION_STALE), get(PROPERTY_FOREGROUND_STALE_ICON),
get(PROPERTY_FOREGROUND_ICON))),
stop(BACKGROUND_LAYER, switchCase(
get(PROPERTY_LOCATION_STALE), get(PROPERTY_BACKGROUND_STALE_ICON),
get(PROPERTY_BACKGROUND_ICON))),
stop(SHADOW_LAYER, literal(SHADOW_ICON)),
stop(BEARING_LAYER, get(PROPERTY_BEARING_ICON))
)
),
iconOffset(
match(literal(layerId), literal(new Float[] {0f, 0f}),
stop(literal(FOREGROUND_LAYER), get(PROPERTY_FOREGROUND_ICON_OFFSET)),
stop(literal(SHADOW_LAYER), get(PROPERTY_SHADOW_ICON_OFFSET))
)
)
);
return layer;
}
@NonNull
Layer generateAccuracyLayer() {
return new CircleLayer(ACCURACY_LAYER, LOCATION_SOURCE)
.withProperties(
circleRadius(get(PROPERTY_ACCURACY_RADIUS)),
circleColor(get(PROPERTY_ACCURACY_COLOR)),
circleOpacity(get(PROPERTY_ACCURACY_ALPHA)),
circleStrokeColor(get(PROPERTY_ACCURACY_COLOR)),
circlePitchAlignment(Property.CIRCLE_PITCH_ALIGNMENT_MAP)
);
}
}

View File

@@ -0,0 +1,422 @@
package com.mapbox.mapboxsdk.location;
import android.animation.Animator;
import android.location.Location;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.util.SparseArray;
import android.view.animation.LinearInterpolator;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.log.Logger;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.Projection;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.ACCURACY_RADIUS_ANIMATION_DURATION;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.COMPASS_UPDATE_RATE_MS;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.MAX_ANIMATION_DURATION_MS;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.TRANSITION_ANIMATION_DURATION_MS;
import static com.mapbox.mapboxsdk.location.MapboxAnimator.ANIMATOR_CAMERA_COMPASS_BEARING;
import static com.mapbox.mapboxsdk.location.MapboxAnimator.ANIMATOR_CAMERA_GPS_BEARING;
import static com.mapbox.mapboxsdk.location.MapboxAnimator.ANIMATOR_CAMERA_LATLNG;
import static com.mapbox.mapboxsdk.location.MapboxAnimator.ANIMATOR_LAYER_ACCURACY;
import static com.mapbox.mapboxsdk.location.MapboxAnimator.ANIMATOR_LAYER_COMPASS_BEARING;
import static com.mapbox.mapboxsdk.location.MapboxAnimator.ANIMATOR_LAYER_GPS_BEARING;
import static com.mapbox.mapboxsdk.location.MapboxAnimator.ANIMATOR_LAYER_LATLNG;
import static com.mapbox.mapboxsdk.location.MapboxAnimator.ANIMATOR_TILT;
import static com.mapbox.mapboxsdk.location.MapboxAnimator.ANIMATOR_ZOOM;
import static com.mapbox.mapboxsdk.location.Utils.immediateAnimation;
final class LocationAnimatorCoordinator {
private static final String TAG = "Mbgl-LocationAnimatorCoordinator";
@VisibleForTesting
final SparseArray<MapboxAnimator> animatorArray = new SparseArray<>();
private final Projection projection;
private Location previousLocation;
private float previousAccuracyRadius = -1;
private float previousCompassBearing = -1;
private long locationUpdateTimestamp = -1;
private float durationMultiplier;
private final MapboxAnimatorProvider animatorProvider;
private final MapboxAnimatorSetProvider animatorSetProvider;
private boolean compassAnimationEnabled;
private boolean accuracyAnimationEnabled;
@VisibleForTesting
int maxAnimationFps = Integer.MAX_VALUE;
@VisibleForTesting
final SparseArray<MapboxAnimator.AnimationsValueChangeListener> listeners = new SparseArray<>();
LocationAnimatorCoordinator(@NonNull Projection projection, @NonNull MapboxAnimatorSetProvider animatorSetProvider,
@NonNull MapboxAnimatorProvider animatorProvider) {
this.projection = projection;
this.animatorProvider = animatorProvider;
this.animatorSetProvider = animatorSetProvider;
}
void updateAnimatorListenerHolders(@NonNull Set<AnimatorListenerHolder> listenerHolders) {
listeners.clear();
for (AnimatorListenerHolder holder : listenerHolders) {
listeners.append(holder.getAnimatorType(), holder.getListener());
}
}
void feedNewLocation(@NonNull Location newLocation, @NonNull CameraPosition currentCameraPosition,
boolean isGpsNorth) {
if (previousLocation == null) {
previousLocation = newLocation;
locationUpdateTimestamp = SystemClock.elapsedRealtime() - TRANSITION_ANIMATION_DURATION_MS;
}
LatLng previousLayerLatLng = getPreviousLayerLatLng();
float previousLayerBearing = getPreviousLayerGpsBearing();
LatLng previousCameraLatLng = currentCameraPosition.target;
float previousCameraBearing = (float) currentCameraPosition.bearing;
LatLng targetLatLng = new LatLng(newLocation);
float targetLayerBearing = newLocation.getBearing();
float targetCameraBearing = newLocation.getBearing();
targetCameraBearing = checkGpsNorth(isGpsNorth, targetCameraBearing);
updateLayerAnimators(previousLayerLatLng, targetLatLng, previousLayerBearing, targetLayerBearing);
updateCameraAnimators(previousCameraLatLng, previousCameraBearing, targetLatLng, targetCameraBearing);
boolean snap = immediateAnimation(projection, previousCameraLatLng, targetLatLng)
|| immediateAnimation(projection, previousLayerLatLng, targetLatLng);
long animationDuration = 0;
if (!snap) {
long previousUpdateTimeStamp = locationUpdateTimestamp;
locationUpdateTimestamp = SystemClock.elapsedRealtime();
if (previousUpdateTimeStamp == 0) {
animationDuration = 0;
} else {
animationDuration = (long) ((locationUpdateTimestamp - previousUpdateTimeStamp) * durationMultiplier)
/* make animation slightly longer with durationMultiplier, defaults to 1.1f */;
}
animationDuration = Math.min(animationDuration, MAX_ANIMATION_DURATION_MS);
}
playAnimators(animationDuration,
ANIMATOR_LAYER_LATLNG,
ANIMATOR_LAYER_GPS_BEARING,
ANIMATOR_CAMERA_LATLNG,
ANIMATOR_CAMERA_GPS_BEARING);
previousLocation = newLocation;
}
void feedNewCompassBearing(float targetCompassBearing, @NonNull CameraPosition currentCameraPosition) {
if (previousCompassBearing < 0) {
previousCompassBearing = targetCompassBearing;
}
float previousLayerBearing = getPreviousLayerCompassBearing();
float previousCameraBearing = (float) currentCameraPosition.bearing;
updateCompassAnimators(targetCompassBearing, previousLayerBearing, previousCameraBearing);
playAnimators(
compassAnimationEnabled ? COMPASS_UPDATE_RATE_MS : 0,
ANIMATOR_LAYER_COMPASS_BEARING,
ANIMATOR_CAMERA_COMPASS_BEARING);
previousCompassBearing = targetCompassBearing;
}
void feedNewAccuracyRadius(float targetAccuracyRadius, boolean noAnimation) {
if (previousAccuracyRadius < 0) {
previousAccuracyRadius = targetAccuracyRadius;
}
float previousAccuracyRadius = getPreviousAccuracyRadius();
updateAccuracyAnimators(targetAccuracyRadius, previousAccuracyRadius);
playAnimators(
noAnimation || !accuracyAnimationEnabled ? 0 : ACCURACY_RADIUS_ANIMATION_DURATION,
ANIMATOR_LAYER_ACCURACY);
this.previousAccuracyRadius = targetAccuracyRadius;
}
void feedNewZoomLevel(double targetZoomLevel, @NonNull CameraPosition currentCameraPosition, long animationDuration,
@Nullable MapboxMap.CancelableCallback callback) {
updateZoomAnimator((float) targetZoomLevel, (float) currentCameraPosition.zoom, callback);
playAnimators(animationDuration, ANIMATOR_ZOOM);
}
void feedNewTilt(double targetTilt, @NonNull CameraPosition currentCameraPosition, long animationDuration,
@Nullable MapboxMap.CancelableCallback callback) {
updateTiltAnimator((float) targetTilt, (float) currentCameraPosition.tilt, callback);
playAnimators(animationDuration, ANIMATOR_TILT);
}
private LatLng getPreviousLayerLatLng() {
LatLng previousLatLng;
MapboxAnimator latLngAnimator = animatorArray.get(ANIMATOR_LAYER_LATLNG);
if (latLngAnimator != null) {
previousLatLng = (LatLng) latLngAnimator.getAnimatedValue();
} else {
previousLatLng = new LatLng(previousLocation);
}
return previousLatLng;
}
private float getPreviousLayerGpsBearing() {
MapboxFloatAnimator animator = (MapboxFloatAnimator) animatorArray.get(ANIMATOR_LAYER_GPS_BEARING);
float previousBearing;
if (animator != null) {
previousBearing = (float) animator.getAnimatedValue();
} else {
previousBearing = previousLocation.getBearing();
}
return previousBearing;
}
private float getPreviousLayerCompassBearing() {
MapboxFloatAnimator animator = (MapboxFloatAnimator) animatorArray.get(ANIMATOR_LAYER_COMPASS_BEARING);
float previousBearing;
if (animator != null) {
previousBearing = (float) animator.getAnimatedValue();
} else {
previousBearing = previousCompassBearing;
}
return previousBearing;
}
private float getPreviousAccuracyRadius() {
MapboxAnimator animator = animatorArray.get(ANIMATOR_LAYER_ACCURACY);
float previousRadius;
if (animator != null) {
previousRadius = (float) animator.getAnimatedValue();
} else {
previousRadius = previousAccuracyRadius;
}
return previousRadius;
}
private void updateLayerAnimators(LatLng previousLatLng, LatLng targetLatLng,
float previousBearing, float targetBearing) {
createNewLatLngAnimator(ANIMATOR_LAYER_LATLNG, previousLatLng, targetLatLng);
// Because Location bearing values are normalized to [0, 360]
// we need to do the same for the previous bearing value to determine the shortest path
previousBearing = Utils.normalize(previousBearing);
float normalizedLayerBearing = Utils.shortestRotation(targetBearing, previousBearing);
createNewFloatAnimator(ANIMATOR_LAYER_GPS_BEARING, previousBearing, normalizedLayerBearing);
}
private void updateCameraAnimators(LatLng previousCameraLatLng, float previousCameraBearing,
LatLng targetLatLng, float targetBearing) {
createNewLatLngAnimator(ANIMATOR_CAMERA_LATLNG, previousCameraLatLng, targetLatLng);
float normalizedCameraBearing = Utils.shortestRotation(targetBearing, previousCameraBearing);
createNewFloatAnimator(ANIMATOR_CAMERA_GPS_BEARING, previousCameraBearing, normalizedCameraBearing);
}
private void updateCompassAnimators(float targetCompassBearing, float previousLayerBearing,
float previousCameraBearing) {
float normalizedLayerBearing = Utils.shortestRotation(targetCompassBearing, previousLayerBearing);
createNewFloatAnimator(ANIMATOR_LAYER_COMPASS_BEARING, previousLayerBearing, normalizedLayerBearing);
float normalizedCameraBearing = Utils.shortestRotation(targetCompassBearing, previousCameraBearing);
createNewFloatAnimator(ANIMATOR_CAMERA_COMPASS_BEARING, previousCameraBearing, normalizedCameraBearing);
}
private void updateAccuracyAnimators(float targetAccuracyRadius, float previousAccuracyRadius) {
createNewFloatAnimator(ANIMATOR_LAYER_ACCURACY, previousAccuracyRadius, targetAccuracyRadius);
}
private void updateZoomAnimator(float targetZoomLevel, float previousZoomLevel,
@Nullable MapboxMap.CancelableCallback cancelableCallback) {
createNewCameraAdapterAnimator(ANIMATOR_ZOOM, previousZoomLevel, targetZoomLevel, cancelableCallback);
}
private void updateTiltAnimator(float targetTilt, float previousTiltLevel,
@Nullable MapboxMap.CancelableCallback cancelableCallback) {
createNewCameraAdapterAnimator(ANIMATOR_TILT, previousTiltLevel, targetTilt, cancelableCallback);
}
private void createNewLatLngAnimator(@MapboxAnimator.Type int animatorType, LatLng previous, LatLng target) {
cancelAnimator(animatorType);
MapboxAnimator.AnimationsValueChangeListener listener = listeners.get(animatorType);
if (listener != null) {
animatorArray.put(animatorType, animatorProvider.latLngAnimator(previous, target, listener, maxAnimationFps));
}
}
private void createNewFloatAnimator(@MapboxAnimator.Type int animatorType, float previous, float target) {
cancelAnimator(animatorType);
MapboxAnimator.AnimationsValueChangeListener listener = listeners.get(animatorType);
if (listener != null) {
animatorArray.put(animatorType, animatorProvider.floatAnimator(previous, target, listener, maxAnimationFps));
}
}
private void createNewCameraAdapterAnimator(@MapboxAnimator.Type int animatorType, float previous, float target,
@Nullable MapboxMap.CancelableCallback cancelableCallback) {
cancelAnimator(animatorType);
MapboxAnimator.AnimationsValueChangeListener listener = listeners.get(animatorType);
if (listener != null) {
animatorArray.put(animatorType, animatorProvider.cameraAnimator(previous, target, listener, cancelableCallback));
}
}
private float checkGpsNorth(boolean isGpsNorth, float targetCameraBearing) {
if (isGpsNorth) {
targetCameraBearing = 0;
}
return targetCameraBearing;
}
private void playAnimators(long duration, @MapboxAnimator.Type int... animatorTypes) {
List<Animator> animators = new ArrayList<>();
for (@MapboxAnimator.Type int animatorType : animatorTypes) {
Animator animator = animatorArray.get(animatorType);
if (animator != null) {
animators.add(animator);
}
}
animatorSetProvider.startAnimation(animators, new LinearInterpolator(), duration);
}
void resetAllCameraAnimations(@NonNull CameraPosition currentCameraPosition, boolean isGpsNorth) {
resetCameraCompassAnimation(currentCameraPosition);
boolean snap = resetCameraLocationAnimations(currentCameraPosition, isGpsNorth);
playAnimators(
snap ? 0 : TRANSITION_ANIMATION_DURATION_MS,
ANIMATOR_CAMERA_LATLNG,
ANIMATOR_CAMERA_GPS_BEARING);
}
private boolean resetCameraLocationAnimations(@NonNull CameraPosition currentCameraPosition, boolean isGpsNorth) {
resetCameraGpsBearingAnimation(currentCameraPosition, isGpsNorth);
return resetCameraLatLngAnimation(currentCameraPosition);
}
private boolean resetCameraLatLngAnimation(@NonNull CameraPosition currentCameraPosition) {
MapboxLatLngAnimator animator = (MapboxLatLngAnimator) animatorArray.get(ANIMATOR_CAMERA_LATLNG);
if (animator == null) {
return false;
}
LatLng currentTarget = animator.getTarget();
LatLng previousCameraTarget = currentCameraPosition.target;
createNewLatLngAnimator(ANIMATOR_CAMERA_LATLNG, previousCameraTarget, currentTarget);
return immediateAnimation(projection, previousCameraTarget, currentTarget);
}
private void resetCameraGpsBearingAnimation(@NonNull CameraPosition currentCameraPosition, boolean isGpsNorth) {
MapboxFloatAnimator animator = (MapboxFloatAnimator) animatorArray.get(ANIMATOR_CAMERA_GPS_BEARING);
if (animator == null) {
return;
}
float currentTargetBearing = animator.getTarget();
currentTargetBearing = checkGpsNorth(isGpsNorth, currentTargetBearing);
float previousCameraBearing = (float) currentCameraPosition.bearing;
float normalizedCameraBearing = Utils.shortestRotation(currentTargetBearing, previousCameraBearing);
createNewFloatAnimator(ANIMATOR_CAMERA_GPS_BEARING, previousCameraBearing, normalizedCameraBearing);
}
private void resetCameraCompassAnimation(@NonNull CameraPosition currentCameraPosition) {
MapboxFloatAnimator animator =
(MapboxFloatAnimator) animatorArray.get(ANIMATOR_CAMERA_COMPASS_BEARING);
if (animator == null) {
return;
}
float currentTargetBearing = animator.getTarget();
float previousCameraBearing = (float) currentCameraPosition.bearing;
float normalizedCameraBearing = Utils.shortestRotation(currentTargetBearing, previousCameraBearing);
createNewFloatAnimator(ANIMATOR_CAMERA_COMPASS_BEARING, previousCameraBearing, normalizedCameraBearing);
}
void resetAllLayerAnimations() {
MapboxLatLngAnimator latLngAnimator = (MapboxLatLngAnimator) animatorArray.get(ANIMATOR_LAYER_LATLNG);
MapboxFloatAnimator gpsBearingAnimator = (MapboxFloatAnimator) animatorArray.get(ANIMATOR_LAYER_GPS_BEARING);
MapboxFloatAnimator compassBearingAnimator =
(MapboxFloatAnimator) animatorArray.get(ANIMATOR_LAYER_COMPASS_BEARING);
if (latLngAnimator != null && gpsBearingAnimator != null) {
LatLng currentLatLng = (LatLng) latLngAnimator.getAnimatedValue();
LatLng currentLatLngTarget = latLngAnimator.getTarget();
createNewLatLngAnimator(ANIMATOR_LAYER_LATLNG, currentLatLng, currentLatLngTarget);
float currentGpsBearing = (float) gpsBearingAnimator.getAnimatedValue();
float currentGpsBearingTarget = gpsBearingAnimator.getTarget();
createNewFloatAnimator(ANIMATOR_LAYER_GPS_BEARING, currentGpsBearing, currentGpsBearingTarget);
long duration = latLngAnimator.getDuration() - latLngAnimator.getCurrentPlayTime();
playAnimators(duration, ANIMATOR_LAYER_LATLNG, ANIMATOR_LAYER_GPS_BEARING);
}
if (compassBearingAnimator != null) {
float currentLayerBearing = getPreviousLayerCompassBearing();
float currentLayerBearingTarget = compassBearingAnimator.getTarget();
createNewFloatAnimator(ANIMATOR_LAYER_COMPASS_BEARING, currentLayerBearing, currentLayerBearingTarget);
playAnimators(
compassAnimationEnabled ? COMPASS_UPDATE_RATE_MS : 0,
ANIMATOR_LAYER_COMPASS_BEARING);
}
}
void cancelZoomAnimation() {
cancelAnimator(ANIMATOR_ZOOM);
}
void cancelTiltAnimation() {
cancelAnimator(ANIMATOR_TILT);
}
void cancelAllAnimations() {
for (int i = 0; i < animatorArray.size(); i++) {
@MapboxAnimator.Type int animatorType = animatorArray.keyAt(i);
cancelAnimator(animatorType);
}
}
private void cancelAnimator(@MapboxAnimator.Type int animatorType) {
MapboxAnimator animator = animatorArray.get(animatorType);
if (animator != null) {
animator.cancel();
animator.removeAllUpdateListeners();
animator.removeAllListeners();
animatorArray.put(animatorType, null);
}
}
void setTrackingAnimationDurationMultiplier(float trackingAnimationDurationMultiplier) {
this.durationMultiplier = trackingAnimationDurationMultiplier;
}
void setCompassAnimationEnabled(boolean compassAnimationEnabled) {
this.compassAnimationEnabled = compassAnimationEnabled;
}
void setAccuracyAnimationEnabled(boolean accuracyAnimationEnabled) {
this.accuracyAnimationEnabled = accuracyAnimationEnabled;
}
void setMaxAnimationFps(int maxAnimationFps) {
if (maxAnimationFps <= 0) {
Logger.e(TAG, "Max animation FPS cannot be less or equal to 0.");
return;
}
this.maxAnimationFps = maxAnimationFps;
}
}

View File

@@ -0,0 +1,437 @@
package com.mapbox.mapboxsdk.location;
import android.content.Context;
import android.graphics.PointF;
import android.location.Location;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.view.MotionEvent;
import com.mapbox.android.gestures.AndroidGesturesManager;
import com.mapbox.android.gestures.MoveGestureDetector;
import com.mapbox.android.gestures.RotateGestureDetector;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.camera.CameraUpdate;
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.location.modes.CameraMode;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.Transform;
import java.util.HashSet;
import java.util.Set;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.TRANSITION_ANIMATION_DURATION_MS;
final class LocationCameraController {
@CameraMode.Mode
private int cameraMode;
private final MapboxMap mapboxMap;
private final Transform transform;
private final OnCameraTrackingChangedListener internalCameraTrackingChangedListener;
private LocationComponentOptions options;
private boolean adjustFocalPoint;
private final MoveGestureDetector moveGestureDetector;
private final OnCameraMoveInvalidateListener onCameraMoveInvalidateListener;
private final AndroidGesturesManager initialGesturesManager;
private final AndroidGesturesManager internalGesturesManager;
private boolean isTransitioning;
LocationCameraController(
Context context,
MapboxMap mapboxMap,
Transform transform,
OnCameraTrackingChangedListener internalCameraTrackingChangedListener,
@NonNull LocationComponentOptions options,
OnCameraMoveInvalidateListener onCameraMoveInvalidateListener) {
this.mapboxMap = mapboxMap;
this.transform = transform;
initialGesturesManager = mapboxMap.getGesturesManager();
internalGesturesManager = new LocationGesturesManager(context);
moveGestureDetector = internalGesturesManager.getMoveGestureDetector();
mapboxMap.addOnRotateListener(onRotateListener);
mapboxMap.addOnFlingListener(onFlingListener);
mapboxMap.addOnMoveListener(onMoveListener);
this.internalCameraTrackingChangedListener = internalCameraTrackingChangedListener;
this.onCameraMoveInvalidateListener = onCameraMoveInvalidateListener;
initializeOptions(options);
}
// Package private for testing purposes
LocationCameraController(MapboxMap mapboxMap,
Transform transform,
MoveGestureDetector moveGestureDetector,
OnCameraTrackingChangedListener internalCameraTrackingChangedListener,
OnCameraMoveInvalidateListener onCameraMoveInvalidateListener,
AndroidGesturesManager initialGesturesManager,
AndroidGesturesManager internalGesturesManager) {
this.mapboxMap = mapboxMap;
this.transform = transform;
this.moveGestureDetector = moveGestureDetector;
this.internalCameraTrackingChangedListener = internalCameraTrackingChangedListener;
this.onCameraMoveInvalidateListener = onCameraMoveInvalidateListener;
this.internalGesturesManager = internalGesturesManager;
this.initialGesturesManager = initialGesturesManager;
}
void initializeOptions(LocationComponentOptions options) {
this.options = options;
if (options.trackingGesturesManagement()) {
if (mapboxMap.getGesturesManager() != internalGesturesManager) {
mapboxMap.setGesturesManager(internalGesturesManager, true, true);
}
adjustGesturesThresholds();
} else if (mapboxMap.getGesturesManager() != initialGesturesManager) {
mapboxMap.setGesturesManager(initialGesturesManager, true, true);
}
}
void setCameraMode(@CameraMode.Mode int cameraMode) {
setCameraMode(cameraMode, null, TRANSITION_ANIMATION_DURATION_MS, null, null, null, null);
}
void setCameraMode(@CameraMode.Mode final int cameraMode, @Nullable Location lastLocation,
long transitionDuration,
@Nullable Double zoom, @Nullable Double bearing, @Nullable Double tilt,
@Nullable OnLocationCameraTransitionListener internalTransitionListener) {
if (this.cameraMode == cameraMode) {
return;
}
final boolean wasTracking = isLocationTracking();
this.cameraMode = cameraMode;
if (cameraMode != CameraMode.NONE) {
mapboxMap.cancelTransitions();
}
adjustGesturesThresholds();
notifyCameraTrackingChangeListener(wasTracking);
transitionToCurrentLocation(
wasTracking, lastLocation, transitionDuration, zoom, bearing, tilt, internalTransitionListener);
}
/**
* Initiates a camera animation to the current location if location tracking was engaged.
* Notifies an internal listener when the transition's finished to invalidate animators and notify external listeners.
*/
private void transitionToCurrentLocation(boolean wasTracking, Location lastLocation,
long transitionDuration,
Double zoom, Double bearing, Double tilt,
final OnLocationCameraTransitionListener internalTransitionListener) {
if (!wasTracking && isLocationTracking() && lastLocation != null) {
isTransitioning = true;
LatLng target = new LatLng(lastLocation);
CameraPosition.Builder builder = new CameraPosition.Builder().target(target);
if (zoom != null) {
builder.zoom(zoom);
}
if (tilt != null) {
builder.tilt(tilt);
}
if (bearing != null) {
builder.bearing(bearing);
} else {
if (isLocationBearingTracking()) {
builder.bearing(cameraMode == CameraMode.TRACKING_GPS_NORTH ? 0 : lastLocation.getBearing());
}
}
CameraUpdate update = CameraUpdateFactory.newCameraPosition(builder.build());
MapboxMap.CancelableCallback callback = new MapboxMap.CancelableCallback() {
@Override
public void onCancel() {
isTransitioning = false;
if (internalTransitionListener != null) {
internalTransitionListener.onLocationCameraTransitionCanceled(cameraMode);
}
}
@Override
public void onFinish() {
isTransitioning = false;
if (internalTransitionListener != null) {
internalTransitionListener.onLocationCameraTransitionFinished(cameraMode);
}
}
};
CameraPosition currentPosition = mapboxMap.getCameraPosition();
if (Utils.immediateAnimation(mapboxMap.getProjection(), currentPosition.target, target)) {
transform.moveCamera(
mapboxMap,
update,
callback);
} else {
transform.animateCamera(
mapboxMap,
update,
(int) transitionDuration,
callback);
}
} else {
if (internalTransitionListener != null) {
internalTransitionListener.onLocationCameraTransitionFinished(cameraMode);
}
}
}
int getCameraMode() {
return cameraMode;
}
private void setBearing(float bearing) {
if (isTransitioning) {
return;
}
transform.moveCamera(mapboxMap, CameraUpdateFactory.bearingTo(bearing), null);
onCameraMoveInvalidateListener.onInvalidateCameraMove();
}
private void setLatLng(@NonNull LatLng latLng) {
if (isTransitioning) {
return;
}
transform.moveCamera(mapboxMap, CameraUpdateFactory.newLatLng(latLng), null);
onCameraMoveInvalidateListener.onInvalidateCameraMove();
if (adjustFocalPoint) {
PointF focalPoint = mapboxMap.getProjection().toScreenLocation(latLng);
mapboxMap.getUiSettings().setFocalPoint(focalPoint);
adjustFocalPoint = false;
}
}
private void setZoom(float zoom) {
if (isTransitioning) {
return;
}
transform.moveCamera(mapboxMap, CameraUpdateFactory.zoomTo(zoom), null);
onCameraMoveInvalidateListener.onInvalidateCameraMove();
}
private void setTilt(float tilt) {
if (isTransitioning) {
return;
}
transform.moveCamera(mapboxMap, CameraUpdateFactory.tiltTo(tilt), null);
onCameraMoveInvalidateListener.onInvalidateCameraMove();
}
private final MapboxAnimator.AnimationsValueChangeListener<LatLng> latLngValueListener =
new MapboxAnimator.AnimationsValueChangeListener<LatLng>() {
@Override
public void onNewAnimationValue(LatLng value) {
setLatLng(value);
}
};
private final MapboxAnimator.AnimationsValueChangeListener<Float> gpsBearingValueListener =
new MapboxAnimator.AnimationsValueChangeListener<Float>() {
@Override
public void onNewAnimationValue(Float value) {
boolean trackingNorth = cameraMode == CameraMode.TRACKING_GPS_NORTH
&& mapboxMap.getCameraPosition().bearing == 0;
if (!trackingNorth) {
setBearing(value);
}
}
};
private final MapboxAnimator.AnimationsValueChangeListener<Float> compassBearingValueListener =
new MapboxAnimator.AnimationsValueChangeListener<Float>() {
@Override
public void onNewAnimationValue(Float value) {
if (cameraMode == CameraMode.TRACKING_COMPASS
|| cameraMode == CameraMode.NONE_COMPASS) {
setBearing(value);
}
}
};
private final MapboxAnimator.AnimationsValueChangeListener<Float> zoomValueListener =
new MapboxAnimator.AnimationsValueChangeListener<Float>() {
@Override
public void onNewAnimationValue(Float value) {
setZoom(value);
}
};
private final MapboxAnimator.AnimationsValueChangeListener<Float> tiltValueListener =
new MapboxAnimator.AnimationsValueChangeListener<Float>() {
@Override
public void onNewAnimationValue(Float value) {
setTilt(value);
}
};
Set<AnimatorListenerHolder> getAnimationListeners() {
Set<AnimatorListenerHolder> holders = new HashSet<>();
if (isLocationTracking()) {
holders.add(new AnimatorListenerHolder(MapboxAnimator.ANIMATOR_CAMERA_LATLNG, latLngValueListener));
}
if (isLocationBearingTracking()) {
holders.add(new AnimatorListenerHolder(MapboxAnimator.ANIMATOR_CAMERA_GPS_BEARING, gpsBearingValueListener));
}
if (isConsumingCompass()) {
holders.add(new AnimatorListenerHolder(
MapboxAnimator.ANIMATOR_CAMERA_COMPASS_BEARING,
compassBearingValueListener));
}
holders.add(new AnimatorListenerHolder(MapboxAnimator.ANIMATOR_ZOOM, zoomValueListener));
holders.add(new AnimatorListenerHolder(MapboxAnimator.ANIMATOR_TILT, tiltValueListener));
return holders;
}
boolean isTransitioning() {
return isTransitioning;
}
private void adjustGesturesThresholds() {
if (options.trackingGesturesManagement()) {
if (isLocationTracking()) {
adjustFocalPoint = true;
moveGestureDetector.setMoveThreshold(options.trackingInitialMoveThreshold());
} else {
moveGestureDetector.setMoveThreshold(0f);
}
}
}
boolean isConsumingCompass() {
return cameraMode == CameraMode.TRACKING_COMPASS
|| cameraMode == CameraMode.NONE_COMPASS;
}
private boolean isLocationTracking() {
return cameraMode == CameraMode.TRACKING
|| cameraMode == CameraMode.TRACKING_COMPASS
|| cameraMode == CameraMode.TRACKING_GPS
|| cameraMode == CameraMode.TRACKING_GPS_NORTH;
}
private boolean isBearingTracking() {
return cameraMode == CameraMode.NONE_COMPASS
|| cameraMode == CameraMode.TRACKING_COMPASS
|| cameraMode == CameraMode.NONE_GPS
|| cameraMode == CameraMode.TRACKING_GPS
|| cameraMode == CameraMode.TRACKING_GPS_NORTH;
}
private boolean isLocationBearingTracking() {
return cameraMode == CameraMode.TRACKING_GPS
|| cameraMode == CameraMode.TRACKING_GPS_NORTH
|| cameraMode == CameraMode.NONE_GPS;
}
private void notifyCameraTrackingChangeListener(boolean wasTracking) {
internalCameraTrackingChangedListener.onCameraTrackingChanged(cameraMode);
if (wasTracking && !isLocationTracking()) {
mapboxMap.getUiSettings().setFocalPoint(null);
internalCameraTrackingChangedListener.onCameraTrackingDismissed();
}
}
@NonNull
@VisibleForTesting
MapboxMap.OnMoveListener onMoveListener = new MapboxMap.OnMoveListener() {
private boolean interrupt;
@Override
public void onMoveBegin(@NonNull MoveGestureDetector detector) {
if (options.trackingGesturesManagement()
&& detector.getPointersCount() > 1
&& detector.getMoveThreshold() != options.trackingMultiFingerMoveThreshold()
&& isLocationTracking()) {
detector.setMoveThreshold(options.trackingMultiFingerMoveThreshold());
interrupt = true;
} else {
setCameraMode(CameraMode.NONE);
}
}
@Override
public void onMove(@NonNull MoveGestureDetector detector) {
if (interrupt) {
detector.interrupt();
return;
}
if (isLocationTracking() || isBearingTracking()) {
setCameraMode(CameraMode.NONE);
detector.interrupt();
}
}
@Override
public void onMoveEnd(@NonNull MoveGestureDetector detector) {
if (options.trackingGesturesManagement() && !interrupt && isLocationTracking()) {
detector.setMoveThreshold(options.trackingInitialMoveThreshold());
}
interrupt = false;
}
};
@NonNull
private MapboxMap.OnRotateListener onRotateListener = new MapboxMap.OnRotateListener() {
@Override
public void onRotateBegin(@NonNull RotateGestureDetector detector) {
if (isBearingTracking()) {
setCameraMode(CameraMode.NONE);
}
}
@Override
public void onRotate(@NonNull RotateGestureDetector detector) {
// no implementation
}
@Override
public void onRotateEnd(@NonNull RotateGestureDetector detector) {
// no implementation
}
};
@NonNull
private MapboxMap.OnFlingListener onFlingListener = new MapboxMap.OnFlingListener() {
@Override
public void onFling() {
setCameraMode(CameraMode.NONE);
}
};
private class LocationGesturesManager extends AndroidGesturesManager {
LocationGesturesManager(Context context) {
super(context);
}
@Override
public boolean onTouchEvent(@Nullable MotionEvent motionEvent) {
if (motionEvent != null) {
int action = motionEvent.getActionMasked();
if (action == MotionEvent.ACTION_UP) {
adjustGesturesThresholds();
}
}
return super.onTouchEvent(motionEvent);
}
}
}

View File

@@ -0,0 +1,244 @@
package com.mapbox.mapboxsdk.location;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.mapbox.android.core.location.LocationEngine;
import com.mapbox.android.core.location.LocationEngineRequest;
import com.mapbox.mapboxsdk.maps.Style;
/**
* A class which holds the various options for activating the Maps SDK's {@link LocationComponent} to eventually
* show the device location on the map.
*/
public class LocationComponentActivationOptions {
private final Context context;
private final Style style;
private final LocationEngine locationEngine;
private final LocationEngineRequest locationEngineRequest;
private final LocationComponentOptions locationComponentOptions;
private final int styleRes;
private final boolean useDefaultLocationEngine;
private LocationComponentActivationOptions(@NonNull Context context, @NonNull Style style,
@Nullable LocationEngine locationEngine,
@Nullable LocationEngineRequest locationEngineRequest,
@Nullable LocationComponentOptions locationComponentOptions,
int styleRes,
boolean useDefaultLocationEngine) {
this.context = context;
this.style = style;
this.locationEngine = locationEngine;
this.locationEngineRequest = locationEngineRequest;
this.locationComponentOptions = locationComponentOptions;
this.styleRes = styleRes;
this.useDefaultLocationEngine = useDefaultLocationEngine;
}
/**
* Convenience method to retrieve a {@link LocationComponentActivationOptions} object which is ready to build with
*
* @return a builder object
*/
@NonNull
public static Builder builder(@NonNull Context context, @NonNull Style fullyLoadedMapStyle) {
return new LocationComponentActivationOptions.Builder(context, fullyLoadedMapStyle);
}
/**
* The application's current context
*
* @return the application's current context
*/
@NonNull
public Context context() {
return context;
}
/**
* The proxy object for current map style. More info at {@link Style}
*
* @return the map's fully loaded Style object
*/
@NonNull
public Style style() {
return style;
}
/**
* The {@link LocationEngine} which the {@link LocationComponent} should use
*
* @return the engine
*/
@Nullable
public LocationEngine locationEngine() {
return locationEngine;
}
/**
* The location request which the {@link LocationComponent} should use
*
* @return the LocationEngineRequest object
*/
@Nullable
public LocationEngineRequest locationEngineRequest() {
return locationEngineRequest;
}
/**
* A built {@link LocationComponentOptions} object, which holds the various {@link LocationComponent} styling options
*
* @return the options for styling the actual LocationComponent
*/
@Nullable
public LocationComponentOptions locationComponentOptions() {
return locationComponentOptions;
}
/**
* The LocationComponent style resource (e.g. R.style.style_name)
*
* @return the style resource.
*/
public int styleRes() {
return styleRes;
}
/**
* True if you want to initialize and use the built-in location engine or false if there should be no
* location engine initialized
*
* @return whether the default LocationEngine is used
*/
public boolean useDefaultLocationEngine() {
return useDefaultLocationEngine;
}
/**
* Builder class for constructing a new instance of {@link LocationComponentActivationOptions}.
*/
public static class Builder {
private final Context context;
private final Style style;
private LocationEngine locationEngine;
private LocationEngineRequest locationEngineRequest;
private LocationComponentOptions locationComponentOptions;
private int styleRes;
/**
* Set to true as the default in case a true/false value isn't declared via the builder's
* {@link LocationComponentActivationOptions#useDefaultLocationEngine()} method.
* <p>
* Please be aware that this activation boolean is ignored when a non-null
* {@link LocationEngine} is set via the builder's `locationEngine()` method.
*/
private boolean useDefaultLocationEngine = true;
/**
* Constructor for the {@link LocationComponentActivationOptions} builder class.
* While other activation options are optional, the activation process always requires
* context and a fully-loaded map {@link Style} object, which is why the two are in this
* constructor.
*/
public Builder(@NonNull Context context, @NonNull Style style) {
this.context = context;
this.style = style;
}
/**
* Deliver your own {@link LocationEngine} to the LocationComponent.
* <p>
* The true/false
* {@link LocationComponentActivationOptions.Builder#useDefaultLocationEngine()}
* activation option is ignored when a non-null {@link LocationEngine} is set via
* this `locationEngine()` method.
*
* @param locationEngine a {@link LocationEngine} object
* @return the {@link Builder} object being constructed
*/
@NonNull
public Builder locationEngine(@Nullable LocationEngine locationEngine) {
this.locationEngine = locationEngine;
return this;
}
/**
* @param locationEngineRequest the location request which the
* {@link LocationComponent} should use
* @return the {@link Builder} object being constructed
*/
public Builder locationEngineRequest(LocationEngineRequest locationEngineRequest) {
this.locationEngineRequest = locationEngineRequest;
return this;
}
/**
* @param locationComponentOptions a built {@link LocationComponentOptions} object,
* which holds the various {@link LocationComponent}
* styling options
* @return the {@link Builder} object being constructed
*/
public Builder locationComponentOptions(LocationComponentOptions locationComponentOptions) {
this.locationComponentOptions = locationComponentOptions;
return this;
}
/**
* @param styleRes the LocationComponent style resource (e.g. R.style.style_name)
* @return the {@link Builder} object being constructed
*/
public Builder styleRes(int styleRes) {
this.styleRes = styleRes;
return this;
}
/**
* @param useDefaultLocationEngine true if you want to initialize and use the
* built-in location engine or false if there
* should be no location engine initialized
* This is ignored when null is set as the parameter
* for {@link LocationComponentActivationOptions#builder
* (Context, Style)#locationEngine()}.
* @return the {@link Builder} object being constructed
*/
public Builder useDefaultLocationEngine(boolean useDefaultLocationEngine) {
this.useDefaultLocationEngine = useDefaultLocationEngine;
return this;
}
/**
* Method which actually builds the {@link LocationComponentActivationOptions} object while
* taking the various options into account.
*
* @return a built {@link LocationComponentActivationOptions} object
*/
public LocationComponentActivationOptions build() {
if (styleRes != 0 && locationComponentOptions != null) {
throw new IllegalArgumentException(
"You've provided both a style resource and a LocationComponentOptions object to the "
+ "LocationComponentActivationOptions builder. You can't use both and "
+ "you must choose one of the two to style the LocationComponent.");
}
if (context == null) {
throw new NullPointerException(
"Context in LocationComponentActivationOptions is null.");
}
if (style == null) {
throw new NullPointerException(
"Style in LocationComponentActivationOptions is null. Make sure the "
+ "Style object isn't null. Wait for the map to fully load before passing "
+ "the Style object to LocationComponentActivationOptions.");
}
if (!style.isFullyLoaded()) {
throw new IllegalArgumentException(
"Style in LocationComponentActivationOptions isn't fully loaded. Wait for the "
+ "map to fully load before passing the Style object to "
+ "LocationComponentActivationOptions.");
}
return new LocationComponentActivationOptions(context, style, locationEngine,
locationEngineRequest, locationComponentOptions, styleRes, useDefaultLocationEngine);
}
}
}

View File

@@ -0,0 +1,268 @@
package com.mapbox.mapboxsdk.location;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.Surface;
import android.view.WindowManager;
import com.mapbox.mapboxsdk.log.Logger;
import java.util.ArrayList;
import java.util.List;
/**
* This manager class handles compass events such as starting the tracking of device bearing, or
* when a new compass update occurs.
*/
class LocationComponentCompassEngine implements CompassEngine, SensorEventListener {
private static final String TAG = "Mbgl-LocationComponentCompassEngine";
// The rate sensor events will be delivered at. As the Android documentation states, this is only
// a hint to the system and the events might actually be received faster or slower then this
// specified rate. Since the minimum Android API levels about 9, we are able to set this value
// ourselves rather than using one of the provided constants which deliver updates too quickly for
// our use case. The default is set to 100ms
static final int SENSOR_DELAY_MICROS = 100 * 1000;
// Filtering coefficient 0 < ALPHA < 1
private static final float ALPHA = 0.45f;
@NonNull
private final WindowManager windowManager;
@NonNull
private final SensorManager sensorManager;
private final List<CompassListener> compassListeners = new ArrayList<>();
// Not all devices have a compassSensor
@Nullable
private Sensor compassSensor;
@Nullable
private Sensor gravitySensor;
@Nullable
private Sensor magneticFieldSensor;
@NonNull
private float[] truncatedRotationVectorValue = new float[4];
@NonNull
private float[] rotationMatrix = new float[9];
private float[] rotationVectorValue;
private float lastHeading;
private int lastAccuracySensorStatus;
private long compassUpdateNextTimestamp;
@Nullable
private float[] gravityValues = new float[3];
@Nullable
private float[] magneticValues = new float[3];
/**
* Construct a new instance of the this class. A internal compass listeners needed to separate it
* from the cleared list of public listeners.
*/
LocationComponentCompassEngine(@NonNull WindowManager windowManager, @NonNull SensorManager sensorManager) {
this.windowManager = windowManager;
this.sensorManager = sensorManager;
compassSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
if (compassSensor == null) {
if (isGyroscopeAvailable()) {
Logger.d(TAG, "Rotation vector sensor not supported on device, "
+ "falling back to orientation.");
compassSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
} else {
Logger.d(TAG, "Rotation vector sensor not supported on device, "
+ "falling back to accelerometer and magnetic field.");
gravitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
magneticFieldSensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
}
}
}
@Override
public void addCompassListener(@NonNull CompassListener compassListener) {
if (compassListeners.isEmpty()) {
registerSensorListeners();
}
compassListeners.add(compassListener);
}
@Override
public void removeCompassListener(@NonNull CompassListener compassListener) {
compassListeners.remove(compassListener);
if (compassListeners.isEmpty()) {
unregisterSensorListeners();
}
}
@Override
public int getLastAccuracySensorStatus() {
return lastAccuracySensorStatus;
}
@Override
public float getLastHeading() {
return lastHeading;
}
@Override
public void onSensorChanged(@NonNull SensorEvent event) {
// check when the last time the compass was updated, return if too soon.
long currentTime = SystemClock.elapsedRealtime();
if (currentTime < compassUpdateNextTimestamp) {
return;
}
if (lastAccuracySensorStatus == SensorManager.SENSOR_STATUS_UNRELIABLE) {
Logger.d(TAG, "Compass sensor is unreliable, device calibration is needed.");
return;
}
if (event.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR) {
rotationVectorValue = getRotationVectorFromSensorEvent(event);
updateOrientation();
} else if (event.sensor.getType() == Sensor.TYPE_ORIENTATION) {
notifyCompassChangeListeners((event.values[0] + 360) % 360);
} else if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
gravityValues = lowPassFilter(getRotationVectorFromSensorEvent(event), gravityValues);
updateOrientation();
} else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
magneticValues = lowPassFilter(getRotationVectorFromSensorEvent(event), magneticValues);
updateOrientation();
}
// Update the compassUpdateNextTimestamp
compassUpdateNextTimestamp = currentTime + LocationComponentConstants.COMPASS_UPDATE_RATE_MS;
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
if (lastAccuracySensorStatus != accuracy) {
for (CompassListener compassListener : compassListeners) {
compassListener.onCompassAccuracyChange(accuracy);
}
lastAccuracySensorStatus = accuracy;
}
}
private boolean isGyroscopeAvailable() {
return sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null;
}
@SuppressWarnings("SuspiciousNameCombination")
private void updateOrientation() {
if (rotationVectorValue != null) {
SensorManager.getRotationMatrixFromVector(rotationMatrix, rotationVectorValue);
} else {
// Get rotation matrix given the gravity and geomagnetic matrices
SensorManager.getRotationMatrix(rotationMatrix, null, gravityValues, magneticValues);
}
final int worldAxisForDeviceAxisX;
final int worldAxisForDeviceAxisY;
// Remap the axes as if the device screen was the instrument panel,
// and adjust the rotation matrix for the device orientation.
switch (windowManager.getDefaultDisplay().getRotation()) {
case Surface.ROTATION_90:
worldAxisForDeviceAxisX = SensorManager.AXIS_Z;
worldAxisForDeviceAxisY = SensorManager.AXIS_MINUS_X;
break;
case Surface.ROTATION_180:
worldAxisForDeviceAxisX = SensorManager.AXIS_MINUS_X;
worldAxisForDeviceAxisY = SensorManager.AXIS_MINUS_Z;
break;
case Surface.ROTATION_270:
worldAxisForDeviceAxisX = SensorManager.AXIS_MINUS_Z;
worldAxisForDeviceAxisY = SensorManager.AXIS_X;
break;
case Surface.ROTATION_0:
default:
worldAxisForDeviceAxisX = SensorManager.AXIS_X;
worldAxisForDeviceAxisY = SensorManager.AXIS_Z;
break;
}
float[] adjustedRotationMatrix = new float[9];
SensorManager.remapCoordinateSystem(rotationMatrix, worldAxisForDeviceAxisX,
worldAxisForDeviceAxisY, adjustedRotationMatrix);
// Transform rotation matrix into azimuth/pitch/roll
float[] orientation = new float[3];
SensorManager.getOrientation(adjustedRotationMatrix, orientation);
// The x-axis is all we care about here.
notifyCompassChangeListeners((float) Math.toDegrees(orientation[0]));
}
private void notifyCompassChangeListeners(float heading) {
for (CompassListener compassListener : compassListeners) {
compassListener.onCompassChanged(heading);
}
lastHeading = heading;
}
private void registerSensorListeners() {
if (isCompassSensorAvailable()) {
// Does nothing if the sensors already registered.
sensorManager.registerListener(this, compassSensor, SENSOR_DELAY_MICROS);
} else {
sensorManager.registerListener(this, gravitySensor, SENSOR_DELAY_MICROS);
sensorManager.registerListener(this, magneticFieldSensor, SENSOR_DELAY_MICROS);
}
}
private void unregisterSensorListeners() {
if (isCompassSensorAvailable()) {
sensorManager.unregisterListener(this, compassSensor);
} else {
sensorManager.unregisterListener(this, gravitySensor);
sensorManager.unregisterListener(this, magneticFieldSensor);
}
}
private boolean isCompassSensorAvailable() {
return compassSensor != null;
}
/**
* Helper function, that filters newValues, considering previous values
*
* @param newValues array of float, that contains new data
* @param smoothedValues array of float, that contains previous state
* @return float filtered array of float
*/
@NonNull
private float[] lowPassFilter(@NonNull float[] newValues, @Nullable float[] smoothedValues) {
if (smoothedValues == null) {
return newValues;
}
for (int i = 0; i < newValues.length; i++) {
smoothedValues[i] = smoothedValues[i] + ALPHA * (newValues[i] - smoothedValues[i]);
}
return smoothedValues;
}
/**
* Pulls out the rotation vector from a SensorEvent, with a maximum length
* vector of four elements to avoid potential compatibility issues.
*
* @param event the sensor event
* @return the events rotation vector, potentially truncated
*/
@NonNull
private float[] getRotationVectorFromSensorEvent(@NonNull SensorEvent event) {
if (event.values.length > 4) {
// On some Samsung devices SensorManager.getRotationMatrixFromVector
// appears to throw an exception if rotation vector has length > 4.
// For the purposes of this class the first 4 values of the
// rotation vector are sufficient (see crbug.com/335298 for details).
// Only affects Android 4.3
System.arraycopy(event.values, 0, truncatedRotationVectorValue, 0, 4);
return truncatedRotationVectorValue;
} else {
return event.values;
}
}
}

View File

@@ -0,0 +1,94 @@
package com.mapbox.mapboxsdk.location;
/**
* Contains all the constants being used for the {@link LocationComponent}.
*/
public final class LocationComponentConstants {
// Controls the compass update rate in milliseconds
static final long COMPASS_UPDATE_RATE_MS = 500;
// Sets the transition animation duration when switching camera modes.
static final long TRANSITION_ANIMATION_DURATION_MS = 750;
// Sets the max allowed time for the location icon animation from one LatLng to another.
static final long MAX_ANIMATION_DURATION_MS = 2000;
// Sets the duration of change of accuracy radius when a different value is provided.
static final long ACCURACY_RADIUS_ANIMATION_DURATION = 250;
// Default animation duration for zooming while tracking.
static final long DEFAULT_TRACKING_ZOOM_ANIM_DURATION = 750;
// Default animation duration for tilting while tracking.
static final long DEFAULT_TRACKING_TILT_ANIM_DURATION = 1250;
// Threshold value to perform immediate camera/layer position update.
static final double INSTANT_LOCATION_TRANSITION_THRESHOLD = 50_000;
// Default interval between location updates
static final long DEFAULT_INTERVAL_MILLIS = 1000;
// Default fastest acceptable interval between location updates
static final long DEFAULT_FASTEST_INTERVAL_MILLIS = 1000;
// Sources
/**
* Source ID of the location's GeoJsonSource.
*/
public static final String LOCATION_SOURCE = "mapbox-location-source";
static final String PROPERTY_GPS_BEARING = "mapbox-property-gps-bearing";
static final String PROPERTY_COMPASS_BEARING = "mapbox-property-compass-bearing";
static final String PROPERTY_LOCATION_STALE = "mapbox-property-location-stale";
static final String PROPERTY_ACCURACY_RADIUS = "mapbox-property-accuracy-radius";
static final String PROPERTY_ACCURACY_COLOR = "mapbox-property-accuracy-color";
static final String PROPERTY_ACCURACY_ALPHA = "mapbox-property-accuracy-alpha";
static final String PROPERTY_FOREGROUND_ICON_OFFSET = "mapbox-property-foreground-icon-offset";
static final String PROPERTY_SHADOW_ICON_OFFSET = "mapbox-property-shadow-icon-offset";
static final String PROPERTY_FOREGROUND_ICON = "mapbox-property-foreground-icon";
static final String PROPERTY_BACKGROUND_ICON = "mapbox-property-background-icon";
static final String PROPERTY_FOREGROUND_STALE_ICON = "mapbox-property-foreground-stale-icon";
static final String PROPERTY_BACKGROUND_STALE_ICON = "mapbox-property-background-stale-icon";
static final String PROPERTY_BEARING_ICON = "mapbox-property-shadow-icon";
// Layers
/**
* Layer ID of the location shadow.
*/
public static final String SHADOW_LAYER = "mapbox-location-shadow-layer";
/**
* Layer ID of the location foreground icon.
*/
public static final String FOREGROUND_LAYER = "mapbox-location-foreground-layer";
/**
* Layer ID of the location background icon.
*/
public static final String BACKGROUND_LAYER = "mapbox-location-background-layer";
/**
* Layer ID of the location accuracy.
*/
public static final String ACCURACY_LAYER = "mapbox-location-accuracy-layer";
/**
* Layer ID of the location bearing icon.
*/
public static final String BEARING_LAYER = "mapbox-location-bearing-layer";
// Icons
static final String FOREGROUND_ICON = "mapbox-location-icon";
static final String BACKGROUND_ICON = "mapbox-location-stroke-icon";
static final String FOREGROUND_STALE_ICON = "mapbox-location-stale-icon";
static final String BACKGROUND_STALE_ICON = "mapbox-location-background-stale-icon";
static final String SHADOW_ICON = "mapbox-location-shadow-icon";
static final String BEARING_ICON = "mapbox-location-bearing-icon";
private LocationComponentConstants() {
// Class should not be initialized
}
}

View File

@@ -0,0 +1,8 @@
package com.mapbox.mapboxsdk.location;
final class LocationComponentNotInitializedException extends RuntimeException {
LocationComponentNotInitializedException() {
super("The LocationComponent has to be activated with one of the LocationComponent#activateLocationComponent"
+ " overloads before any other methods are invoked.");
}
}

View File

@@ -0,0 +1,48 @@
package com.mapbox.mapboxsdk.location;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.mapbox.mapboxsdk.maps.Style;
import com.mapbox.mapboxsdk.style.layers.Layer;
class LocationComponentPositionManager {
@NonNull
private final Style style;
@Nullable
private String layerAbove;
@Nullable
private String layerBelow;
LocationComponentPositionManager(@NonNull Style style, @Nullable String layerAbove, @Nullable String layerBelow) {
this.style = style;
this.layerAbove = layerAbove;
this.layerBelow = layerBelow;
}
/**
* Returns true whenever layer above/below configuration has changed and requires re-layout.
*/
boolean update(@Nullable String layerAbove, @Nullable String layerBelow) {
boolean requiresUpdate =
!(this.layerAbove == layerAbove || (this.layerAbove != null && this.layerAbove.equals(layerAbove)))
|| !(this.layerBelow == layerBelow || (this.layerBelow != null && this.layerBelow.equals(layerBelow)));
this.layerAbove = layerAbove;
this.layerBelow = layerBelow;
return requiresUpdate;
}
void addLayerToMap(@NonNull Layer layer) {
if (layerAbove != null) {
style.addLayerAbove(layer, layerAbove);
} else if (layerBelow != null) {
style.addLayerBelow(layer, layerBelow);
} else {
style.addLayer(layer);
}
}
}

View File

@@ -0,0 +1,464 @@
package com.mapbox.mapboxsdk.location;
import android.graphics.Bitmap;
import android.graphics.PointF;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.mapbox.geojson.Feature;
import com.mapbox.geojson.Point;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.location.modes.RenderMode;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.Style;
import com.mapbox.mapboxsdk.style.layers.Layer;
import com.mapbox.mapboxsdk.style.layers.SymbolLayer;
import com.mapbox.mapboxsdk.style.sources.GeoJsonSource;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.ACCURACY_LAYER;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.BACKGROUND_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.BACKGROUND_LAYER;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.BACKGROUND_STALE_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.BEARING_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.BEARING_LAYER;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.FOREGROUND_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.FOREGROUND_LAYER;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.FOREGROUND_STALE_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.LOCATION_SOURCE;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_ACCURACY_ALPHA;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_ACCURACY_COLOR;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_ACCURACY_RADIUS;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_BACKGROUND_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_BACKGROUND_STALE_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_BEARING_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_COMPASS_BEARING;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_FOREGROUND_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_FOREGROUND_ICON_OFFSET;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_FOREGROUND_STALE_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_GPS_BEARING;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_LOCATION_STALE;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.PROPERTY_SHADOW_ICON_OFFSET;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.SHADOW_ICON;
import static com.mapbox.mapboxsdk.location.LocationComponentConstants.SHADOW_LAYER;
import static com.mapbox.mapboxsdk.style.expressions.Expression.interpolate;
import static com.mapbox.mapboxsdk.style.expressions.Expression.linear;
import static com.mapbox.mapboxsdk.style.expressions.Expression.stop;
import static com.mapbox.mapboxsdk.style.expressions.Expression.zoom;
import static com.mapbox.mapboxsdk.style.layers.Property.NONE;
import static com.mapbox.mapboxsdk.style.layers.Property.VISIBLE;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconSize;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.visibility;
import static com.mapbox.mapboxsdk.utils.ColorUtils.colorToRgbaString;
final class LocationLayerController {
@RenderMode.Mode
private int renderMode;
private final MapboxMap mapboxMap;
private Style style;
private final LayerSourceProvider layerSourceProvider;
private final LayerBitmapProvider bitmapProvider;
private LocationComponentOptions options;
private final OnRenderModeChangedListener internalRenderModeChangedListener;
@VisibleForTesting
final Set<String> layerSet = new HashSet<>();
private Feature locationFeature;
private GeoJsonSource locationSource;
private boolean isHidden = true;
private LocationComponentPositionManager positionManager;
LocationLayerController(MapboxMap mapboxMap, Style style, LayerSourceProvider layerSourceProvider,
LayerFeatureProvider featureProvider, LayerBitmapProvider bitmapProvider,
@NonNull LocationComponentOptions options,
@NonNull OnRenderModeChangedListener internalRenderModeChangedListener) {
this.mapboxMap = mapboxMap;
this.style = style;
this.layerSourceProvider = layerSourceProvider;
this.bitmapProvider = bitmapProvider;
this.locationFeature = featureProvider.generateLocationFeature(locationFeature, options);
this.internalRenderModeChangedListener = internalRenderModeChangedListener;
initializeComponents(style, options);
}
void initializeComponents(Style style, LocationComponentOptions options) {
this.style = style;
this.positionManager = new LocationComponentPositionManager(style, options.layerAbove(), options.layerBelow());
addLocationSource();
addLayers();
applyStyle(options);
if (isHidden) {
hide();
} else {
show();
}
}
void applyStyle(@NonNull LocationComponentOptions options) {
if (positionManager.update(options.layerAbove(), options.layerBelow())) {
removeLayers();
addLayers();
if (isHidden) {
hide();
}
}
this.options = options;
float elevation = options.elevation();
// Only add icon elevation if the values greater than 0.
if (elevation > 0) {
styleShadow(options);
}
styleForeground(options);
styleBackground(options);
styleBearing(options);
styleAccuracy(options.accuracyAlpha(), options.accuracyColor());
styleScaling(options);
determineIconsSource(options);
if (!isHidden) {
show();
}
}
void setRenderMode(@RenderMode.Mode int renderMode) {
if (this.renderMode == renderMode) {
return;
}
this.renderMode = renderMode;
styleForeground(options);
determineIconsSource(options);
if (!isHidden) {
show();
}
internalRenderModeChangedListener.onRenderModeChanged(renderMode);
}
int getRenderMode() {
return renderMode;
}
//
// Layer action
//
void show() {
isHidden = false;
boolean isStale = locationFeature.getBooleanProperty(PROPERTY_LOCATION_STALE);
switch (renderMode) {
case RenderMode.NORMAL:
setLayerVisibility(SHADOW_LAYER, true);
setLayerVisibility(FOREGROUND_LAYER, true);
setLayerVisibility(BACKGROUND_LAYER, true);
setLayerVisibility(ACCURACY_LAYER, !isStale);
setLayerVisibility(BEARING_LAYER, false);
break;
case RenderMode.COMPASS:
setLayerVisibility(SHADOW_LAYER, true);
setLayerVisibility(FOREGROUND_LAYER, true);
setLayerVisibility(BACKGROUND_LAYER, true);
setLayerVisibility(ACCURACY_LAYER, !isStale);
setLayerVisibility(BEARING_LAYER, true);
break;
case RenderMode.GPS:
setLayerVisibility(SHADOW_LAYER, false);
setLayerVisibility(FOREGROUND_LAYER, true);
setLayerVisibility(BACKGROUND_LAYER, true);
setLayerVisibility(ACCURACY_LAYER, false);
setLayerVisibility(BEARING_LAYER, false);
break;
default:
break;
}
}
void hide() {
isHidden = true;
for (String layerId : layerSet) {
setLayerVisibility(layerId, false);
}
}
void updateForegroundOffset(double tilt) {
JsonArray foregroundJsonArray = new JsonArray();
foregroundJsonArray.add(0f);
foregroundJsonArray.add((float) (-0.05 * tilt));
locationFeature.addProperty(PROPERTY_FOREGROUND_ICON_OFFSET, foregroundJsonArray);
JsonArray backgroundJsonArray = new JsonArray();
backgroundJsonArray.add(0f);
backgroundJsonArray.add((float) (0.05 * tilt));
locationFeature.addProperty(PROPERTY_SHADOW_ICON_OFFSET, backgroundJsonArray);
refreshSource();
}
void updateForegroundBearing(float bearing) {
if (renderMode != RenderMode.GPS) {
setBearingProperty(PROPERTY_GPS_BEARING, bearing);
}
}
boolean isHidden() {
return isHidden;
}
boolean isConsumingCompass() {
return renderMode == RenderMode.COMPASS;
}
private void setLayerVisibility(@NonNull String layerId, boolean visible) {
Layer layer = style.getLayer(layerId);
if (layer != null) {
String targetVisibility = visible ? VISIBLE : NONE;
if (!layer.getVisibility().value.equals(targetVisibility)) {
layer.setProperties(visibility(visible ? VISIBLE : NONE));
}
}
}
private void addLayers() {
// positions the top-most reference layer
Layer layer = layerSourceProvider.generateLayer(BEARING_LAYER);
positionManager.addLayerToMap(layer);
layerSet.add(layer.getId());
// adds remaining layers while keeping the order
addSymbolLayer(FOREGROUND_LAYER, BEARING_LAYER);
addSymbolLayer(BACKGROUND_LAYER, FOREGROUND_LAYER);
addSymbolLayer(SHADOW_LAYER, BACKGROUND_LAYER);
addAccuracyLayer();
}
private void addSymbolLayer(@NonNull String layerId, @NonNull String beforeLayerId) {
Layer layer = layerSourceProvider.generateLayer(layerId);
addLayerToMap(layer, beforeLayerId);
}
private void addAccuracyLayer() {
Layer accuracyLayer = layerSourceProvider.generateAccuracyLayer();
addLayerToMap(accuracyLayer, BACKGROUND_LAYER);
}
private void addLayerToMap(Layer layer, @NonNull String idBelowLayer) {
style.addLayerBelow(layer, idBelowLayer);
layerSet.add(layer.getId());
}
private void removeLayers() {
for (String layerId : layerSet) {
style.removeLayer(layerId);
}
layerSet.clear();
}
private void setBearingProperty(@NonNull String propertyId, float bearing) {
locationFeature.addNumberProperty(propertyId, bearing);
refreshSource();
}
private void updateAccuracyRadius(float accuracy) {
locationFeature.addNumberProperty(PROPERTY_ACCURACY_RADIUS, accuracy);
refreshSource();
}
//
// Source actions
//
private void addLocationSource() {
locationSource = layerSourceProvider.generateSource(locationFeature);
style.addSource(locationSource);
}
private void refreshSource() {
GeoJsonSource source = style.getSourceAs(LOCATION_SOURCE);
if (source != null) {
locationSource.setGeoJson(locationFeature);
}
}
private void setLocationPoint(Point locationPoint) {
JsonObject properties = locationFeature.properties();
if (properties != null) {
locationFeature = Feature.fromGeometry(locationPoint, properties);
refreshSource();
}
}
//
// Styling
//
private void styleBackground(LocationComponentOptions options) {
Bitmap backgroundBitmap = bitmapProvider.generateBitmap(
options.backgroundDrawable(), options.backgroundTintColor()
);
Bitmap backgroundStaleBitmap = bitmapProvider.generateBitmap(
options.backgroundDrawableStale(), options.backgroundStaleTintColor()
);
style.addImage(BACKGROUND_ICON, backgroundBitmap);
style.addImage(BACKGROUND_STALE_ICON, backgroundStaleBitmap);
}
private void styleShadow(@NonNull LocationComponentOptions options) {
style.addImage(SHADOW_ICON, bitmapProvider.generateShadowBitmap(options));
}
private void styleBearing(LocationComponentOptions options) {
Bitmap bearingBitmap = bitmapProvider.generateBitmap(options.bearingDrawable(), options.bearingTintColor());
style.addImage(BEARING_ICON, bearingBitmap);
}
private void styleAccuracy(float accuracyAlpha, @ColorInt int accuracyColor) {
locationFeature.addNumberProperty(PROPERTY_ACCURACY_ALPHA, accuracyAlpha);
locationFeature.addStringProperty(PROPERTY_ACCURACY_COLOR, colorToRgbaString(accuracyColor));
refreshSource();
}
private void styleForeground(LocationComponentOptions options) {
Bitmap foregroundBitmap = bitmapProvider.generateBitmap(
options.foregroundDrawable(), options.foregroundTintColor()
);
Bitmap foregroundBitmapStale = bitmapProvider.generateBitmap(
options.foregroundDrawableStale(), options.foregroundStaleTintColor()
);
if (renderMode == RenderMode.GPS) {
foregroundBitmap = bitmapProvider.generateBitmap(
options.gpsDrawable(), options.foregroundTintColor()
);
foregroundBitmapStale = bitmapProvider.generateBitmap(
options.gpsDrawable(), options.foregroundStaleTintColor()
);
}
style.addImage(FOREGROUND_ICON, foregroundBitmap);
style.addImage(FOREGROUND_STALE_ICON, foregroundBitmapStale);
}
private void styleScaling(@NonNull LocationComponentOptions options) {
for (String layerId : layerSet) {
Layer layer = style.getLayer(layerId);
if (layer instanceof SymbolLayer) {
layer.setProperties(
iconSize(
interpolate(linear(), zoom(),
stop(mapboxMap.getMinZoomLevel(), options.minZoomIconScale()),
stop(mapboxMap.getMaxZoomLevel(), options.maxZoomIconScale())
)
)
);
}
}
}
private void determineIconsSource(LocationComponentOptions options) {
String foregroundIconString = buildIconString(
renderMode == RenderMode.GPS ? options.gpsName() : options.foregroundName(), FOREGROUND_ICON);
String foregroundStaleIconString = buildIconString(options.foregroundStaleName(), FOREGROUND_STALE_ICON);
String backgroundIconString = buildIconString(options.backgroundName(), BACKGROUND_ICON);
String backgroundStaleIconString = buildIconString(options.backgroundStaleName(), BACKGROUND_STALE_ICON);
String bearingIconString = buildIconString(options.bearingName(), BEARING_ICON);
locationFeature.addStringProperty(PROPERTY_FOREGROUND_ICON, foregroundIconString);
locationFeature.addStringProperty(PROPERTY_BACKGROUND_ICON, backgroundIconString);
locationFeature.addStringProperty(PROPERTY_FOREGROUND_STALE_ICON, foregroundStaleIconString);
locationFeature.addStringProperty(PROPERTY_BACKGROUND_STALE_ICON, backgroundStaleIconString);
locationFeature.addStringProperty(PROPERTY_BEARING_ICON, bearingIconString);
refreshSource();
}
@NonNull
private String buildIconString(@Nullable String bitmapName, @NonNull String drawableName) {
if (bitmapName != null) {
return bitmapName;
}
return drawableName;
}
void setLocationsStale(boolean isStale) {
locationFeature.addBooleanProperty(PROPERTY_LOCATION_STALE, isStale);
refreshSource();
if (renderMode != RenderMode.GPS) {
setLayerVisibility(ACCURACY_LAYER, !isStale);
}
}
//
// Map click event
//
boolean onMapClick(@NonNull LatLng point) {
PointF screenLoc = mapboxMap.getProjection().toScreenLocation(point);
List<Feature> features = mapboxMap.queryRenderedFeatures(screenLoc,
BACKGROUND_LAYER,
FOREGROUND_LAYER,
BEARING_LAYER
);
return !features.isEmpty();
}
private final MapboxAnimator.AnimationsValueChangeListener<LatLng> latLngValueListener =
new MapboxAnimator.AnimationsValueChangeListener<LatLng>() {
@Override
public void onNewAnimationValue(LatLng value) {
Point point = Point.fromLngLat(value.getLongitude(), value.getLatitude());
setLocationPoint(point);
}
};
private final MapboxAnimator.AnimationsValueChangeListener<Float> gpsBearingValueListener =
new MapboxAnimator.AnimationsValueChangeListener<Float>() {
@Override
public void onNewAnimationValue(Float value) {
setBearingProperty(PROPERTY_GPS_BEARING, value);
}
};
private final MapboxAnimator.AnimationsValueChangeListener<Float> compassBearingValueListener =
new MapboxAnimator.AnimationsValueChangeListener<Float>() {
@Override
public void onNewAnimationValue(Float value) {
setBearingProperty(PROPERTY_COMPASS_BEARING, value);
}
};
private final MapboxAnimator.AnimationsValueChangeListener<Float> accuracyValueListener =
new MapboxAnimator.AnimationsValueChangeListener<Float>() {
@Override
public void onNewAnimationValue(Float value) {
updateAccuracyRadius(value);
}
};
Set<AnimatorListenerHolder> getAnimationListeners() {
Set<AnimatorListenerHolder> holders = new HashSet<>();
holders.add(new AnimatorListenerHolder(MapboxAnimator.ANIMATOR_LAYER_LATLNG, latLngValueListener));
if (renderMode == RenderMode.GPS) {
holders.add(new AnimatorListenerHolder(MapboxAnimator.ANIMATOR_LAYER_GPS_BEARING, gpsBearingValueListener));
} else if (renderMode == RenderMode.COMPASS) {
holders.add(
new AnimatorListenerHolder(MapboxAnimator.ANIMATOR_LAYER_COMPASS_BEARING, compassBearingValueListener));
}
if (renderMode == RenderMode.COMPASS || renderMode == RenderMode.NORMAL) {
holders.add(new AnimatorListenerHolder(MapboxAnimator.ANIMATOR_LAYER_ACCURACY, accuracyValueListener));
}
return holders;
}
}

View File

@@ -0,0 +1,95 @@
package com.mapbox.mapboxsdk.location;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Abstract class for all of the location component animators.
*
* @param <K> Data type that will be animated.
*/
abstract class MapboxAnimator<K> extends ValueAnimator implements ValueAnimator.AnimatorUpdateListener {
@Retention(RetentionPolicy.SOURCE)
@IntDef( {
ANIMATOR_LAYER_LATLNG,
ANIMATOR_CAMERA_LATLNG,
ANIMATOR_LAYER_GPS_BEARING,
ANIMATOR_LAYER_COMPASS_BEARING,
ANIMATOR_CAMERA_GPS_BEARING,
ANIMATOR_CAMERA_COMPASS_BEARING,
ANIMATOR_LAYER_ACCURACY,
ANIMATOR_ZOOM,
ANIMATOR_TILT
})
@interface Type {
}
static final int ANIMATOR_LAYER_LATLNG = 0;
static final int ANIMATOR_CAMERA_LATLNG = 1;
static final int ANIMATOR_LAYER_GPS_BEARING = 2;
static final int ANIMATOR_LAYER_COMPASS_BEARING = 3;
static final int ANIMATOR_CAMERA_GPS_BEARING = 4;
static final int ANIMATOR_CAMERA_COMPASS_BEARING = 5;
static final int ANIMATOR_LAYER_ACCURACY = 6;
static final int ANIMATOR_ZOOM = 7;
static final int ANIMATOR_TILT = 8;
private final AnimationsValueChangeListener<K> updateListener;
private final K target;
private K animatedValue;
private final double minUpdateInterval;
private long timeElapsed;
MapboxAnimator(@NonNull K previous, @NonNull K target, @NonNull AnimationsValueChangeListener<K> updateListener,
int maxAnimationFps) {
minUpdateInterval = 1E9 / maxAnimationFps;
setObjectValues(previous, target);
setEvaluator(provideEvaluator());
this.updateListener = updateListener;
this.target = target;
addUpdateListener(this);
addListener(new AnimatorListener());
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
animatedValue = (K) animation.getAnimatedValue();
long currentTime = System.nanoTime();
if ((currentTime - timeElapsed) < minUpdateInterval) {
return;
}
postUpdates();
timeElapsed = currentTime;
}
private class AnimatorListener extends AnimatorListenerAdapter {
@Override
public void onAnimationEnd(Animator animation) {
postUpdates();
}
}
private void postUpdates() {
updateListener.onNewAnimationValue(animatedValue);
}
K getTarget() {
return target;
}
abstract TypeEvaluator provideEvaluator();
interface AnimationsValueChangeListener<K> {
void onNewAnimationValue(K value);
}
}

View File

@@ -0,0 +1,40 @@
package com.mapbox.mapboxsdk.location;
import android.support.annotation.Nullable;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.maps.MapboxMap;
final class MapboxAnimatorProvider {
private static MapboxAnimatorProvider INSTANCE;
private MapboxAnimatorProvider() {
// private constructor
}
public static MapboxAnimatorProvider getInstance() {
if (INSTANCE == null) {
INSTANCE = new MapboxAnimatorProvider();
}
return INSTANCE;
}
MapboxLatLngAnimator latLngAnimator(LatLng previous, LatLng target,
MapboxAnimator.AnimationsValueChangeListener updateListener,
int maxAnimationFps) {
return new MapboxLatLngAnimator(previous, target, updateListener, maxAnimationFps);
}
MapboxFloatAnimator floatAnimator(Float previous, Float target,
MapboxAnimator.AnimationsValueChangeListener updateListener,
int maxAnimationFps) {
return new MapboxFloatAnimator(previous, target, updateListener, maxAnimationFps);
}
MapboxCameraAnimatorAdapter cameraAnimator(Float previous, Float target,
MapboxAnimator.AnimationsValueChangeListener updateListener,
@Nullable MapboxMap.CancelableCallback cancelableCallback) {
return new MapboxCameraAnimatorAdapter(previous, target, updateListener, cancelableCallback);
}
}

View File

@@ -0,0 +1,32 @@
package com.mapbox.mapboxsdk.location;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.support.annotation.NonNull;
import android.view.animation.Interpolator;
import java.util.List;
class MapboxAnimatorSetProvider {
private static MapboxAnimatorSetProvider instance;
private MapboxAnimatorSetProvider() {
// private constructor
}
static MapboxAnimatorSetProvider getInstance() {
if (instance == null) {
instance = new MapboxAnimatorSetProvider();
}
return instance;
}
void startAnimation(@NonNull List<Animator> animators, @NonNull Interpolator interpolator,
long duration) {
AnimatorSet locationAnimatorSet = new AnimatorSet();
locationAnimatorSet.playTogether(animators);
locationAnimatorSet.setInterpolator(interpolator);
locationAnimatorSet.setDuration(duration);
locationAnimatorSet.start();
}
}

View File

@@ -0,0 +1,36 @@
package com.mapbox.mapboxsdk.location;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.support.annotation.Nullable;
import com.mapbox.mapboxsdk.maps.MapboxMap;
class MapboxCameraAnimatorAdapter extends MapboxFloatAnimator {
@Nullable
private final MapboxMap.CancelableCallback cancelableCallback;
MapboxCameraAnimatorAdapter(Float previous, Float target,
AnimationsValueChangeListener updateListener,
@Nullable MapboxMap.CancelableCallback cancelableCallback) {
super(previous, target, updateListener, Integer.MAX_VALUE);
this.cancelableCallback = cancelableCallback;
addListener(new MapboxAnimatorListener());
}
private final class MapboxAnimatorListener extends AnimatorListenerAdapter {
@Override
public void onAnimationCancel(Animator animation) {
if (cancelableCallback != null) {
cancelableCallback.onCancel();
}
}
@Override
public void onAnimationEnd(Animator animation) {
if (cancelableCallback != null) {
cancelableCallback.onFinish();
}
}
}
}

View File

@@ -0,0 +1,17 @@
package com.mapbox.mapboxsdk.location;
import android.animation.FloatEvaluator;
import android.animation.TypeEvaluator;
import android.support.annotation.NonNull;
class MapboxFloatAnimator extends MapboxAnimator<Float> {
MapboxFloatAnimator(Float previous, Float target, AnimationsValueChangeListener updateListener, int maxAnimationFps) {
super(previous, target, updateListener, maxAnimationFps);
}
@NonNull
@Override
TypeEvaluator provideEvaluator() {
return new FloatEvaluator();
}
}

View File

@@ -0,0 +1,20 @@
package com.mapbox.mapboxsdk.location;
import android.animation.TypeEvaluator;
import android.support.annotation.NonNull;
import com.mapbox.mapboxsdk.geometry.LatLng;
class MapboxLatLngAnimator extends MapboxAnimator<LatLng> {
MapboxLatLngAnimator(LatLng previous, LatLng target, AnimationsValueChangeListener updateListener,
int maxAnimationFps) {
super(previous, target, updateListener, maxAnimationFps);
}
@NonNull
@Override
TypeEvaluator provideEvaluator() {
return new LatLngEvaluator();
}
}

View File

@@ -0,0 +1,7 @@
package com.mapbox.mapboxsdk.location;
interface OnCameraMoveInvalidateListener {
void onInvalidateCameraMove();
}

View File

@@ -0,0 +1,21 @@
package com.mapbox.mapboxsdk.location;
import com.mapbox.mapboxsdk.location.modes.CameraMode;
/**
* Listener that gets invoked when camera tracking state changes.
*/
public interface OnCameraTrackingChangedListener {
/**
* Invoked whenever camera tracking is broken.
* This callback gets invoked just after {@link #onCameraTrackingChanged(int)}, if needed.
*/
void onCameraTrackingDismissed();
/**
* Invoked on every {@link CameraMode} change.
*
* @param currentMode current active {@link CameraMode}.
*/
void onCameraTrackingChanged(@CameraMode.Mode int currentMode);
}

View File

@@ -0,0 +1,24 @@
package com.mapbox.mapboxsdk.location;
import com.mapbox.mapboxsdk.location.modes.CameraMode;
/**
* Callback for {@link CameraMode } transition state.
*/
public interface OnLocationCameraTransitionListener {
/**
* Invoked when the camera mode transition animation has been finished.
*
* @param cameraMode camera mode change that initiated the transition
*/
void onLocationCameraTransitionFinished(@CameraMode.Mode int cameraMode);
/**
* Invoked when the camera mode transition animation has been canceled.
* <p>
* The camera mode is set regardless of the cancellation of the transition animation.
*
* @param cameraMode camera mode change that initiated the transition
*/
void onLocationCameraTransitionCanceled(@CameraMode.Mode int cameraMode);
}

View File

@@ -0,0 +1,14 @@
package com.mapbox.mapboxsdk.location;
/**
* The Location Component exposes an API for listening to when the user clicks on the location
* layer icon visible on the map. When this event occurs, the {@link #onLocationComponentClick()} method
* gets invoked.
*/
public interface OnLocationClickListener {
/**
* Called whenever user clicks on the location layer drawn on the map.
*/
void onLocationComponentClick();
}

View File

@@ -0,0 +1,14 @@
package com.mapbox.mapboxsdk.location;
/**
* The Location Component exposes an API for listening to when the user long clicks on the location
* layer icon visible on the map. when this event occurs, the {@link #onLocationComponentLongClick()} method
* gets invoked.
*/
public interface OnLocationLongClickListener {
/**
* Called whenever user long clicks on the location layer drawn on the map.
*/
void onLocationComponentLongClick();
}

Some files were not shown because too many files have changed in this diff Show More