diff --git a/android/java/.gitignore b/android/java/.gitignore new file mode 100644 index 00000000000..478d786980b --- /dev/null +++ b/android/java/.gitignore @@ -0,0 +1,14 @@ +# Gradle files +.gradle/ + +# IntelliJ files +.idea +*.iml + +# Build files +build/ +*/build/ +*.so + +# Local settings +local.properties diff --git a/android/java/app/build.gradle b/android/java/app/build.gradle new file mode 100644 index 00000000000..cb3750b7d37 --- /dev/null +++ b/android/java/app/build.gradle @@ -0,0 +1,23 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 19 + buildToolsVersion "21.1.0" + + defaultConfig { + applicationId "com.mapbox.mapboxgl.app" + minSdkVersion 19 + targetSdkVersion 20 + } + + buildTypes { + release { + runProguard false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + } + } +} + +dependencies { + compile project(':lib') +} diff --git a/android/java/app/lint.xml b/android/java/app/lint.xml new file mode 100644 index 00000000000..7328a1fd1c3 --- /dev/null +++ b/android/java/app/lint.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/android/java/app/src/main/AndroidManifest.xml b/android/java/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..83995b8d3dd --- /dev/null +++ b/android/java/app/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/android/java/app/src/main/java/com/mapbox/mapboxgl/app/FragmentActivity.java b/android/java/app/src/main/java/com/mapbox/mapboxgl/app/FragmentActivity.java new file mode 100644 index 00000000000..1b27ca2fdd1 --- /dev/null +++ b/android/java/app/src/main/java/com/mapbox/mapboxgl/app/FragmentActivity.java @@ -0,0 +1,29 @@ +package com.mapbox.mapboxgl.app; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; + +public class FragmentActivity extends Activity { + + // + // Static members + // + + // Tag used for logging + private static final String TAG = "FragmentActivity"; + + // + // Lifecycle events + // + + // Called when activity is created + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Log.v(TAG, "onCreate"); + + // Load the layout + setContentView(R.layout.activity_fragment); + } +} diff --git a/android/java/app/src/main/java/com/mapbox/mapboxgl/app/MainActivity.java b/android/java/app/src/main/java/com/mapbox/mapboxgl/app/MainActivity.java new file mode 100644 index 00000000000..43537f9ac87 --- /dev/null +++ b/android/java/app/src/main/java/com/mapbox/mapboxgl/app/MainActivity.java @@ -0,0 +1,98 @@ +package com.mapbox.mapboxgl.app; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; + +import com.mapbox.mapboxgl.lib.MapView; + +public class MainActivity extends Activity { + + // + // Static members + // + + // Tag used for logging + private static final String TAG = "MainActivity"; + + // The map + private MapView mMapView; + + // + // Lifecycle events + // + + // Called when the activity is created + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Log.v(TAG, "onCreate"); + + // Load the layout + setContentView(R.layout.activity_main); + mMapView = (MapView) findViewById(R.id.map); + + // Need to pass on any saved state to the map + mMapView.onCreate(savedInstanceState); + } + + // Called when the activity is destroyed + @Override + protected void onDestroy() { + super.onDestroy(); + Log.v(TAG, "onDestroy"); + + // Need to pass on to view + mMapView.onDestroy(); + } + + // Called when the activity is visible + @Override + protected void onStart() { + super.onStart(); + Log.v(TAG, "onStart"); + + // Need to pass on to view + mMapView.onStart(); + } + + // Called when the activity is invisible + @Override + protected void onStop() { + super.onStop(); + Log.v(TAG, "onStop"); + + // Need to pass on to view + mMapView.onStop(); + } + + // Called when the activity is in the background + @Override + protected void onPause() { + super.onPause(); + Log.v(TAG, "onPause"); + + // Need to pass on to view + mMapView.onPause(); + } + + // Called when the activity is no longer in the background + @Override + protected void onResume() { + super.onResume(); + Log.v(TAG, "onResume"); + + // Need to pass on to view + mMapView.onResume(); + } + + // Called before activity is destroyed + @Override + protected void onSaveInstanceState(Bundle outState) { + Log.v(TAG, "onSaveInstanceState"); + + // Need to retrieve any saved state from the map + mMapView.onSaveInstanceState(outState); + super.onSaveInstanceState(outState); + } +} diff --git a/android/java/app/src/main/res/drawable-hdpi/ic_launcher.png b/android/java/app/src/main/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 00000000000..288b66551d1 Binary files /dev/null and b/android/java/app/src/main/res/drawable-hdpi/ic_launcher.png differ diff --git a/android/java/app/src/main/res/drawable-mdpi/ic_launcher.png b/android/java/app/src/main/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 00000000000..6ae570b4db4 Binary files /dev/null and b/android/java/app/src/main/res/drawable-mdpi/ic_launcher.png differ diff --git a/android/java/app/src/main/res/drawable-xhdpi/ic_launcher.png b/android/java/app/src/main/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 00000000000..d4fb7cd9d86 Binary files /dev/null and b/android/java/app/src/main/res/drawable-xhdpi/ic_launcher.png differ diff --git a/android/java/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/android/java/app/src/main/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 00000000000..85a6081587e Binary files /dev/null and b/android/java/app/src/main/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/android/java/app/src/main/res/layout/activity_fragment.xml b/android/java/app/src/main/res/layout/activity_fragment.xml new file mode 100644 index 00000000000..6310106fce8 --- /dev/null +++ b/android/java/app/src/main/res/layout/activity_fragment.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/android/java/app/src/main/res/layout/activity_main.xml b/android/java/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000000..e4b11e3ada7 --- /dev/null +++ b/android/java/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/android/java/app/src/main/res/values-v11/styles.xml b/android/java/app/src/main/res/values-v11/styles.xml new file mode 100644 index 00000000000..3c02242ad04 --- /dev/null +++ b/android/java/app/src/main/res/values-v11/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/android/java/app/src/main/res/values-v14/styles.xml b/android/java/app/src/main/res/values-v14/styles.xml new file mode 100644 index 00000000000..a91fd0372b2 --- /dev/null +++ b/android/java/app/src/main/res/values-v14/styles.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/android/java/app/src/main/res/values/strings.xml b/android/java/app/src/main/res/values/strings.xml new file mode 100644 index 00000000000..f6d4e76fadc --- /dev/null +++ b/android/java/app/src/main/res/values/strings.xml @@ -0,0 +1,6 @@ + + + + Mapbox GL + + diff --git a/android/java/app/src/main/res/values/styles.xml b/android/java/app/src/main/res/values/styles.xml new file mode 100644 index 00000000000..6ce89c7ba43 --- /dev/null +++ b/android/java/app/src/main/res/values/styles.xml @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/android/java/build.gradle b/android/java/build.gradle new file mode 100644 index 00000000000..e1e1653021e --- /dev/null +++ b/android/java/build.gradle @@ -0,0 +1,15 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:0.13.2' + } +} + +allprojects { + repositories { + jcenter() + } +} diff --git a/android/java/gradle/wrapper/gradle-wrapper.jar b/android/java/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000000..8c0fb64a869 Binary files /dev/null and b/android/java/gradle/wrapper/gradle-wrapper.jar differ diff --git a/android/java/gradle/wrapper/gradle-wrapper.properties b/android/java/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000000..ffaa281a453 --- /dev/null +++ b/android/java/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Apr 10 15:27:10 PDT 2013 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.1-all.zip diff --git a/android/java/gradlew b/android/java/gradlew new file mode 100755 index 00000000000..91a7e269e19 --- /dev/null +++ b/android/java/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/android/java/gradlew.bat b/android/java/gradlew.bat new file mode 100644 index 00000000000..8a0b282aa68 --- /dev/null +++ b/android/java/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/android/java/lib/build.gradle b/android/java/lib/build.gradle new file mode 100644 index 00000000000..d6e4f141c30 --- /dev/null +++ b/android/java/lib/build.gradle @@ -0,0 +1,31 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 19 + buildToolsVersion "21.1.0" + + defaultConfig { + applicationId "com.mapbox.mapboxgl.lib" + minSdkVersion 19 + targetSdkVersion 20 + + ndk { + moduleName "NativeMapView" + } + } + + buildTypes { + debug { + jniDebugBuild true + } + + release { + runProguard false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + } + } +} + +dependencies { + compile "commons-io:commons-io:2.4" +} diff --git a/android/java/lib/lint.xml b/android/java/lib/lint.xml new file mode 100644 index 00000000000..84598b8d4ba --- /dev/null +++ b/android/java/lib/lint.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/android/java/lib/src/main/AndroidManifest.xml b/android/java/lib/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..b947bc7d0b4 --- /dev/null +++ b/android/java/lib/src/main/AndroidManifest.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/BaseGestureDetector.java b/android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/BaseGestureDetector.java new file mode 100644 index 00000000000..86d1d50f687 --- /dev/null +++ b/android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/BaseGestureDetector.java @@ -0,0 +1,160 @@ +package com.almeros.android.multitouch.gesturedetectors; + +import android.content.Context; +import android.view.MotionEvent; + +/** + * @author Almer Thie (code.almeros.com) Copyright (c) 2013, Almer Thie + * (code.almeros.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +public abstract class BaseGestureDetector { + protected final Context mContext; + protected boolean mGestureInProgress; + + protected MotionEvent mPrevEvent; + protected MotionEvent mCurrEvent; + + protected float mCurrPressure; + protected float mPrevPressure; + protected long mTimeDelta; + + /** + * This value is the threshold ratio between the previous combined pressure + * and the current combined pressure. When pressure decreases rapidly + * between events the position values can often be imprecise, as it usually + * indicates that the user is in the process of lifting a pointer off of the + * device. This value was tuned experimentally. + */ + protected static final float PRESSURE_THRESHOLD = 0.67f; + + public BaseGestureDetector(Context context) { + mContext = context; + } + + /** + * All gesture detectors need to be called through this method to be able to + * detect gestures. This method delegates work to handler methods + * (handleStartProgressEvent, handleInProgressEvent) implemented in + * extending classes. + * + * @param event + * @return + */ + public boolean onTouchEvent(MotionEvent event) { + final int actionCode = event.getAction() & MotionEvent.ACTION_MASK; + if (!mGestureInProgress) { + handleStartProgressEvent(actionCode, event); + } else { + handleInProgressEvent(actionCode, event); + } + return true; + } + + /** + * Called when the current event occurred when NO gesture is in progress + * yet. The handling in this implementation may set the gesture in progress + * (via mGestureInProgress) or out of progress + * + * @param actionCode + * @param event + */ + protected abstract void handleStartProgressEvent(int actionCode, + MotionEvent event); + + /** + * Called when the current event occurred when a gesture IS in progress. The + * handling in this implementation may set the gesture out of progress (via + * mGestureInProgress). + * + * @param action + * @param event + */ + protected abstract void handleInProgressEvent(int actionCode, + MotionEvent event); + + protected void updateStateByEvent(MotionEvent curr) { + final MotionEvent prev = mPrevEvent; + + // Reset mCurrEvent + if (mCurrEvent != null) { + mCurrEvent.recycle(); + mCurrEvent = null; + } + mCurrEvent = MotionEvent.obtain(curr); + + // Delta time + mTimeDelta = curr.getEventTime() - prev.getEventTime(); + + // Pressure + mCurrPressure = curr.getPressure(curr.getActionIndex()); + mPrevPressure = prev.getPressure(prev.getActionIndex()); + } + + protected void resetState() { + if (mPrevEvent != null) { + mPrevEvent.recycle(); + mPrevEvent = null; + } + if (mCurrEvent != null) { + mCurrEvent.recycle(); + mCurrEvent = null; + } + mGestureInProgress = false; + } + + /** + * Returns {@code true} if a gesture is currently in progress. + * + * @return {@code true} if a gesture is currently in progress, {@code false} + * otherwise. + */ + public boolean isInProgress() { + return mGestureInProgress; + } + + /** + * Return the time difference in milliseconds between the previous accepted + * GestureDetector event and the current GestureDetector event. + * + * @return Time difference since the last move event in milliseconds. + */ + public long getTimeDelta() { + return mTimeDelta; + } + + /** + * Return the event time of the current GestureDetector event being + * processed. + * + * @return Current GestureDetector event time in milliseconds. + */ + public long getEventTime() { + return mCurrEvent.getEventTime(); + } + +} diff --git a/android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/MoveGestureDetector.java b/android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/MoveGestureDetector.java new file mode 100644 index 00000000000..0136183ed27 --- /dev/null +++ b/android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/MoveGestureDetector.java @@ -0,0 +1,188 @@ +package com.almeros.android.multitouch.gesturedetectors; + +import android.content.Context; +import android.graphics.PointF; +import android.view.MotionEvent; + +/** + * @author Almer Thie (code.almeros.com) Copyright (c) 2013, Almer Thie + * (code.almeros.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +public class MoveGestureDetector extends BaseGestureDetector { + + /** + * Listener which must be implemented which is used by MoveGestureDetector + * to perform callbacks to any implementing class which is registered to a + * MoveGestureDetector via the constructor. + * + * @see MoveGestureDetector.SimpleOnMoveGestureListener + */ + public interface OnMoveGestureListener { + public boolean onMove(MoveGestureDetector detector); + + public boolean onMoveBegin(MoveGestureDetector detector); + + public void onMoveEnd(MoveGestureDetector detector); + } + + /** + * Helper class which may be extended and where the methods may be + * implemented. This way it is not necessary to implement all methods of + * OnMoveGestureListener. + */ + public static class SimpleOnMoveGestureListener implements + OnMoveGestureListener { + public boolean onMove(MoveGestureDetector detector) { + return false; + } + + public boolean onMoveBegin(MoveGestureDetector detector) { + return true; + } + + public void onMoveEnd(MoveGestureDetector detector) { + // Do nothing, overridden implementation may be used + } + } + + private static final PointF FOCUS_DELTA_ZERO = new PointF(); + + private final OnMoveGestureListener mListener; + + private PointF mCurrFocusInternal; + private PointF mPrevFocusInternal; + private PointF mFocusExternal = new PointF(); + private PointF mFocusDeltaExternal = new PointF(); + + public MoveGestureDetector(Context context, OnMoveGestureListener listener) { + super(context); + mListener = listener; + } + + @Override + protected void handleStartProgressEvent(int actionCode, MotionEvent event) { + switch (actionCode) { + case MotionEvent.ACTION_DOWN: + resetState(); // In case we missed an UP/CANCEL event + + mPrevEvent = MotionEvent.obtain(event); + mTimeDelta = 0; + + updateStateByEvent(event); + break; + + case MotionEvent.ACTION_MOVE: + mGestureInProgress = mListener.onMoveBegin(this); + break; + } + } + + @Override + protected void handleInProgressEvent(int actionCode, MotionEvent event) { + switch (actionCode) { + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mListener.onMoveEnd(this); + resetState(); + break; + + case MotionEvent.ACTION_MOVE: + updateStateByEvent(event); + + // Only accept the event if our relative pressure is within + // a certain limit. This can help filter shaky data as a + // finger is lifted. + if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) { + final boolean updatePrevious = mListener.onMove(this); + if (updatePrevious) { + mPrevEvent.recycle(); + mPrevEvent = MotionEvent.obtain(event); + } + } + break; + } + } + + protected void updateStateByEvent(MotionEvent curr) { + super.updateStateByEvent(curr); + + final MotionEvent prev = mPrevEvent; + + // Focus intenal + mCurrFocusInternal = determineFocalPoint(curr); + mPrevFocusInternal = determineFocalPoint(prev); + + // Focus external + // - Prevent skipping of focus delta when a finger is added or removed + boolean mSkipNextMoveEvent = prev.getPointerCount() != curr + .getPointerCount(); + mFocusDeltaExternal = mSkipNextMoveEvent ? FOCUS_DELTA_ZERO + : new PointF(mCurrFocusInternal.x - mPrevFocusInternal.x, + mCurrFocusInternal.y - mPrevFocusInternal.y); + + // - Don't directly use mFocusInternal (or skipping will occur). Add + // unskipped delta values to mFocusExternal instead. + mFocusExternal.x += mFocusDeltaExternal.x; + mFocusExternal.y += mFocusDeltaExternal.y; + } + + /** + * Determine (multi)finger focal point (a.k.a. center point between all + * fingers) + * + * @param MotionEvent + * e + * @return PointF focal point + */ + private PointF determineFocalPoint(MotionEvent e) { + // Number of fingers on screen + final int pCount = e.getPointerCount(); + float x = 0.0f; + float y = 0.0f; + + for (int i = 0; i < pCount; i++) { + x += e.getX(i); + y += e.getY(i); + } + + return new PointF(x / pCount, y / pCount); + } + + public float getFocusX() { + return mFocusExternal.x; + } + + public float getFocusY() { + return mFocusExternal.y; + } + + public PointF getFocusDelta() { + return mFocusDeltaExternal; + } + +} diff --git a/android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/RotateGestureDetector.java b/android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/RotateGestureDetector.java new file mode 100644 index 00000000000..124fe8509c8 --- /dev/null +++ b/android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/RotateGestureDetector.java @@ -0,0 +1,180 @@ +package com.almeros.android.multitouch.gesturedetectors; + +import android.content.Context; +import android.view.MotionEvent; + +/** + * @author Almer Thie (code.almeros.com) Copyright (c) 2013, Almer Thie + * (code.almeros.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +public class RotateGestureDetector extends TwoFingerGestureDetector { + + /** + * Listener which must be implemented which is used by RotateGestureDetector + * to perform callbacks to any implementing class which is registered to a + * RotateGestureDetector via the constructor. + * + * @see RotateGestureDetector.SimpleOnRotateGestureListener + */ + public interface OnRotateGestureListener { + public boolean onRotate(RotateGestureDetector detector); + + public boolean onRotateBegin(RotateGestureDetector detector); + + public void onRotateEnd(RotateGestureDetector detector); + } + + /** + * Helper class which may be extended and where the methods may be + * implemented. This way it is not necessary to implement all methods of + * OnRotateGestureListener. + */ + public static class SimpleOnRotateGestureListener implements + OnRotateGestureListener { + public boolean onRotate(RotateGestureDetector detector) { + return false; + } + + public boolean onRotateBegin(RotateGestureDetector detector) { + return true; + } + + public void onRotateEnd(RotateGestureDetector detector) { + // Do nothing, overridden implementation may be used + } + } + + private final OnRotateGestureListener mListener; + private boolean mSloppyGesture; + + public RotateGestureDetector(Context context, + OnRotateGestureListener listener) { + super(context); + mListener = listener; + } + + @Override + protected void handleStartProgressEvent(int actionCode, MotionEvent event) { + switch (actionCode) { + case MotionEvent.ACTION_POINTER_DOWN: + // At least the second finger is on screen now + + resetState(); // In case we missed an UP/CANCEL event + mPrevEvent = MotionEvent.obtain(event); + mTimeDelta = 0; + + updateStateByEvent(event); + + // See if we have a sloppy gesture + mSloppyGesture = isSloppyGesture(event); + if (!mSloppyGesture) { + // No, start gesture now + mGestureInProgress = mListener.onRotateBegin(this); + } + break; + + case MotionEvent.ACTION_MOVE: + if (!mSloppyGesture) { + break; + } + + // See if we still have a sloppy gesture + mSloppyGesture = isSloppyGesture(event); + if (!mSloppyGesture) { + // No, start normal gesture now + mGestureInProgress = mListener.onRotateBegin(this); + } + + break; + + case MotionEvent.ACTION_POINTER_UP: + if (!mSloppyGesture) { + break; + } + + break; + } + } + + @Override + protected void handleInProgressEvent(int actionCode, MotionEvent event) { + switch (actionCode) { + case MotionEvent.ACTION_POINTER_UP: + // Gesture ended but + updateStateByEvent(event); + + if (!mSloppyGesture) { + mListener.onRotateEnd(this); + } + + resetState(); + break; + + case MotionEvent.ACTION_CANCEL: + if (!mSloppyGesture) { + mListener.onRotateEnd(this); + } + + resetState(); + break; + + case MotionEvent.ACTION_MOVE: + updateStateByEvent(event); + + // Only accept the event if our relative pressure is within + // a certain limit. This can help filter shaky data as a + // finger is lifted. + if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) { + final boolean updatePrevious = mListener.onRotate(this); + if (updatePrevious) { + mPrevEvent.recycle(); + mPrevEvent = MotionEvent.obtain(event); + } + } + break; + } + } + + @Override + protected void resetState() { + super.resetState(); + mSloppyGesture = false; + } + + /** + * Return the rotation difference from the previous rotate event to the + * current event. + * + * @return The current rotation //difference in degrees. + */ + public float getRotationDegreesDelta() { + double diffRadians = Math.atan2(mPrevFingerDiffY, mPrevFingerDiffX) + - Math.atan2(mCurrFingerDiffY, mCurrFingerDiffX); + return (float) (diffRadians * 180.0 / Math.PI); + } +} diff --git a/android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/ShoveGestureDetector.java b/android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/ShoveGestureDetector.java new file mode 100644 index 00000000000..254597105b1 --- /dev/null +++ b/android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/ShoveGestureDetector.java @@ -0,0 +1,213 @@ +package com.almeros.android.multitouch.gesturedetectors; + +import android.content.Context; +import android.view.MotionEvent; + +/** + * @author Robert Nordan (robert.nordan@norkart.no) + * + * Copyright (c) 2013, Norkart AS + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +public class ShoveGestureDetector extends TwoFingerGestureDetector { + + /** + * Listener which must be implemented which is used by ShoveGestureDetector + * to perform callbacks to any implementing class which is registered to a + * ShoveGestureDetector via the constructor. + * + * @see ShoveGestureDetector.SimpleOnShoveGestureListener + */ + public interface OnShoveGestureListener { + public boolean onShove(ShoveGestureDetector detector); + + public boolean onShoveBegin(ShoveGestureDetector detector); + + public void onShoveEnd(ShoveGestureDetector detector); + } + + /** + * Helper class which may be extended and where the methods may be + * implemented. This way it is not necessary to implement all methods of + * OnShoveGestureListener. + */ + public static class SimpleOnShoveGestureListener implements + OnShoveGestureListener { + public boolean onShove(ShoveGestureDetector detector) { + return false; + } + + public boolean onShoveBegin(ShoveGestureDetector detector) { + return true; + } + + public void onShoveEnd(ShoveGestureDetector detector) { + // Do nothing, overridden implementation may be used + } + } + + private float mPrevAverageY; + private float mCurrAverageY; + + private final OnShoveGestureListener mListener; + private boolean mSloppyGesture; + + public ShoveGestureDetector(Context context, OnShoveGestureListener listener) { + super(context); + mListener = listener; + } + + @Override + protected void handleStartProgressEvent(int actionCode, MotionEvent event) { + switch (actionCode) { + case MotionEvent.ACTION_POINTER_DOWN: + // At least the second finger is on screen now + + resetState(); // In case we missed an UP/CANCEL event + mPrevEvent = MotionEvent.obtain(event); + mTimeDelta = 0; + + updateStateByEvent(event); + + // See if we have a sloppy gesture + mSloppyGesture = isSloppyGesture(event); + if (!mSloppyGesture) { + // No, start gesture now + mGestureInProgress = mListener.onShoveBegin(this); + } + break; + + case MotionEvent.ACTION_MOVE: + if (!mSloppyGesture) { + break; + } + + // See if we still have a sloppy gesture + mSloppyGesture = isSloppyGesture(event); + if (!mSloppyGesture) { + // No, start normal gesture now + mGestureInProgress = mListener.onShoveBegin(this); + } + + break; + + case MotionEvent.ACTION_POINTER_UP: + if (!mSloppyGesture) { + break; + } + + break; + } + } + + @Override + protected void handleInProgressEvent(int actionCode, MotionEvent event) { + switch (actionCode) { + case MotionEvent.ACTION_POINTER_UP: + // Gesture ended but + updateStateByEvent(event); + + if (!mSloppyGesture) { + mListener.onShoveEnd(this); + } + + resetState(); + break; + + case MotionEvent.ACTION_CANCEL: + if (!mSloppyGesture) { + mListener.onShoveEnd(this); + } + + resetState(); + break; + + case MotionEvent.ACTION_MOVE: + updateStateByEvent(event); + + // Only accept the event if our relative pressure is within + // a certain limit. This can help filter shaky data as a + // finger is lifted. Also check that shove is meaningful. + if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD + && Math.abs(getShovePixelsDelta()) > 0.5f) { + final boolean updatePrevious = mListener.onShove(this); + if (updatePrevious) { + mPrevEvent.recycle(); + mPrevEvent = MotionEvent.obtain(event); + } + } + break; + } + } + + @Override + protected void resetState() { + super.resetState(); + mSloppyGesture = false; + mPrevAverageY = 0.0f; + mCurrAverageY = 0.0f; + } + + @Override + protected void updateStateByEvent(MotionEvent curr) { + super.updateStateByEvent(curr); + + final MotionEvent prev = mPrevEvent; + float py0 = prev.getY(0); + float py1 = prev.getY(1); + mPrevAverageY = (py0 + py1) / 2.0f; + + float cy0 = curr.getY(0); + float cy1 = curr.getY(1); + mCurrAverageY = (cy0 + cy1) / 2.0f; + } + + @Override + protected boolean isSloppyGesture(MotionEvent event) { + boolean sloppy = super.isSloppyGesture(event); + if (sloppy) + return true; + + // If it's not traditionally sloppy, we check if the angle between + // fingers + // is acceptable. + double angle = Math.abs(Math.atan2(mCurrFingerDiffY, mCurrFingerDiffX)); + // about 20 degrees, left or right + return !((0.0f < angle && angle < 0.35f) || 2.79f < angle + && angle < Math.PI); + } + + /** + * Return the distance in pixels from the previous shove event to the + * current event. + * + * @return The current distance in pixels. + */ + public float getShovePixelsDelta() { + return mCurrAverageY - mPrevAverageY; + } +} diff --git a/android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/TwoFingerGestureDetector.java b/android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/TwoFingerGestureDetector.java new file mode 100644 index 00000000000..5215a91bba1 --- /dev/null +++ b/android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/TwoFingerGestureDetector.java @@ -0,0 +1,228 @@ +package com.almeros.android.multitouch.gesturedetectors; + +import android.content.Context; +import android.graphics.PointF; +import android.util.DisplayMetrics; +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +/** + * @author Almer Thie (code.almeros.com) Copyright (c) 2013, Almer Thie + * (code.almeros.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +public abstract class TwoFingerGestureDetector extends BaseGestureDetector { + + private final float mEdgeSlop; + private float mRightSlopEdge; + private float mBottomSlopEdge; + + protected float mPrevFingerDiffX; + protected float mPrevFingerDiffY; + protected float mCurrFingerDiffX; + protected float mCurrFingerDiffY; + + private float mCurrLen; + private float mPrevLen; + + private PointF mFocus; + + public TwoFingerGestureDetector(Context context) { + super(context); + + ViewConfiguration config = ViewConfiguration.get(context); + mEdgeSlop = config.getScaledEdgeSlop(); + } + + @Override + protected abstract void handleStartProgressEvent(int actionCode, + MotionEvent event); + + @Override + protected abstract void handleInProgressEvent(int actionCode, + MotionEvent event); + + protected void updateStateByEvent(MotionEvent curr) { + super.updateStateByEvent(curr); + + final MotionEvent prev = mPrevEvent; + + mCurrLen = -1; + mPrevLen = -1; + + // Previous + final float px0 = prev.getX(0); + final float py0 = prev.getY(0); + final float px1 = prev.getX(1); + final float py1 = prev.getY(1); + final float pvx = px1 - px0; + final float pvy = py1 - py0; + mPrevFingerDiffX = pvx; + mPrevFingerDiffY = pvy; + + // Current + final float cx0 = curr.getX(0); + final float cy0 = curr.getY(0); + final float cx1 = curr.getX(1); + final float cy1 = curr.getY(1); + final float cvx = cx1 - cx0; + final float cvy = cy1 - cy0; + mCurrFingerDiffX = cvx; + mCurrFingerDiffY = cvy; + mFocus = determineFocalPoint(curr); + } + + /** + * Return the current distance between the two pointers forming the gesture + * in progress. + * + * @return Distance between pointers in pixels. + */ + public float getCurrentSpan() { + if (mCurrLen == -1) { + final float cvx = mCurrFingerDiffX; + final float cvy = mCurrFingerDiffY; + mCurrLen = (float) Math.sqrt(cvx * cvx + cvy * cvy); + } + return mCurrLen; + } + + /** + * Return the previous distance between the two pointers forming the gesture + * in progress. + * + * @return Previous distance between pointers in pixels. + */ + public float getPreviousSpan() { + if (mPrevLen == -1) { + final float pvx = mPrevFingerDiffX; + final float pvy = mPrevFingerDiffY; + mPrevLen = (float) Math.sqrt(pvx * pvx + pvy * pvy); + } + return mPrevLen; + } + + /** + * MotionEvent has no getRawX(int) method; simulate it pending future API + * approval. + * + * @param event + * @param pointerIndex + * @return + */ + protected static float getRawX(MotionEvent event, int pointerIndex) { + float offset = event.getX() - event.getRawX(); + if (pointerIndex < event.getPointerCount()) { + return event.getX(pointerIndex) + offset; + } + return 0.0f; + } + + /** + * MotionEvent has no getRawY(int) method; simulate it pending future API + * approval. + * + * @param event + * @param pointerIndex + * @return + */ + protected static float getRawY(MotionEvent event, int pointerIndex) { + float offset = event.getY() - event.getRawY(); + if (pointerIndex < event.getPointerCount()) { + return event.getY(pointerIndex) + offset; + } + return 0.0f; + } + + /** + * Check if we have a sloppy gesture. Sloppy gestures can happen if the edge + * of the user's hand is touching the screen, for example. + * + * @param event + * @return + */ + protected boolean isSloppyGesture(MotionEvent event) { + // As orientation can change, query the metrics in touch down + DisplayMetrics metrics = mContext.getResources().getDisplayMetrics(); + mRightSlopEdge = metrics.widthPixels - mEdgeSlop; + mBottomSlopEdge = metrics.heightPixels - mEdgeSlop; + + final float edgeSlop = mEdgeSlop; + final float rightSlop = mRightSlopEdge; + final float bottomSlop = mBottomSlopEdge; + + final float x0 = event.getRawX(); + final float y0 = event.getRawY(); + final float x1 = getRawX(event, 1); + final float y1 = getRawY(event, 1); + + boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop || x0 > rightSlop + || y0 > bottomSlop; + boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop || x1 > rightSlop + || y1 > bottomSlop; + + if (p0sloppy && p1sloppy) { + return true; + } else if (p0sloppy) { + return true; + } else if (p1sloppy) { + return true; + } + return false; + } + + /** + * Determine (multi)finger focal point (a.k.a. center point between all + * fingers) + * + * @param MotionEvent + * e + * @return PointF focal point + */ + public static PointF determineFocalPoint(MotionEvent e) { + // Number of fingers on screen + final int pCount = e.getPointerCount(); + float x = 0.0f; + float y = 0.0f; + + for (int i = 0; i < pCount; i++) { + x += e.getX(i); + y += e.getY(i); + } + + return new PointF(x / pCount, y / pCount); + } + + public float getFocusX() { + return mFocus.x; + } + + public float getFocusY() { + return mFocus.y; + } + +} diff --git a/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/LonLat.java b/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/LonLat.java new file mode 100644 index 00000000000..87260c2a9e1 --- /dev/null +++ b/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/LonLat.java @@ -0,0 +1,91 @@ +package com.mapbox.mapboxgl.lib; + +import android.os.Parcel; +import android.os.Parcelable; + +public class LonLat implements Parcelable { + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public LonLat createFromParcel(Parcel in) { + return new LonLat(in); + } + + public LonLat[] newArray(int size) { + return new LonLat[size]; + } + }; + + private double lon; + private double lat; + + public LonLat(double lon, double lat) { + this.lon = lon; + this.lat = lat; + } + + private LonLat(Parcel in) { + lon = in.readDouble(); + lat = in.readDouble(); + } + + public double getLon() { + return lon; + } + + public void setLon(double lon) { + this.lon = lon; + } + + public double getLat() { + return lat; + } + + public void setLat(double lat) { + this.lat = lat; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + long temp; + temp = Double.doubleToLongBits(lat); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(lon); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + LonLat other = (LonLat) obj; + if (Double.doubleToLongBits(lat) != Double.doubleToLongBits(other.lat)) + return false; + if (Double.doubleToLongBits(lon) != Double.doubleToLongBits(other.lon)) + return false; + return true; + } + + @Override + public String toString() { + return "LonLat [lon=" + lon + ", lat=" + lat + "]"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeDouble(lon); + out.writeDouble(lat); + } + +} diff --git a/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/LonLatZoom.java b/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/LonLatZoom.java new file mode 100644 index 00000000000..d20cccd47b6 --- /dev/null +++ b/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/LonLatZoom.java @@ -0,0 +1,124 @@ +package com.mapbox.mapboxgl.lib; + +import android.os.Parcel; +import android.os.Parcelable; + +public class LonLatZoom implements Parcelable { + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public LonLatZoom createFromParcel(Parcel in) { + return new LonLatZoom(in); + } + + public LonLatZoom[] newArray(int size) { + return new LonLatZoom[size]; + } + }; + + private double lon; + private double lat; + private double zoom; + + public LonLatZoom(double lon, double lat, double zoom) { + this.lon = lon; + this.lat = lat; + this.zoom = zoom; + } + + public LonLatZoom(LonLat lonLat, double zoom) { + this.lon = lonLat.getLon(); + this.lat = lonLat.getLat(); + this.zoom = zoom; + } + + private LonLatZoom(Parcel in) { + lon = in.readDouble(); + lat = in.readDouble(); + zoom = in.readDouble(); + } + + public double getLon() { + return lon; + } + + public void setLon(double lon) { + this.lon = lon; + } + + public double getLat() { + return lat; + } + + public void setLat(double lat) { + this.lat = lat; + } + + public double getZoom() { + return zoom; + } + + public void setZoom(double zoom) { + this.zoom = zoom; + } + + public LonLat getLonLat() { + return new LonLat(lon, lat); + } + + public void setLonLat(LonLat lonLat) { + this.lon = lonLat.getLon(); + this.lat = lonLat.getLat(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + long temp; + temp = Double.doubleToLongBits(lat); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(lon); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(zoom); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + LonLatZoom other = (LonLatZoom) obj; + if (Double.doubleToLongBits(lat) != Double.doubleToLongBits(other.lat)) + return false; + if (Double.doubleToLongBits(lon) != Double.doubleToLongBits(other.lon)) + return false; + if (Double.doubleToLongBits(zoom) != Double + .doubleToLongBits(other.zoom)) + return false; + return true; + } + + @Override + public String toString() { + return "LonLatZoom [lon=" + lon + ", lat=" + lat + ", zoom=" + zoom + + "]"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeDouble(lon); + out.writeDouble(lat); + out.writeDouble(zoom); + } + +} diff --git a/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/MapFragment.java b/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/MapFragment.java new file mode 100644 index 00000000000..1263ac56e89 --- /dev/null +++ b/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/MapFragment.java @@ -0,0 +1,150 @@ +package com.mapbox.mapboxgl.lib; + +import android.app.Activity; +import android.app.Fragment; +import android.content.res.TypedArray; +import android.os.Bundle; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +public class MapFragment extends Fragment { + + // + // Static members + // + + // Tag used for logging + private static final String TAG = "MapFragment"; + + // The map + private MapView mMapView; + + // The style attrs to load into the map + AttributeSet mAttrs; + + // + // Lifecycle events + // + + // Called when the fragment is created + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + super.onCreateView(inflater, container, savedInstanceState); + Log.v(TAG, "onCreateView"); + + // Create the map + mMapView = new MapView(getActivity()); + + // Load the attributes + TypedArray typedArray = getActivity().obtainStyledAttributes(mAttrs, + R.styleable.MapFragment, 0, 0); + try { + double centerLongitude = typedArray.getFloat( + R.styleable.MapFragment_centerLongitude, 0.0f); + double centerLatitude = typedArray.getFloat( + R.styleable.MapFragment_centerLatitude, 0.0f); + LonLat centerCoordinate = new LonLat(centerLongitude, + centerLatitude); + mMapView.setCenterCoordinate(centerCoordinate); + mMapView.setDirection(typedArray.getFloat( + R.styleable.MapFragment_direction, 0.0f)); + mMapView.setZoomLevel(typedArray.getFloat( + R.styleable.MapFragment_zoomLevel, 0.0f)); + mMapView.setZoomEnabled(typedArray.getBoolean( + R.styleable.MapFragment_zoomEnabled, true)); + mMapView.setScrollEnabled(typedArray.getBoolean( + R.styleable.MapFragment_scrollEnabled, true)); + mMapView.setRotateEnabled(typedArray.getBoolean( + R.styleable.MapFragment_rotateEnabled, true)); + mMapView.setDebugActive(typedArray.getBoolean( + R.styleable.MapFragment_debugActive, false)); + } finally { + typedArray.recycle(); + } + + // Need to pass on any saved state to the map + mMapView.onCreate(savedInstanceState); + + // Return the map as the root view + return mMapView; + } + + // Called when the fragment is destroyed + @Override + public void onDestroyView() { + super.onDestroyView(); + Log.v(TAG, "onDestroyView"); + + // Need to pass on to view + mMapView.onDestroy(); + mMapView = null; + } + + // Called to load any fragment attributes set in the activity's layout + @Override + public void onInflate(Activity activity, AttributeSet attrs, + Bundle savedInstanceState) { + super.onInflate(activity, attrs, savedInstanceState); + Log.v(TAG, "onInflate"); + + // TODO seems this is called before onCreateView + // Kinda strange, is it supposed to be like this? + // Need to keep a copy of these and pass on to MapView later + + mAttrs = attrs; + } + + // Called when the fragment is visible + @Override + public void onStart() { + super.onStart(); + Log.v(TAG, "onStart"); + + // Need to pass on to view + mMapView.onStart(); + } + + // Called when the fragment is invisible + @Override + public void onStop() { + super.onStop(); + Log.v(TAG, "onStop"); + + // Need to pass on to view + mMapView.onStop(); + } + + // Called when the fragment is in the background + @Override + public void onPause() { + super.onPause(); + Log.v(TAG, "onPause"); + + // Need to pass on to view + mMapView.onPause(); + } + + // Called when the fragment is no longer in the background + @Override + public void onResume() { + super.onResume(); + Log.v(TAG, "onResume"); + + // Need to pass on to view + mMapView.onResume(); + } + + // Called before fragment is destroyed + @Override + public void onSaveInstanceState(Bundle outState) { + Log.v(TAG, "onSaveInstanceState"); + + // Need to retrieve any saved state from the map + mMapView.onSaveInstanceState(outState); + super.onSaveInstanceState(outState); + } +} diff --git a/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/MapView.java b/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/MapView.java new file mode 100644 index 00000000000..0f1961d2403 --- /dev/null +++ b/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/MapView.java @@ -0,0 +1,1178 @@ +package com.mapbox.mapboxgl.lib; + +import java.io.IOException; + +import org.apache.commons.io.Charsets; +import org.apache.commons.io.IOUtils; +import org.json.JSONException; +import org.json.JSONObject; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.TypedArray; +import android.graphics.PointF; +import android.os.Bundle; +import android.util.AttributeSet; +import android.util.Log; +import android.view.GestureDetector; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.ViewConfiguration; +import android.widget.ZoomButtonsController; + +import com.almeros.android.multitouch.gesturedetectors.RotateGestureDetector; +import com.almeros.android.multitouch.gesturedetectors.TwoFingerGestureDetector; + +// Custom view that shows a Map +// Based on SurfaceView as we use OpenGL ES to render +public class MapView extends SurfaceView { + // TODO try TextureView + + // + // Static members + // + + // Tag used for logging + private static final String TAG = "MapView"; + + // Used for saving instance state + private static final String STATE_CENTER_COORDINATE = "centerCoordinate"; + private static final String STATE_CENTER_DIRECTION = "centerDirection"; + private static final String STATE_ZOOM_LEVEL = "zoomLevel"; + private static final String STATE_ZOOM_ENABLED = "zoomEnabled"; + private static final String STATE_SCROLL_ENABLED = "scrollEnabled"; + private static final String STATE_ROTATE_ENABLED = "rotateEnabled"; + private static final String STATE_DEBUG_ACTIVE = "debugActive"; + + // + // Instance members + // + + // Used to call JNI NativeMapView + private NativeMapView mNativeMapView; + + // Used to style the map + private String mDefaultStyleJSON; + + // Touch gesture detectors + private GestureDetector mGestureDetector; + private ScaleGestureDetector mScaleGestureDetector; + private RotateGestureDetector mRotateGestureDetector; + private boolean mTwoTap = false; + + // Shows zoom buttons + private ZoomButtonsController mZoomButtonsController; + + // Used to track trackball long presses + private TrackballLongPressTimeOut mCurrentTrackballLongPressTimeOut; + + // + // Properties + // + + private boolean mZoomEnabled = true; + private boolean mScrollEnabled = true; + private boolean mRotateEnabled = true; + + // + // Constructors + // + + // Called when no properties are being set from XML + public MapView(Context context) { + super(context); + initialize(context, null, 0); + } + + // Called when properties are being set from XML + public MapView(Context context, AttributeSet attrs) { + super(context, attrs); + initialize(context, attrs, 0); + } + + // Called when properties are being set from XML + public MapView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initialize(context, attrs, defStyle); + } + + // + // Initialization + // + + // Common initialization code goes here + private void initialize(Context context, AttributeSet attrs, int defStyle) { + Log.v(TAG, "initialize"); + + // Check if we are in Eclipse UI editor + if (isInEditMode()) { + // TODO editor does not load properly because + return; + } + + // Load the map style + try { + mDefaultStyleJSON = IOUtils.toString(context.getResources() + // .openRawResource(R.raw.style_leith), Charsets.UTF_8); + .openRawResource(R.raw.style), Charsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException( + "Couldn't load default style JSON resource", e); + } + + // Create the NativeMapView + mNativeMapView = new NativeMapView(this, mDefaultStyleJSON); + mNativeMapView.addDefaultSource(); + // mNativeMapView.addSource("countries", + // "http://192.168.20.103/%d/%d/%d.pbf"); + + // Load the attributes + TypedArray typedArray = context.obtainStyledAttributes(attrs, + R.styleable.MapView, 0, 0); + try { + double centerLongitude = typedArray.getFloat( + R.styleable.MapView_centerLongitude, 0.0f); + double centerLatitude = typedArray.getFloat( + R.styleable.MapView_centerLatitude, 0.0f); + LonLat centerCoordinate = new LonLat(centerLongitude, + centerLatitude); + setCenterCoordinate(centerCoordinate); + setDirection(typedArray.getFloat(R.styleable.MapView_direction, + 0.0f)); + setZoomLevel(typedArray.getFloat(R.styleable.MapView_zoomLevel, + 0.0f)); + setZoomEnabled(typedArray.getBoolean( + R.styleable.MapView_zoomEnabled, true)); + setScrollEnabled(typedArray.getBoolean( + R.styleable.MapView_scrollEnabled, true)); + setRotateEnabled(typedArray.getBoolean( + R.styleable.MapView_rotateEnabled, true)); + setDebugActive(typedArray.getBoolean( + R.styleable.MapView_debugActive, false)); + } finally { + typedArray.recycle(); + } + + // Ensure this view is interactable + setClickable(true); + setLongClickable(true); + setFocusable(true); + setFocusableInTouchMode(true); + requestFocus(); + + // Register the SurfaceHolder callbacks + getHolder().addCallback(new Callbacks()); + + // Touch gesture detectors + mGestureDetector = new GestureDetector(context, new GestureListener()); + mGestureDetector.setIsLongpressEnabled(true); + mScaleGestureDetector = new ScaleGestureDetector(context, + new ScaleGestureListener()); + mRotateGestureDetector = new RotateGestureDetector(context, + new RotateGestureListener()); + + // Shows the zoom controls + // But not when in Eclipse UI editor + if (!isInEditMode()) { + if (!context.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)) { + mZoomButtonsController = new ZoomButtonsController(this); + mZoomButtonsController.setZoomSpeed(300); + mZoomButtonsController.setOnZoomListener(new OnZoomListener()); + } + } + } + + // + // Property methods + // + + public LonLat getCenterCoordinate() { + return mNativeMapView.getLonLat(); + } + + public void setCenterCoordinate(LonLat centerCoordinate) { + setCenterCoordinate(centerCoordinate, false); + } + + public void setCenterCoordinate(LonLat centerCoordinate, boolean animated) { + double duration = animated ? 0.3 : 0.0; + mNativeMapView.setLonLat(centerCoordinate, duration); + } + + public void setCenterCoordinate(LonLatZoom centerCoordinate) { + setCenterCoordinate(centerCoordinate, false); + } + + public void setCenterCoordinate(LonLatZoom centerCoordinate, + boolean animated) { + double duration = animated ? 0.3 : 0.0; + mNativeMapView.setLonLatZoom(centerCoordinate, duration); + } + + public double getDirection() { + double direction = mNativeMapView.getAngle(); + + direction *= 180 / Math.PI; + + while (direction > 360) + direction -= 360; + while (direction < 0) + direction += 360; + + return direction; + } + + public void setDirection(double direction) { + setDirection(direction, false); + } + + public void setDirection(double direction, boolean animated) { + double duration = animated ? 0.3 : 0.0; + + direction *= Math.PI / 180; + + mNativeMapView.setAngle(direction, duration); + } + + public void resetPosition() { + mNativeMapView.resetPosition(); + } + + public void resetNorth() { + mNativeMapView.resetNorth(); + } + + public double getZoomLevel() { + return mNativeMapView.getZoom(); + } + + public void setZoomLevel(double zoomLevel) { + setZoomLevel(zoomLevel, false); + } + + public void setZoomLevel(double zoomLevel, boolean animated) { + double duration = animated ? 0.3 : 0.0; + mNativeMapView.setZoom(zoomLevel, duration); + } + + public boolean isZoomEnabled() { + return mZoomEnabled; + } + + public void setZoomEnabled(boolean zoomEnabled) { + this.mZoomEnabled = zoomEnabled; + + if ((mZoomButtonsController != null) + && (getVisibility() == View.VISIBLE) && mZoomEnabled) { + mZoomButtonsController.setVisible(true); + } + } + + public boolean isScrollEnabled() { + return mScrollEnabled; + } + + public void setScrollEnabled(boolean scrollEnabled) { + this.mScrollEnabled = scrollEnabled; + } + + public boolean isRotateEnabled() { + return mRotateEnabled; + } + + public void setRotateEnabled(boolean rotateEnabled) { + this.mRotateEnabled = rotateEnabled; + } + + public boolean isDebugActive() { + return mNativeMapView.getDebug(); + } + + public void setDebugActive(boolean debugActive) { + mNativeMapView.setDebug(debugActive); + } + + public void toggleDebug() { + mNativeMapView.toggleDebug(); + } + + // + // Style methods + // + + public void toggleStyle() { + // TODO + } + + // TODO seems like JSON simple may be better since it implements Map + // interface + // Other candidates: fastjson, json-smart, fossnova json, + public JSONObject getRawStyle() { + try { + return new JSONObject(mNativeMapView.getStyleJSON()); + } catch (JSONException e) { + e.printStackTrace(); + return null; + } + } + + public void setRawStyle(JSONObject style) { + mNativeMapView.setStyleJSON(style.toString()); + } + + public void getStyleOrderedLayerNames() { + // TODO + } + + public void setStyleOrderedLayerNames() { + // TODO + } + + public void getAllStyleClasses() { + // TODO + } + + public void getAppliedStyleClasses() { + // TODO + } + + public void setAppliedStyleClasses() { + // TODO + } + + public void getStyleDescriptionForLayer() { + // TODO + } + + public void setStyleDescription() { + // TODO + } + + // + // Lifecycle events + // + + // Called when we need to restore instance state + // Must be called from Activity onCreate + public void onCreate(Bundle savedInstanceState) { + Log.v(TAG, "onCreate"); + if (savedInstanceState != null) { + lonlat = (LonLat) savedInstanceState + .getParcelable(STATE_CENTER_COORDINATE); + angle = savedInstanceState.getDouble(STATE_CENTER_DIRECTION); + zoom = savedInstanceState.getDouble(STATE_ZOOM_LEVEL); + setCenterCoordinate((LonLat) savedInstanceState + .getParcelable(STATE_CENTER_COORDINATE)); + setDirection(savedInstanceState.getDouble(STATE_CENTER_DIRECTION)); + setZoomLevel(savedInstanceState.getDouble(STATE_ZOOM_LEVEL)); + setZoomEnabled(savedInstanceState.getBoolean(STATE_ZOOM_ENABLED)); + setScrollEnabled(savedInstanceState + .getBoolean(STATE_SCROLL_ENABLED)); + setRotateEnabled(savedInstanceState + .getBoolean(STATE_ROTATE_ENABLED)); + setDebugActive(savedInstanceState.getBoolean(STATE_DEBUG_ACTIVE)); + } + + mNativeMapView.initializeDisplay(); + } + + // Called when we need to save instance state + // Must be called from Activity onSaveInstanceState + public void onSaveInstanceState(Bundle outState) { + Log.v(TAG, "onSaveInstanceState"); + outState.putParcelable(STATE_CENTER_COORDINATE, getCenterCoordinate()); + outState.putDouble(STATE_CENTER_DIRECTION, getDirection()); + outState.putDouble(STATE_ZOOM_LEVEL, getZoomLevel()); + outState.putBoolean(STATE_ZOOM_ENABLED, isZoomEnabled()); + outState.putBoolean(STATE_SCROLL_ENABLED, isScrollEnabled()); + outState.putBoolean(STATE_ROTATE_ENABLED, isRotateEnabled()); + outState.putBoolean(STATE_DEBUG_ACTIVE, isDebugActive()); + } + + // Called when we need to clean up + // Must be called from Activity onDestroy + public void onDestroy() { + Log.v(TAG, "onDestroy"); + mNativeMapView.terminateDisplay(); + } + + // Called when we need to create the GL context + // Must be called from Activity onStart + public void onStart() { + Log.v(TAG, "onStart"); + mNativeMapView.initializeContext(); + } + + // Called when we need to terminate the GL context + // Must be called from Activity onPause + public void onStop() { + Log.v(TAG, "onStop"); + mNativeMapView.terminateContext(); + } + + // Called when we need to stop the render thread + // Must be called from Activity onPause + public void onPause() { + Log.v(TAG, "onPause"); + lonlat = mNativeMapView.getLonLat(); + angle = mNativeMapView.getAngle(); + zoom = mNativeMapView.getZoom(); + mNativeMapView.stop(); + } + + // Called when we need to start the render thread + // Must be called from Activity onResume + + // TODO need to fix this in Map C++ code + // Seems map state gets reset when we start() + private LonLat lonlat; + private double angle, zoom; + + public void onResume() { + Log.v(TAG, "onResume"); + mNativeMapView.start(); + if (lonlat != null) { + mNativeMapView.setLonLat(lonlat); + mNativeMapView.setAngle(angle); + mNativeMapView.setZoom(zoom); + } + } + + // This class handles SurfaceHolder callbacks + private class Callbacks implements SurfaceHolder.Callback2 { + + // Called when we need to redraw the view + // This is called before our view is first visible to prevent an initial + // flicker (see Android SDK documentation) + @Override + public void surfaceRedrawNeeded(SurfaceHolder holder) { + Log.v(TAG, "surfaceRedrawNeeded"); + mNativeMapView.update(); + } + + // Called when the native surface buffer has been created + // Must do all EGL/GL ES initialization here + @Override + public void surfaceCreated(SurfaceHolder holder) { + Log.v(TAG, "surfaceCreated"); + mNativeMapView.createSurface(holder.getSurface()); + if (lonlat != null) { + mNativeMapView.setLonLat(lonlat); + mNativeMapView.setAngle(angle); + mNativeMapView.setZoom(zoom); + } + } + + // Called when the native surface buffer has been destroyed + // Must do all EGL/GL ES destruction here + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + Log.v(TAG, "surfaceDestroyed"); + mNativeMapView.destroySurface(); + } + + // Called when the format or size of the native surface buffer has been + // changed + // Must handle window resizing here. + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, + int height) { + Log.v(TAG, "surfaceChanged"); + Log.i(TAG, "resize " + format + " " + width + " " + height); + mNativeMapView.resize(width, height); + // TODO fix bug when rotating - sometimes 1/2 map black + } + } + + // Called when view is no longer connected + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + // Required by ZoomButtonController (from Android SDK documentation) + if (mZoomButtonsController != null) { + mZoomButtonsController.setVisible(false); + } + } + + // Called when view is hidden and shown + @Override + protected void onVisibilityChanged(View changedView, int visibility) { + // Required by ZoomButtonController (from Android SDK documentation) + if ((mZoomButtonsController != null) && (visibility != View.VISIBLE)) { + mZoomButtonsController.setVisible(false); + } + if ((mZoomButtonsController != null) && (visibility == View.VISIBLE) + && mZoomEnabled) { + mZoomButtonsController.setVisible(true); + } + } + + // + // Draw events + // + + // TODO: onDraw for editor? + // By default it just shows a gray screen with "MapView" + // Not too important but perhaps we could put a static demo map image there + + // + // Input events + // + + // Zoom in or out + private void zoom(boolean zoomIn) { + zoom(zoomIn, -1.0f, -1.0f); + } + + private void zoom(boolean zoomIn, float x, float y) { + // Cancel any animation + mNativeMapView.cancelTransitions(); + + if (zoomIn) { + mNativeMapView.scaleBy(2.0, x, y, 0.3); + } else { + // TODO two finger tap zoom out + mNativeMapView.scaleBy(0.5, x, y, 0.3); + } + } + + // Called when user touches the screen, all positions are absolute + @Override + public boolean onTouchEvent(MotionEvent event) { + // Check and ignore non touch or left clicks + if ((event.getButtonState() != 0) + && (event.getButtonState() != MotionEvent.BUTTON_PRIMARY)) { + return false; + } + + // Check two finger gestures first + boolean rotateRetVal = false; + mRotateGestureDetector.onTouchEvent(event); + boolean scaleRetVal = false; + mScaleGestureDetector.onTouchEvent(event); + + // Handle two finger tap + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + // First pointer down + break; + + case MotionEvent.ACTION_POINTER_DOWN: + // Second pointer down + if (event.getPointerCount() == 2) { + mTwoTap = true; + } else { + mTwoTap = false; + } + break; + + case MotionEvent.ACTION_POINTER_UP: + // Second pointer up + break; + + case MotionEvent.ACTION_UP: + // First pointer up + long tapInterval = event.getEventTime() - event.getDownTime(); + boolean isTap = tapInterval <= ViewConfiguration.getTapTimeout(); + boolean inProgress = mRotateGestureDetector.isInProgress() + || mScaleGestureDetector.isInProgress(); + + if (mTwoTap && isTap && !inProgress) { + PointF focalPoint = TwoFingerGestureDetector + .determineFocalPoint(event); + zoom(false, focalPoint.x, focalPoint.y); + mTwoTap = false; + return true; + } + + mTwoTap = false; + break; + + case MotionEvent.ACTION_CANCEL: + mTwoTap = false; + break; + } + + // Do not change this code! It will break very easily. + boolean retVal = rotateRetVal || scaleRetVal; + retVal = mGestureDetector.onTouchEvent(event) || retVal; + return retVal || super.onTouchEvent(event); + } + + // This class handles one finger gestures + private class GestureListener extends + GestureDetector.SimpleOnGestureListener { + + // Must always return true otherwise all events are ignored + @Override + public boolean onDown(MotionEvent e) { + // Show the zoom controls + if ((mZoomButtonsController != null) && mZoomEnabled) { + mZoomButtonsController.setVisible(true); + } + + return true; + } + + // Called for double taps + @Override + public boolean onDoubleTap(MotionEvent e) { + if (!mZoomEnabled) { + return false; + } + + // Single finger double tap + // Zoom in + zoom(true, e.getX(), e.getY()); + return true; + } + + @Override + public boolean onSingleTapUp(MotionEvent e) { + // Cancel any animation + mNativeMapView.cancelTransitions(); + + return true; + } + + // Called for single taps after a delay + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + return false; + } + + // Called for a long press + @Override + public void onLongPress(MotionEvent e) { + // TODO + } + + // Called for flings + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, + float velocityY) { + if (!mScrollEnabled) { + return false; + } + + // Fling the map + // TODO Google Maps also has a rotate and zoom fling + // TODO does not work + /* + * float ease = 0.25f; + * + * velocityX = velocityX * ease; velocityY = velocityY * ease; + * + * double speed = Math.sqrt(velocityX * velocityX + velocityY * + * velocityY); double deceleration = 2500; double duration = speed / + * (deceleration * ease); + * + * + * // Cancel any animation mNativeMapView.cancelTransitions(); + * + * mNativeMapView.moveBy(velocityX * duration / 2.0, velocityY * + * duration / 2.0, duration); + * + * return true; + */ + return false; + } + + // Called for drags + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, + float distanceX, float distanceY) { + if (!mScrollEnabled) { + return false; + } + + // Cancel any animation + mNativeMapView.cancelTransitions(); // TODO need to test canceling + // transitions with touch + + // Scroll the map + mNativeMapView.moveBy(-distanceX, -distanceY); + return true; + } + } + + // This class handles two finger gestures + private class ScaleGestureListener extends + ScaleGestureDetector.SimpleOnScaleGestureListener { + + long mBeginTime = 0; + float mScaleFactor = 1.0f; + boolean mStarted = false; + + // Called when two fingers first touch the screen + @Override + public boolean onScaleBegin(ScaleGestureDetector detector) { + if (!mZoomEnabled) { + return false; + } + + mBeginTime = detector.getEventTime(); + + return true; + } + + // Called when fingers leave screen + @Override + public void onScaleEnd(ScaleGestureDetector detector) { + mBeginTime = 0; + mScaleFactor = 1.0f; + mStarted = false; + } + + // Called each time one of the two fingers moves + // Called for pinch zooms + @Override + public boolean onScale(ScaleGestureDetector detector) { + if (!mZoomEnabled) { + return false; + } + + // If scale is large enough ignore a tap + // TODO: Google Maps seem to use a velocity rather than absolute + // value? + mScaleFactor *= detector.getScaleFactor(); + if ((mScaleFactor > 1.05f) || (mScaleFactor < 0.95f)) { + mStarted = true; + } + + // Ignore short touches in case it is a tap + // Also ignore small scales + long time = detector.getEventTime(); + long interval = time - mBeginTime; + if (!mStarted && (interval <= ViewConfiguration.getTapTimeout())) { + return false; + } + + // TODO complex decision between roate or scale or both (see Google + // Maps app) + + // Cancel any animation + mNativeMapView.cancelTransitions(); + + // Scale the map + mNativeMapView.scaleBy(detector.getScaleFactor(), + detector.getFocusX(), detector.getFocusY()); + + return true; + } + } + + // This class handles two rotate gestures + // TODO need way to single finger rotate - need to research how google maps + // does this - for phones with single touch, or when using mouse etc + private class RotateGestureListener extends + RotateGestureDetector.SimpleOnRotateGestureListener { + + long mBeginTime = 0; + float mTotalAngle = 0.0f; + boolean mStarted = false; + + // Called when two fingers first touch the screen + @Override + public boolean onRotateBegin(RotateGestureDetector detector) { + if (!mRotateEnabled) { + return false; + } + + mBeginTime = detector.getEventTime(); + Log.d("rotate", "rotate begin"); + return true; + } + + // Called when the fingers leave the screen + @Override + public void onRotateEnd(RotateGestureDetector detector) { + mBeginTime = 0; + mTotalAngle = 0.0f; + mStarted = false; + Log.d("rotate", "rotate end"); + } + + // Called each time one of the two fingers moves + // Called for rotation + @Override + public boolean onRotate(RotateGestureDetector detector) { + if (!mRotateEnabled) { + return false; + } + + Log.d("rotate", "rotate evt"); + + // If rotate is large enough ignore a tap + // TODO: Google Maps seem to use a velocity rather than absolute + // value, up to a point then they always rotate + mTotalAngle += detector.getRotationDegreesDelta(); + Log.d("rotate", "ttl angle " + mTotalAngle); + if ((mTotalAngle > 5.0f) || (mTotalAngle < -5.0f)) { + mStarted = true; + Log.d("rotate", "rotate started"); + } + + // Ignore short touches in case it is a tap + // Also ignore small rotate + long time = detector.getEventTime(); + long interval = time - mBeginTime; + if (!mStarted && (interval <= ViewConfiguration.getTapTimeout())) { + Log.d("rotate", "rotate ignored"); + return false; + } + + // TODO complex decision between rotate or scale or both (see Google + // Maps app). It seems if you start one or the other it takes more + // to start the other too. Haven't figured out what it uses to + // decide when to transition to both at the same time. + + // Cancel any animation + mNativeMapView.cancelTransitions(); + + // Rotate the map + double angle = mNativeMapView.getAngle(); + angle -= detector.getRotationDegreesDelta() * Math.PI / 180.0; + Log.d("rotate", "rotate to " + angle); + mNativeMapView.setAngle(angle, detector.getFocusX(), + detector.getFocusY()); + + return true; + } + } + + // This class handles input events from the zoom control buttons + // Zoom controls allow single touch only devices to zoom in and out + private class OnZoomListener implements + ZoomButtonsController.OnZoomListener { + + // Not used + @Override + public void onVisibilityChanged(boolean visible) { + // Ignore + } + + // Called when user pushes a zoom button + @Override + public void onZoom(boolean zoomIn) { + if (!mZoomEnabled) { + return; + } + + // Zoom in or out + zoom(zoomIn); + } + } + + // Called when the user presses a key, also called for repeating keys held + // down + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + // If the user has held the scroll key down for a while then accelerate + // the scroll speed + double scrollDist = event.getRepeatCount() >= 5 ? 50.0 : 10.0; + + // Check which key was pressed via hardware/real key code + switch (keyCode) { + // Tell the system to track these keys for long presses on + // onKeyLongPress is fired + case KeyEvent.KEYCODE_ENTER: + case KeyEvent.KEYCODE_DPAD_CENTER: + event.startTracking(); + return true; + + case KeyEvent.KEYCODE_DPAD_LEFT: + if (!mScrollEnabled) { + return false; + } + + // Cancel any animation + mNativeMapView.cancelTransitions(); + + // Move left + mNativeMapView.moveBy(scrollDist, 0.0); + return true; + + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (!mScrollEnabled) { + return false; + } + + // Cancel any animation + mNativeMapView.cancelTransitions(); + + // Move right + mNativeMapView.moveBy(-scrollDist, 0.0); + return true; + + case KeyEvent.KEYCODE_DPAD_UP: + if (!mScrollEnabled) { + return false; + } + + // Cancel any animation + mNativeMapView.cancelTransitions(); + + // Move up + mNativeMapView.moveBy(0.0, scrollDist); + return true; + + case KeyEvent.KEYCODE_DPAD_DOWN: + if (!mScrollEnabled) { + return false; + } + + // Cancel any animation + mNativeMapView.cancelTransitions(); + + // Move down + mNativeMapView.moveBy(0.0, -scrollDist); + return true; + + default: + // We are not interested in this key + return super.onKeyUp(keyCode, event); + } + } + + // Called when the user long presses a key that is being tracked + @Override + public boolean onKeyLongPress(int keyCode, KeyEvent event) { + // Check which key was pressed via hardware/real key code + switch (keyCode) { + // Tell the system to track these keys for long presses on + // onKeyLongPress is fired + case KeyEvent.KEYCODE_ENTER: + case KeyEvent.KEYCODE_DPAD_CENTER: + if (!mZoomEnabled) { + return false; + } + + // Zoom out + zoom(false); + return true; + + default: + // We are not interested in this key + return super.onKeyUp(keyCode, event); + } + } + + // Called when the user releases a key + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + // Check if the key action was canceled (used for virtual keyboards) + if (event.isCanceled()) { + return super.onKeyUp(keyCode, event); + } + + // Check which key was pressed via hardware/real key code + // Note if keyboard does not have physical key (ie primary non-shifted + // key) then it will not appear here + // Must use the key character map as physical to character is not + // fixed/guaranteed + switch (keyCode) { + case KeyEvent.KEYCODE_ENTER: + case KeyEvent.KEYCODE_DPAD_CENTER: + if (!mZoomEnabled) { + return false; + } + + // Zoom in + zoom(true); + return true; + } + + // We are not interested in this key + return super.onKeyUp(keyCode, event); + } + + // Called for trackball events, all motions are relative in device specific + // units + // TODO: test trackball click and long click + @Override + public boolean onTrackballEvent(MotionEvent event) { + // Choose the action + switch (event.getActionMasked()) { + // The trackball was rotated + case MotionEvent.ACTION_MOVE: + if (!mScrollEnabled) { + return false; + } + + // Cancel any animation + mNativeMapView.cancelTransitions(); + + // Scroll the map + mNativeMapView.moveBy(-10.0 * event.getX(), -10.0 * event.getY()); + return true; + + // Trackball was pushed in so start tracking and tell system we are + // interested + // We will then get the up action + case MotionEvent.ACTION_DOWN: + // Set up a delayed callback to check if trackball is still + // After waiting the system long press time out + if (mCurrentTrackballLongPressTimeOut != null) { + mCurrentTrackballLongPressTimeOut.cancel(); + mCurrentTrackballLongPressTimeOut = null; + } + mCurrentTrackballLongPressTimeOut = new TrackballLongPressTimeOut(); + postDelayed(mCurrentTrackballLongPressTimeOut, + ViewConfiguration.getLongPressTimeout()); + return true; + + // Trackball was released + case MotionEvent.ACTION_UP: + if (!mZoomEnabled) { + return false; + } + + // Only handle if we have not already long pressed + if (mCurrentTrackballLongPressTimeOut != null) { + // Zoom in + zoom(true); + } + return true; + + // Trackball was cancelled + case MotionEvent.ACTION_CANCEL: + if (mCurrentTrackballLongPressTimeOut != null) { + mCurrentTrackballLongPressTimeOut.cancel(); + mCurrentTrackballLongPressTimeOut = null; + } + return true; + + default: + // We are not interested in this event + return super.onTrackballEvent(event); + } + } + + // This class implements the trackball long press time out callback + private class TrackballLongPressTimeOut implements Runnable { + + // Track if we have been cancelled + private boolean cancelled; + + public TrackballLongPressTimeOut() { + cancelled = false; + } + + // Cancel the timeoht + public void cancel() { + cancelled = true; + } + + // Called when long press time out expires + @Override + public void run() { + // Check if the trackball is still pressed + if (!cancelled) { + // Zoom out + zoom(false); + + // Ensure the up action is not run + mCurrentTrackballLongPressTimeOut = null; + } + } + } + + // Called for events that don't fit the other handlers + // such as mouse scroll events, mouse moves, joystick, trackpad + @Override + public boolean onGenericMotionEvent(MotionEvent event) { + // Mouse events + // TODO: SOURCE_TOUCH_NAVIGATION? + // TODO: source device resolution? + if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) { + // Choose the action + switch (event.getActionMasked()) { + // Mouse scrolls + case MotionEvent.ACTION_SCROLL: + if (!mZoomEnabled) { + return false; + } + + // Cancel any animation + mNativeMapView.cancelTransitions(); + + // Get the vertical scroll amount, one click = 1 + float scrollDist = event.getAxisValue(MotionEvent.AXIS_VSCROLL); + + // Scale the map by the appropriate power of two factor + mNativeMapView.scaleBy(Math.pow(2.0, scrollDist), event.getX(), + event.getY()); + + return true; + + default: + // We are not interested in this event + return super.onGenericMotionEvent(event); + } + } + + // We are not interested in this event + return super.onGenericMotionEvent(event); + } + + // Called when the mouse pointer enters or exits the view + // or when it fades in or out due to movement + @Override + public boolean onHoverEvent(MotionEvent event) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_HOVER_ENTER: + case MotionEvent.ACTION_HOVER_MOVE: + // Show the zoom controls + if ((mZoomButtonsController != null) && mZoomEnabled) { + mZoomButtonsController.setVisible(true); + } + return true; + + case MotionEvent.ACTION_HOVER_EXIT: + // Hide the zoom controls + if (mZoomButtonsController != null) { + mZoomButtonsController.setVisible(false); + } + + default: + // We are not interested in this event + return super.onHoverEvent(event); + } + } + + // + // Accessibility events + // + + // + // Map events + // + + public interface OnMapChangedListener { + void onMapChanged(); + } + + private OnMapChangedListener mOnMapChangedListener; + + // Adds a listener for onMapChanged + public void setOnMapChangedListener(OnMapChangedListener listener) { + mOnMapChangedListener = listener; + } + + // Called when the map view transformation has changed + // Called via JNI from NativeMapView + // Need to update anything that relies on map state + protected void onMapChanged() { + Log.v(TAG, "onMapChanged"); + if (mOnMapChangedListener != null) { + mOnMapChangedListener.onMapChanged(); + } + } +} diff --git a/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/NativeMapView.java b/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/NativeMapView.java new file mode 100644 index 00000000000..468d4713d46 --- /dev/null +++ b/android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/NativeMapView.java @@ -0,0 +1,501 @@ +package com.mapbox.mapboxgl.lib; + +import java.util.Set; + +import android.util.Log; +import android.view.Surface; + +// Class that wraps the native methods for convenience +class NativeMapView { + + // + // Static members + // + + // Tag used for logging + private static final String TAG = "NativeMapView"; + + // + // Instance members + // + + // Holds the pointer to JNI NativeMapView + private long mNativeMapViewPtr = 0; + + // Used for callbacks + private MapView mMapView; + + // + // Static methods + // + + static { + System.loadLibrary("mapbox-gl"); + } + + // + // Constructors + // + + public NativeMapView(MapView mapView, String defaultStyleJSON) { + mMapView = mapView; + + // Create the NativeMapView + mNativeMapViewPtr = nativeCreate(defaultStyleJSON); + } + + // + // Methods + // + + public void initializeDisplay() { + nativeInitializeDisplay(mNativeMapViewPtr); + } + + public void terminateDisplay() { + nativeTerminateDisplay(mNativeMapViewPtr); + } + + public void initializeContext() { + nativeInitializeContext(mNativeMapViewPtr); + } + + public void terminateContext() { + nativeTerminateContext(mNativeMapViewPtr); + } + + public void createSurface(Surface surface) { + nativeCreateSurface(mNativeMapViewPtr, surface); + } + + public void destroySurface() { + nativeDestroySurface(mNativeMapViewPtr); + } + + public void start() { + nativeStart(mNativeMapViewPtr); + } + + public void stop() { + nativeStop(mNativeMapViewPtr); + } + + public void rerender() { + nativeRerender(mNativeMapViewPtr); + } + + public void update() { + nativeUpdate(mNativeMapViewPtr); + } + + public void cleanup() { + nativeCleanup(mNativeMapViewPtr); + } + + public void addDefaultSource() { + nativeAddDefaultSource(mNativeMapViewPtr); + } + + public void removeDefaultSource() { + nativeRemoveDefaultSource(mNativeMapViewPtr); + } + + public void addSource(String name, String url) { + nativeAddSource(mNativeMapViewPtr, name, url); + } + + public void removeSource(String name) { + nativeRemoveSource(mNativeMapViewPtr, name); + } + + public void resize(int width, int height) { + resize(width, height, 1.0f); + } + + public void resize(int width, int height, float ratio) { + if (width < 0) { + throw new IllegalArgumentException("width cannot be negative."); + } + + if (height < 0) { + throw new IllegalArgumentException("height cannot be negative."); + } + + if (width > 65535) { + throw new IllegalArgumentException( + "width cannot be greater than 65535."); + } + + if (height > 65535) { + throw new IllegalArgumentException( + "height cannot be greater than 65535."); + } + + nativeResize(mNativeMapViewPtr, width, height, ratio); + } + + public void resize(int width, int height, float ratio, int fbWidth, + int fbHeight) { + if (width < 0) { + throw new IllegalArgumentException("width cannot be negative."); + } + + if (height < 0) { + throw new IllegalArgumentException("height cannot be negative."); + } + + if (fbWidth < 0) { + throw new IllegalArgumentException("fbWidth cannot be negative."); + } + + if (fbHeight < 0) { + throw new IllegalArgumentException("fbHeight cannot be negative."); + } + + if (fbWidth > 65535) { + throw new IllegalArgumentException( + "fbWidth cannot be greater than 65535."); + } + + if (fbHeight > 65535) { + throw new IllegalArgumentException( + "fbHeight cannot be greater than 65535."); + } + nativeResize(mNativeMapViewPtr, width, height, ratio, fbWidth, fbHeight); + } + + public void setAppliedClasses(Set appliedClasses) { + nativeSetAppliedClasses(mNativeMapViewPtr, appliedClasses); + } + + public Set getAppliedClasses() { + return nativeGetAppliedClasses(mNativeMapViewPtr); + } + + public void setDefaultTransitionDuration() { + setDefaultTransitionDuration(0); + } + + public void setDefaultTransitionDuration(long durationMilliseconds) { + if (durationMilliseconds < 0) { + throw new IllegalArgumentException( + "durationMilliseconds cannot be negative."); + } + + nativeSetDefaultTransitionDuration(mNativeMapViewPtr, + durationMilliseconds); + } + + public void setStyleJSON(String newStyleJSON) { + nativeSetStyleJSON(mNativeMapViewPtr, newStyleJSON); + } + + public String getStyleJSON() { + return nativeGetStyleJSON(mNativeMapViewPtr); + } + + public void cancelTransitions() { + nativeCancelTransitions(mNativeMapViewPtr); + } + + public void moveBy(double dx, double dy) { + moveBy(dx, dy, 0.0); + } + + public void moveBy(double dx, double dy, double duration) { + nativeMoveBy(mNativeMapViewPtr, dx, dy, duration); + } + + public void setLonLat(LonLat lonLat) { + setLonLat(lonLat, 0.0); + } + + public void setLonLat(LonLat lonLat, double duration) { + nativeSetLonLat(mNativeMapViewPtr, lonLat, duration); + } + + public LonLat getLonLat() { + return nativeGetLonLat(mNativeMapViewPtr); + } + + public void startPanning() { + nativeStartPanning(mNativeMapViewPtr); + } + + public void stopPanning() { + nativeStopPanning(mNativeMapViewPtr); + } + + public void resetPosition() { + nativeResetPosition(mNativeMapViewPtr); + } + + public void scaleBy(double ds) { + scaleBy(ds, -1.0, -1.0); + } + + public void scaleBy(double ds, double cx, double cy) { + scaleBy(ds, cx, cy, 0.0); + } + + public void scaleBy(double ds, double cx, double cy, double duration) { + nativeScaleBy(mNativeMapViewPtr, ds, cx, cy, duration); + } + + public void setScale(double scale) { + setScale(scale, -1.0, -1.0); + } + + public void setScale(double scale, double cx, double cy) { + setScale(scale, cx, cy, 0.0); + } + + public void setScale(double scale, double cx, double cy, double duration) { + nativeSetScale(mNativeMapViewPtr, scale, cx, cy, duration); + } + + public double getScale() { + return nativeGetScale(mNativeMapViewPtr); + } + + public void setZoom(double zoom) { + setZoom(zoom, 0.0); + } + + public void setZoom(double zoom, double duration) { + nativeSetZoom(mNativeMapViewPtr, zoom, duration); + } + + public double getZoom() { + return nativeGetZoom(mNativeMapViewPtr); + } + + public void setLonLatZoom(LonLatZoom lonLatZoom) { + setLonLatZoom(lonLatZoom, 0.0); + } + + public void setLonLatZoom(LonLatZoom lonLatZoom, double duration) { + nativeSetLonLatZoom(mNativeMapViewPtr, lonLatZoom, duration); + } + + public LonLatZoom getLonLatZoom() { + return nativeGetLonLatZoom(mNativeMapViewPtr); + } + + public void resetZoom() { + nativeResetZoom(mNativeMapViewPtr); + } + + public void startScaling() { + nativeStartScaling(mNativeMapViewPtr); + } + + public void stopScaling() { + nativeStopScaling(mNativeMapViewPtr); + } + + public double getMinZoom() { + return nativeGetMinZoom(mNativeMapViewPtr); + } + + public double getMaxZoom() { + return nativeGetMaxZoom(mNativeMapViewPtr); + } + + public void rotateBy(double sx, double sy, double ex, double ey) { + rotateBy(sx, sy, ex, ey, 0.0); + } + + public void rotateBy(double sx, double sy, double ex, double ey, + double duration) { + nativeRotateBy(mNativeMapViewPtr, sx, sy, ex, ey, duration); + } + + public void setAngle(double angle) { + setAngle(angle, 0.0); + } + + public void setAngle(double angle, double duration) { + nativeSetAngle(mNativeMapViewPtr, angle, duration); + } + + public void setAngle(double angle, double cx, double cy) { + nativeSetAngle(mNativeMapViewPtr, angle, cx, cy); + } + + public double getAngle() { + return nativeGetAngle(mNativeMapViewPtr); + } + + public void resetNorth() { + nativeResetNorth(mNativeMapViewPtr); + } + + public void startRotating() { + nativeStartRotating(mNativeMapViewPtr); + } + + public void stopRotating() { + nativeStopRotating(mNativeMapViewPtr); + } + + public boolean canRotate() { + return nativeCanRotate(mNativeMapViewPtr); + } + + public void setDebug(boolean debug) { + nativeSetDebug(mNativeMapViewPtr, debug); + } + + public void toggleDebug() { + nativeToggleDebug(mNativeMapViewPtr); + } + + public boolean getDebug() { + return nativeGetDebug(mNativeMapViewPtr); + } + + // + // Callbacks + // + + protected void onMapChanged() { + Log.v(TAG, "onMapChanged"); + mMapView.onMapChanged(); + } + + // + // JNI methods + // + + @Override + protected void finalize() throws Throwable { + nativeDestroy(mNativeMapViewPtr); + mNativeMapViewPtr = 0; + super.finalize(); + } + + private native long nativeCreate(String defaultStyleJSON); + + private native void nativeDestroy(long nativeMapViewPtr); + + private native void nativeInitializeDisplay(long nativeMapViewPtr); + + private native void nativeTerminateDisplay(long nativeMapViewPtr); + + private native void nativeInitializeContext(long nativeMapViewPtr); + + private native void nativeTerminateContext(long nativeMapViewPtr); + + private native void nativeCreateSurface(long nativeMapViewPtr, + Surface surface); + + private native void nativeDestroySurface(long nativeMapViewPtr); + + private native void nativeStart(long nativeMapViewPtr); + + private native void nativeStop(long nativeMapViewPtr); + + private native void nativeRerender(long nativeMapViewPtr); + + private native void nativeUpdate(long nativeMapViewPtr); + + private native void nativeCleanup(long nativeMapViewPtr); + + private native void nativeAddDefaultSource(long nativeMapViewPtr); + + private native void nativeRemoveDefaultSource(long nativeMapViewPtr); + + private native void nativeAddSource(long nativeMapViewPtr, String name, + String url); + + private native void nativeRemoveSource(long nativeMapViewPtr, String name); + + private native void nativeResize(long nativeMapViewPtr, int width, + int height, float ratio); + + private native void nativeResize(long nativeMapViewPtr, int width, + int height, float ratio, int fbWidth, int fbHeight); + + private native void nativeSetAppliedClasses(long nativeMapViewPtr, + Set appliedClasses); + + private native Set nativeGetAppliedClasses(long nativeMapViewPtr); + + private native void nativeSetDefaultTransitionDuration( + long nativeMapViewPtr, long durationMilliseconds); + + private native void nativeSetStyleJSON(long nativeMapViewPtr, + String newStyleJSON); + + private native String nativeGetStyleJSON(long nativeMapViewPtr); + + private native void nativeCancelTransitions(long nativeMapViewPtr); + + private native void nativeMoveBy(long nativeMapViewPtr, double dx, + double dy, double duration); + + private native void nativeSetLonLat(long nativeMapViewPtr, LonLat lonLat, + double duration); + + private native LonLat nativeGetLonLat(long nativeMapViewPtr); + + private native void nativeStartPanning(long nativeMapViewPtr); + + private native void nativeStopPanning(long nativeMapViewPtr); + + private native void nativeResetPosition(long nativeMapViewPtr); + + private native void nativeScaleBy(long nativeMapViewPtr, double ds, + double cx, double cy, double duration); + + private native void nativeSetScale(long nativeMapViewPtr, double scale, + double cx, double cy, double duration); + + private native double nativeGetScale(long nativeMapViewPtr); + + private native void nativeSetZoom(long nativeMapViewPtr, double zoom, + double duration); + + private native double nativeGetZoom(long nativeMapViewPtr); + + private native void nativeSetLonLatZoom(long nativeMapViewPtr, + LonLatZoom lonLatZoom, double duration); + + private native LonLatZoom nativeGetLonLatZoom(long nativeMapViewPtr); + + private native void nativeResetZoom(long nativeMapViewPtr); + + private native void nativeStartScaling(long nativeMapViewPtr); + + private native void nativeStopScaling(long nativeMapViewPtr); + + private native double nativeGetMinZoom(long nativeMapViewPtr); + + private native double nativeGetMaxZoom(long nativeMapViewPtr); + + private native void nativeRotateBy(long nativeMapViewPtr, double sx, + double sy, double ex, double ey, double duration); + + private native void nativeSetAngle(long nativeMapViewPtr, double angle, + double duration); + + private native void nativeSetAngle(long nativeMapViewPtr, double angle, + double cx, double cy); + + private native double nativeGetAngle(long nativeMapViewPtr); + + private native void nativeResetNorth(long nativeMapViewPtr); + + private native void nativeStartRotating(long nativeMapViewPtr); + + private native void nativeStopRotating(long nativeMapViewPtr); + + private native boolean nativeCanRotate(long nativeMapViewPtr); + + private native void nativeSetDebug(long nativeMapViewPtr, boolean debug); + + private native void nativeToggleDebug(long nativeMapViewPtr); + + private native boolean nativeGetDebug(long nativeMapViewPtr); +} diff --git a/android/java/lib/src/main/res/raw/style.min.js b/android/java/lib/src/main/res/raw/style.min.js new file mode 100644 index 00000000000..906bec14f31 --- /dev/null +++ b/android/java/lib/src/main/res/raw/style.min.js @@ -0,0 +1 @@ +{"buckets":{"admin_maritime":{"source":"outdoors","layer":"admin","field":"maritime","value":1,"join":"round","cap":"round","type":"line"},"admin_l2":{"source":"outdoors","layer":"admin","filter":["and",{"field":"admin_level","value":2},{"field":"maritime","operator":"not","value":1}],"join":"round","cap":"round","type":"line"},"admin_l3":{"source":"outdoors","layer":"admin","filter":["and",{"field":"admin_level","value":[3,4,5]},{"field":"maritime","operator":"not","value":1}],"join":"round","type":"line"},"landcover_wood":{"source":"outdoors","layer":"landcover","field":"class","value":"wood","type":"fill"},"landcover_scrub":{"source":"outdoors","layer":"landcover","field":"class","value":"scrub","type":"fill"},"landcover_grass":{"source":"outdoors","layer":"landcover","field":"class","value":"grass","type":"fill"},"landcover_crop":{"source":"outdoors","layer":"landcover","field":"class","value":"crop","type":"fill"},"landcover_snow":{"source":"outdoors","layer":"landcover","field":"class","value":"snow","type":"fill"},"water":{"source":"outdoors","layer":"water","type":"fill"},"waterway_other":{"source":"outdoors","layer":"waterway","field":"type","value":["ditch","drain"],"cap":"round","type":"line"},"waterway_river_canal":{"source":"outdoors","layer":"waterway","field":"type","value":["river","canal"],"cap":"round","type":"line"},"waterway_stream":{"source":"outdoors","layer":"waterway","field":"type","value":"stream","cap":"round","type":"line"},"landuse_park":{"source":"outdoors","layer":"landuse","field":"class","value":"park","type":"fill"},"landuse_pitch":{"source":"outdoors","layer":"landuse","field":"class","value":"pitch","type":"fill"},"landuse_cemetery":{"source":"outdoors","layer":"landuse","field":"class","value":"cemetery","type":"fill"},"landuse_hospital":{"source":"outdoors","layer":"landuse","field":"class","value":"hospital","type":"fill"},"landuse_industrial":{"source":"outdoors","layer":"landuse","type":"fill","field":"class","value":"industrial"},"landuse_school":{"source":"outdoors","layer":"landuse","field":"class","value":"school","type":"fill"},"landuse_wood":{"source":"outdoors","layer":"landuse","field":"class","value":"wood","type":"fill"},"landuse_scrub":{"source":"outdoors","layer":"landuse","field":"class","value":"scrub","type":"fill"},"landuse_grass":{"source":"outdoors","layer":"landuse","field":"class","value":"grass","type":"fill"},"landuse_crop":{"source":"outdoors","layer":"landuse","field":"class","value":"crop","type":"fill"},"landuse_sand":{"source":"outdoors","layer":"landuse","field":"class","value":"sand","type":"fill"},"landuse_rock":{"source":"outdoors","layer":"landuse","field":"class","value":"rock","type":"fill"},"landuse_snow":{"source":"outdoors","layer":"landuse","field":"class","value":"snow","type":"fill"},"overlay_wetland":{"source":"outdoors","layer":"landuse_overlay","field":"class","value":["wetland","wetland_noveg"],"type":"fill"},"overlay_breakwater_pier":{"source":"outdoors","layer":"landuse_overlay","field":"class","value":["breakwater","pier"],"type":"fill"},"hillshade_full_shadow":{"source":"outdoors","layer":"hillshade","field":"class","value":"full_shadow","type":"fill"},"hillshade_medium_shadow":{"source":"outdoors","layer":"hillshade","field":"class","value":"medium_shadow","type":"fill"},"hillshade_medium_highlight":{"source":"outdoors","layer":"hillshade","field":"class","value":"medium_highlight","type":"fill"},"hillshade_full_highlight":{"source":"outdoors","layer":"hillshade","field":"class","value":"full_highlight","type":"fill"},"contour_line_5":{"source":"outdoors","layer":"contour","field":"index","join":"round","value":5,"type":"line"},"contour_line_10":{"source":"outdoors","layer":"contour","field":"index","join":"round","value":10,"type":"line"},"contour_line_other":{"source":"outdoors","layer":"contour","join":"round","type":"line"},"contour_label":{"source":"outdoors","layer":"contour","filter":["and",{"field":"index","value":[5,10]},{"field":"ele","operator":"not","value":0}],"path":"curve","text_field":"{{ele}} m","font":"Open Sans Regular, Arial Unicode MS Regular","fontSize":10,"feature_type":"line","type":"text","maxAngleDelta":0.5},"building":{"source":"outdoors","layer":"building","type":"fill"},"barrier_line_gate":{"source":"outdoors","layer":"barrier_line","field":"class","value":"gate","type":"line"},"barrier_line_fence":{"source":"outdoors","layer":"barrier_line","field":"class","value":"fence","type":"line"},"barrier_line_hedge":{"source":"outdoors","layer":"barrier_line","field":"class","value":"hedge","type":"line"},"barrier_line_land":{"source":"outdoors","layer":"barrier_line","field":"class","value":"land","type":"line"},"barrier_line_land_fill":{"source":"outdoors","layer":"barrier_line","field":"class","value":"land","type":"fill"},"barrier_line_cliff":{"source":"outdoors","layer":"barrier_line","field":"class","value":"cliff","type":"line"},"aeroway_fill":{"source":"outdoors","layer":"aeroway","type":"fill","enabled":12},"aeroway_runway":{"source":"outdoors","layer":"aeroway","field":"type","value":"runway","type":"line","enabled":12},"aeroway_taxiway":{"source":"outdoors","layer":"aeroway","field":"type","value":"taxiway","type":"line","enabled":12},"motorway":{"source":"outdoors","layer":"road","field":"class","value":"motorway","join":"round","cap":"round","type":"line"},"motorway_link":{"source":"outdoors","layer":"road","field":"class","value":"motorway_link","join":"round","cap":"round","type":"line"},"main":{"source":"outdoors","layer":"road","field":"class","value":"main","join":"round","cap":"round","type":"line"},"street":{"source":"outdoors","layer":"road","field":"class","value":["street","street_limited"],"join":"round","cap":"round","type":"line","enabled":12},"service":{"source":"outdoors","layer":"road","field":"class","value":"service","join":"round","cap":"round","type":"line","enabled":15},"path":{"source":"outdoors","layer":"road","field":"class","value":"path","type":"line"},"path_footway":{"source":"outdoors","layer":"road","field":"type","value":"footway","type":"line"},"path_path":{"source":"outdoors","layer":"road","field":"type","value":"path","type":"line"},"path_cycleway":{"source":"outdoors","layer":"road","field":"type","value":"cycleway","type":"line"},"path_mtb":{"source":"outdoors","layer":"road","field":"type","value":"mtb","type":"line"},"path_steps":{"source":"outdoors","layer":"road","field":"type","value":"steps","type":"line"},"path_piste":{"source":"outdoors","layer":"road","field":"type","value":"piste","type":"line"},"major_rail":{"source":"outdoors","layer":"road","field":"class","value":"major_rail","type":"line"},"tunnel_motorway":{"source":"outdoors","layer":"tunnel","field":"class","value":"motorway","type":"line"},"tunnel_motorway_link":{"source":"outdoors","layer":"tunnel","field":"class","value":"motorway_link","type":"line"},"tunnel_main":{"source":"outdoors","layer":"tunnel","field":"class","value":"main","type":"line"},"tunnel_street":{"source":"outdoors","layer":"tunnel","field":"class","value":["street","street_limited"],"type":"line","enabled":12},"tunnel_service":{"source":"outdoors","layer":"tunnel","field":"class","value":"service","type":"line","enabled":15},"tunnel_path":{"source":"outdoors","layer":"tunnel","field":"class","value":"path","type":"line"},"tunnel_path_footway":{"source":"outdoors","layer":"tunnel","field":"type","value":"footway","type":"line"},"tunnel_path_path":{"source":"outdoors","layer":"tunnel","field":"type","value":"path","type":"line"},"tunnel_path_cycleway":{"source":"outdoors","layer":"tunnel","field":"type","value":"cycleway","type":"line"},"tunnel_path_mtb":{"source":"outdoors","layer":"tunnel","field":"type","value":"mtb","type":"line"},"tunnel_path_steps":{"source":"outdoors","layer":"tunnel","field":"type","value":"steps","type":"line"},"tunnel_path_piste":{"source":"outdoors","layer":"tunnel","field":"type","value":"piste","type":"line"},"tunnel_major_rail":{"source":"outdoors","layer":"tunnel","field":"class","value":"major_rail","type":"line"},"bridge_motorway":{"source":"outdoors","layer":"bridge","field":"class","value":"motorway","type":"line"},"bridge_motorway_link":{"source":"outdoors","layer":"bridge","field":"class","value":"motorway_link","type":"line"},"bridge_main":{"source":"outdoors","layer":"bridge","field":"class","value":"main","type":"line"},"bridge_street":{"source":"outdoors","layer":"bridge","field":"class","value":["street","street_limited"],"type":"line","enabled":12},"bridge_service":{"source":"outdoors","layer":"bridge","field":"class","value":"service","type":"line","enabled":15},"bridge_path":{"source":"outdoors","layer":"bridge","type":"line"},"bridge_path_footway":{"source":"outdoors","layer":"bridge","field":"type","value":"footway","type":"line"},"bridge_path_path":{"source":"outdoors","layer":"bridge","field":"type","value":"path","type":"line"},"bridge_path_cycleway":{"source":"outdoors","layer":"bridge","field":"type","value":"cycleway","type":"line"},"bridge_path_mtb":{"source":"outdoors","layer":"bridge","field":"type","value":"mtb","type":"line"},"bridge_path_steps":{"source":"outdoors","layer":"bridge","field":"type","value":"steps","type":"line"},"bridge_path_piste":{"source":"outdoors","layer":"bridge","field":"type","value":"piste","type":"line"},"bridge_major_rail":{"source":"outdoors","layer":"bridge","field":"class","value":"major_rail","type":"line"},"bridge_aerialway":{"source":"outdoors","layer":"bridge","field":"class","value":"aerialway","type":"line"},"country_label":{"source":"outdoors","layer":"country_label","text_field":"{{name_en}}","path":"horizontal","font":"Open Sans Semibold, Arial Unicode MS Bold","fontSize":24,"feature_type":"point","type":"text","maxWidth":5},"country_label_line":{"source":"outdoors","layer":"country_label_line","type":"line"},"marine_label_line_1":{"source":"outdoors","layer":"marine_label","feature_type":"line","type":"text","field":"labelrank","value":1,"text_field":"{{name_en}}","path":"curve","font":"Open Sans Semibold Italic, Arial Unicode MS Bold","fontSize":30,"letterSpacing":0.4,"maxAngleDelta":0.5},"marine_label_line_2":{"source":"outdoors","layer":"marine_label","feature_type":"line","field":"labelrank","value":2,"type":"text","text_field":"{{name_en}}","path":"curve","font":"Open Sans Semibold Italic, Arial Unicode MS Bold","fontSize":24,"letterSpacing":0.2,"maxAngleDelta":0.5},"marine_label_line_3":{"source":"outdoors","layer":"marine_label","feature_type":"line","type":"text","field":"labelrank","value":3,"text_field":"{{name_en}}","path":"curve","font":"Open Sans Semibold Italic, Arial Unicode MS Bold","fontSize":18,"letterSpacing":0.1,"maxAngleDelta":0.5},"marine_label_line_other":{"source":"outdoors","layer":"marine_label","feature_type":"line","type":"text","field":"labelrank","value":[4,5,6],"text_field":"{{name_en}}","path":"curve","font":"Open Sans Semibold Italic, Arial Unicode MS Bold","fontSize":16,"letterSpacing":0.1,"maxAngleDelta":0.5},"marine_label_point_1":{"source":"outdoors","layer":"marine_label","feature_type":"point","type":"text","text_field":"{{name_en}}","path":"horizontal","field":"labelrank","value":1,"font":"Open Sans Semibold Italic, Arial Unicode MS Bold","fontSize":30,"maxWidth":8,"letterSpacing":0.4,"lineHeight":2},"marine_label_point_2":{"source":"outdoors","layer":"marine_label","feature_type":"point","type":"text","text_field":"{{name_en}}","path":"horizontal","field":"labelrank","value":2,"font":"Open Sans Semibold Italic, Arial Unicode MS Bold","fontSize":24,"maxWidth":8,"letterSpacing":0.2,"lineHeight":1.5},"marine_label_point_3":{"source":"outdoors","layer":"marine_label","feature_type":"point","type":"text","text_field":"{{name_en}}","path":"horizontal","field":"labelrank","value":3,"font":"Open Sans Semibold Italic, Arial Unicode MS Bold","fontSize":18,"maxWidth":8,"letterSpacing":0.1,"lineHeight":1.3},"marine_label_point_other":{"source":"outdoors","layer":"marine_label","feature_type":"point","type":"text","text_field":"{{name_en}}","path":"horizontal","field":"labelrank","value":[4,5,6],"font":"Open Sans Semibold Italic, Arial Unicode MS Bold","fontSize":16,"maxWidth":8,"letterSpacing":0.1,"lineHeight":1.2},"state_label":{"source":"outdoors","layer":"state_label","text_field":"{{name_en}}","path":"horizontal","font":"Open Sans Regular, Arial Unicode MS Regular","fontSize":16,"feature_type":"point","type":"text","enabled":4,"maxWidth":8},"place_label_city":{"source":"outdoors","layer":"place_label","field":"type","value":"city","text_field":"{{name_en}}","path":"horizontal","font":"Open Sans Semibold, Arial Unicode MS Bold","fontSize":20,"feature_type":"point","type":"text","maxWidth":8},"place_label_town":{"source":"outdoors","layer":"place_label","field":"type","value":"town","text_field":"{{name_en}}","path":"horizontal","font":"Open Sans Semibold, Arial Unicode MS Bold","fontSize":24,"feature_type":"point","type":"text","maxWidth":8},"place_label_village":{"source":"outdoors","layer":"place_label","field":"type","value":"village","text_field":"{{name_en}}","path":"horizontal","font":"Open Sans Semibold, Arial Unicode MS Bold","fontSize":22,"feature_type":"point","type":"text","maxWidth":8},"place_label_other":{"source":"outdoors","layer":"place_label","field":"type","value":["hamlet","suburb","neighbourhood"],"text_field":"{{name_en}}","path":"horizontal","font":"Open Sans Semibold, Arial Unicode MS Bold","fontSize":18,"maxWidth":6,"feature_type":"point","type":"text"},"road_label_1":{"source":"outdoors","layer":"road_label","field":"class","value":["motorway","main"],"text_field":"{{name_en}}","path":"curve","padding":2,"font":"Open Sans Regular, Arial Unicode MS Regular","fontSize":18,"feature_type":"line","type":"text","maxAngleDelta":0.5},"road_label_2":{"source":"outdoors","layer":"road_label","field":"class","value":["street","street_limited"],"text_field":"{{name_en}}","path":"curve","padding":2,"font":"Open Sans Regular, Arial Unicode MS Regular","fontSize":16,"feature_type":"line","type":"text","maxAngleDelta":0.5},"road_label_3":{"source":"outdoors","layer":"road_label","field":"class","value":["service","driveway","path"],"text_field":"{{name_en}}","path":"curve","padding":2,"font":"Open Sans Regular, Arial Unicode MS Regular","fontSize":14,"feature_type":"line","type":"text","maxAngleDelta":0.5},"water_label":{"source":"outdoors","layer":"water_label","text_field":"{{name_en}}","path":"horizontal","font":"Open Sans Semibold Italic, Arial Unicode MS Bold","fontSize":12,"feature_type":"point","type":"text","maxWidth":8},"waterway_label":{"source":"outdoors","layer":"waterway_label","text_field":"{{name_en}}","path":"curve","font":"Open Sans Semibold Italic, Arial Unicode MS Bold","fontSize":12,"feature_type":"line","type":"text","maxAngleDelta":0.5},"poi":{"source":"outdoors","layer":"poi_label","icon":"{{maki}}","field":"scalerank","value":[1,2],"size":12,"type":"point"},"poi_3":{"source":"outdoors","layer":"poi_label","icon":"{{maki}}","field":"scalerank","value":3,"size":12,"type":"point"},"poi_4_4":{"source":"outdoors","layer":"poi_label","icon":"{{maki}}","filter":["and",{"field":"scalerank","value":4},{"field":"localrank","operator":"<=","value":4}],"size":12,"type":"point"},"poi_4_16":{"source":"outdoors","layer":"poi_label","icon":"{{maki}}","filter":["and",{"field":"scalerank","value":4},{"field":"localrank","operator":"<=","value":16},{"field":"localrank","operator":">","value":4}],"size":12,"type":"point"},"poi_4_all":{"source":"outdoors","layer":"poi_label","icon":"{{maki}}","field":"scalerank","value":4,"size":12,"type":"point"},"poi_aerodrome":{"source":"outdoors","layer":"poi_label","icon":"{{maki}}","field":"maki","value":"airport","size":24,"type":"point"},"poi_label_1-2":{"source":"outdoors","layer":"poi_label","field":"scalerank","value":[1,2],"text_field":"{{name_en}}","path":"horizontal","padding":2,"maxWidth":10,"verticalAlignment":"top","translate":[0,-1],"font":"Open Sans Semibold, Arial Unicode MS Bold","fontSize":12,"feature_type":"point","type":"text"},"poi_label_3":{"source":"outdoors","layer":"poi_label","field":"scalerank","value":3,"text_field":"{{name_en}}","path":"horizontal","padding":2,"maxWidth":8,"verticalAlignment":"top","translate":[0,-1],"font":"Open Sans Semibold, Arial Unicode MS Bold","fontSize":11,"feature_type":"point","type":"text"},"poi_label_4_4":{"source":"outdoors","layer":"poi_label","filter":["and",{"field":"scalerank","value":4},{"field":"localrank","operator":"<=","value":4}],"text_field":"{{name_en}}","path":"horizontal","padding":2,"maxWidth":8,"verticalAlignment":"top","translate":[0,-1],"font":"Open Sans Semibold, Arial Unicode MS Bold","fontSize":10,"feature_type":"point","type":"text"},"poi_label_4_16":{"source":"outdoors","layer":"poi_label","filter":["and",{"field":"scalerank","value":4},{"field":"localrank","operator":"<=","value":16}],"text_field":"{{name_en}}","path":"horizontal","padding":2,"maxWidth":8,"verticalAlignment":"top","translate":[0,-1],"font":"Open Sans Semibold, Arial Unicode MS Bold","fontSize":10,"feature_type":"point","type":"text"},"poi_label_4_all":{"source":"outdoors","layer":"poi_label","field":"scalerank","value":4,"text_field":"{{name_en}}","path":"horizontal","padding":2,"maxWidth":8,"verticalAlignment":"top","translate":[0,-1],"font":"Open Sans Semibold, Arial Unicode MS Bold","fontSize":10,"feature_type":"point","type":"text"}},"structure":[{"name":"background","bucket":"background"},{"name":"landcover_snow","bucket":"landcover_snow"},{"name":"landcover_crop","bucket":"landcover_crop"},{"name":"landcover_grass","bucket":"landcover_grass"},{"name":"landcover_scrub","bucket":"landcover_scrub"},{"name":"landcover_wood","bucket":"landcover_wood"},{"name":"landuse_wood","bucket":"landuse_wood"},{"name":"landuse_school","bucket":"landuse_school"},{"name":"landuse_sand","bucket":"landuse_sand"},{"name":"landuse_pitch","bucket":"landuse_pitch"},{"name":"landuse_park","bucket":"landuse_park"},{"name":"landuse_industrial","bucket":"landuse_industrial"},{"name":"landuse_scrub","bucket":"landuse_scrub"},{"name":"landuse_grass","bucket":"landuse_grass"},{"name":"landuse_crop","bucket":"landuse_crop"},{"name":"landuse_rock","bucket":"landuse_rock"},{"name":"landuse_snow","bucket":"landuse_snow"},{"name":"landuse_hospital","bucket":"landuse_hospital"},{"name":"landuse_cemetery","bucket":"landuse_cemetery"},{"name":"overlay_wetland","bucket":"overlay_wetland"},{"name":"overlay_breakwater_pier","bucket":"overlay_breakwater_pier"},{"name":"waterway_river_canal","bucket":"waterway_river_canal"},{"name":"waterway_stream","bucket":"waterway_stream"},{"name":"building_shadow","bucket":"building"},{"name":"building","bucket":"building"},{"name":"building_wall","bucket":"building"},{"name":"hillshade_full_highlight","bucket":"hillshade_full_highlight"},{"name":"hillshade_medium_highlight","bucket":"hillshade_medium_highlight"},{"name":"hillshade_medium_shadow","bucket":"hillshade_medium_shadow"},{"name":"hillshade_full_shadow","bucket":"hillshade_full_shadow"},{"name":"contour_line_loud","bucket":"contour_line_10"},{"name":"contour_line_loud","bucket":"contour_line_5"},{"name":"contour_line_regular","bucket":"contour_line_other"},{"name":"barrier_line_gate","bucket":"barrier_line_gate"},{"name":"barrier_line_fence","bucket":"barrier_line_fence"},{"name":"barrier_line_hedge","bucket":"barrier_line_hedge"},{"name":"barrier_line_land","bucket":"barrier_line_land"},{"name":"barrier_line_land_fill","bucket":"barrier_line_land_fill"},{"name":"barrier_line_cliff","bucket":"barrier_line_cliff"},{"name":"water","bucket":"water"},{"name":"aeroway_fill","bucket":"aeroway_fill"},{"name":"aeroway_runway","bucket":"aeroway_runway"},{"name":"aeroway_taxiway","bucket":"aeroway_taxiway"},{"name":"tunnel_motorway_link_casing","bucket":"tunnel_motorway_link"},{"name":"tunnel_service_casing","bucket":"tunnel_service"},{"name":"tunnel_main_casing","bucket":"tunnel_main"},{"name":"tunnel_street_casing","bucket":"tunnel_street"},{"name":"tunnel_motorway_link","bucket":"tunnel_motorway_link"},{"name":"tunnel_service","bucket":"tunnel_service"},{"name":"tunnel_street","bucket":"tunnel_street"},{"name":"tunnel_main","bucket":"tunnel_main"},{"name":"tunnel_motorway_casing","bucket":"tunnel_motorway"},{"name":"tunnel_motorway","bucket":"tunnel_motorway"},{"name":"road_path_case","bucket":"tunnel_path"},{"name":"road_path_footway","bucket":"tunnel_path_footway"},{"name":"road_path_path","bucket":"tunnel_path_path"},{"name":"road_path_cycleway","bucket":"tunnel_path_cycleway"},{"name":"road_path_mtb","bucket":"tunnel_path_mtb"},{"name":"road_path_piste","bucket":"tunnel_path_piste"},{"name":"road_path_steps","bucket":"tunnel_path_steps"},{"name":"road_major_rail","bucket":"tunnel_major_rail"},{"name":"road_major_rail_hatching","bucket":"tunnel_major_rail"},{"name":"road_motorway_link_casing","bucket":"motorway_link"},{"name":"road_service_casing","bucket":"service"},{"name":"road_main_casing","bucket":"main"},{"name":"road_street_casing","bucket":"street"},{"name":"road_motorway_casing","bucket":"motorway"},{"name":"road_motorway_link","bucket":"motorway_link"},{"name":"road_service","bucket":"service"},{"name":"road_street","bucket":"street"},{"name":"road_main","bucket":"main"},{"name":"road_motorway","bucket":"motorway"},{"name":"road_path_case","bucket":"path"},{"name":"road_path_footway","bucket":"path_footway"},{"name":"road_path_path","bucket":"path_path"},{"name":"road_path_cycleway","bucket":"path_cycleway"},{"name":"road_path_mtb","bucket":"path_mtb"},{"name":"road_path_piste","bucket":"path_piste"},{"name":"road_path_steps","bucket":"path_steps"},{"name":"road_major_rail","bucket":"major_rail"},{"name":"road_major_rail_hatching","bucket":"major_rail"},{"name":"bridge_motorway_link_casing","bucket":"bridge_motorway_link"},{"name":"bridge_service_casing","bucket":"bridge_service"},{"name":"bridge_main_casing","bucket":"bridge_main"},{"name":"bridge_street_casing","bucket":"bridge_street"},{"name":"bridge_motorway_link","bucket":"bridge_motorway_link"},{"name":"bridge_service","bucket":"bridge_service"},{"name":"bridge_street","bucket":"bridge_street"},{"name":"bridge_main","bucket":"bridge_main"},{"name":"bridge_motorway_casing","bucket":"bridge_motorway"},{"name":"bridge_motorway","bucket":"bridge_motorway"},{"name":"road_path_footway","bucket":"bridge_path_footway"},{"name":"road_path_path","bucket":"bridge_path_path"},{"name":"road_path_cycleway","bucket":"bridge_path_cycleway"},{"name":"road_path_mtb","bucket":"bridge_path_mtb"},{"name":"road_path_piste","bucket":"bridge_path_piste"},{"name":"road_path_steps","bucket":"bridge_path_steps"},{"name":"bridge_aerialway_casing","bucket":"bridge_aerialway"},{"name":"bridge_aerialway","bucket":"bridge_aerialway"},{"name":"road_major_rail","bucket":"bridge_major_rail"},{"name":"road_major_rail_hatching","bucket":"bridge_major_rail"},{"name":"admin_l3","bucket":"admin_l3"},{"name":"admin_l2","bucket":"admin_l2"},{"name":"admin_maritime_cover","bucket":"admin_maritime"},{"name":"admin_maritime","bucket":"admin_maritime"},{"name":"country_label_line","bucket":"country_label_line"},{"name":"country_label","bucket":"country_label"},{"name":"marine_label_line_1","bucket":"marine_label_line_1"},{"name":"marine_label_line_2","bucket":"marine_label_line_2"},{"name":"marine_label_line_3","bucket":"marine_label_line_3"},{"name":"marine_label_line_other","bucket":"marine_label_line_other"},{"name":"marine_label_point_1","bucket":"marine_label_point_1"},{"name":"marine_label_point_2","bucket":"marine_label_point_2"},{"name":"marine_label_point_3","bucket":"marine_label_point_3"},{"name":"marine_label_point_other","bucket":"marine_label_point_other"},{"name":"state_label","bucket":"state_label"},{"name":"place_label_city","bucket":"place_label_city"},{"name":"place_label_town","bucket":"place_label_town"},{"name":"place_label_village","bucket":"place_label_village"},{"name":"place_label_other","bucket":"place_label_other"},{"name":"water_label","bucket":"water_label"},{"name":"poi_aerodrome","bucket":"poi_aerodrome"},{"name":"poi","bucket":"poi"},{"name":"poi_label_1-2","bucket":"poi_label_1-2"},{"name":"road_label_1","bucket":"road_label_1"},{"name":"poi_3","bucket":"poi_3"},{"name":"poi_label_3","bucket":"poi_label_3"},{"name":"road_label_2","bucket":"road_label_2"},{"name":"road_label_3","bucket":"road_label_3"},{"name":"contour_label","bucket":"contour_label"},{"name":"waterway_label","bucket":"waterway_label"},{"name":"poi_4_4","bucket":"poi_4_4"},{"name":"poi_label_4_4","bucket":"poi_label_4_4"},{"name":"poi_4_16","bucket":"poi_4_16"},{"name":"poi_label_4_16","bucket":"poi_label_4_16"},{"name":"poi_4_all","bucket":"poi_4_all"},{"name":"poi_label_4_all","bucket":"poi_label_4_all"}],"constants":{"land":"rgb(244,239,225)","water":"#cdd","water_dark":"#185869","crop":"#eeeed4","grass":"#e6e6cc","scrub":"#dfe5c8","wood":"#cee2bd","snow":"#f4f8ff","rock":"#ddd","sand":"#ffd","cemetery":"#edf4ed","pitch":"#fff","park":"#d4e4bc","piste":"blue","school":"#e8dfe0","hospital":"#f8eee0","builtup":"#f6faff","case":"#fff","motorway":"#cda0a0","main":"#ddc0b9","street":"#fff","text":"#666","text_stroke":"rgba(255,255,255,0.8)","country_text":"#222","marine_text":"#a0bdc0","water_text":"#185869","land_night":"#017293","water_night":"#103","water_dark_night":"#003366","crop_night":"#178d96","grass_night":"#23948a","scrub_night":"#31a186","wood_night":"#45b581","park_night":"#51bd8b","snow_night":"#5ad9fe","rock_night":"#999","sand_night":"#437162","cemetery_night":"#218c96","pitch_night":"rgba(255,255,255,0.2)","school_night":"#01536a","hospital_night":"#015e7a","builtup_night":"#014b60","admin_night":"#ffb680","text_night":"#fff","text_water_night":"#2a5b8a","text_stroke_night":"#103","text2_stroke_night":"rgba(1,69,89,0.8)","case_night":"#015e7a","street_case_night":"#015b76","motorway_night":"#bbdde7","main_night":"#64b2c9","street_night":"#0186ac","contour_night":"#ffff80","river_canal_width":["stops",{"z":11,"val":0.5},{"z":12,"val":1},{"z":14,"val":2},{"z":16,"val":3}],"stream_width":["stops",{"z":13,"val":0.25},{"z":14,"val":0.5},{"z":16,"val":1.5},{"z":18,"val":2}],"motorway_width":["stops",{"z":5,"val":0},{"z":6,"val":0.5},{"z":8,"val":0.8},{"z":10,"val":1},{"z":11,"val":1.2},{"z":12,"val":2},{"z":13,"val":3},{"z":14,"val":4},{"z":15,"val":6},{"z":16,"val":9},{"z":17,"val":12},{"z":18,"val":14}],"motorway_casing_width":["stops",{"z":7.5,"val":0.6},{"z":8,"val":0.8},{"z":10,"val":2.8},{"z":11,"val":3},{"z":12,"val":4},{"z":13,"val":5},{"z":14,"val":6.5},{"z":15,"val":9},{"z":16,"val":12},{"z":17,"val":15},{"z":18,"val":17}],"motorway_link_width":["stops",{"z":12,"val":1.2},{"z":14,"val":2},{"z":16,"val":3},{"z":18,"val":4}],"motorway_link_casing_width":["stops",{"z":12,"val":2.8},{"z":14,"val":3.5},{"z":16,"val":5},{"z":18,"val":6}],"main_width":["stops",{"z":5,"val":1},{"z":12,"val":1},{"z":13,"val":1.5},{"z":14,"val":2},{"z":15,"val":3},{"z":16,"val":6},{"z":17,"val":10},{"z":18,"val":12}],"main_casing_width":["stops",{"z":9,"val":2.9},{"z":12,"val":2.9},{"z":13,"val":3.5},{"z":14,"val":4},{"z":15,"val":5.5},{"z":16,"val":9},{"z":17,"val":12},{"z":18,"val":14}],"street_width":["stops",{"z":14.5,"val":0},{"z":15,"val":1.5},{"z":16,"val":3},{"z":17,"val":8}],"street_casing_width":["stops",{"z":13,"val":0.4},{"z":14,"val":1},{"z":15,"val":2.5},{"z":16,"val":4},{"z":17,"val":10}],"street_casing_opacity":["stops",{"z":14,"val":0},{"z":14.5,"val":1}],"service_casing_width":["stops",{"z":14,"val":0.5},{"z":15,"val":3},{"z":16,"val":3.5},{"z":17,"val":4},{"z":18,"val":5},{"z":19,"val":6}],"runway_width":["stops",{"z":10,"val":1},{"z":11,"val":2},{"z":12,"val":3},{"z":13,"val":5},{"z":14,"val":7},{"z":15,"val":11},{"z":16,"val":15},{"z":17,"val":19},{"z":18,"val":23}],"taxiway_width":["stops",{"z":10,"val":0.2},{"z":12,"val":0.2},{"z":13,"val":1},{"z":14,"val":1.5},{"z":15,"val":2},{"z":16,"val":3},{"z":17,"val":4},{"z":18,"val":5}],"aerialway_width":["stops",{"z":13.5,"val":0.8},{"z":14,"val":1.4},{"z":15,"val":1.6},{"z":16,"val":2},{"z":17,"val":2.4},{"z":18,"val":3}],"aerialway_casing_width":["stops",{"z":13.5,"val":2},{"z":14,"val":2.5},{"z":15,"val":3},{"z":16,"val":3.5},{"z":17,"val":4},{"z":22,"val":5}],"path_width":["stops",{"z":14,"val":1.2},{"z":15,"val":1.5},{"z":16,"val":1.8}],"admin_l2_width":["stops",{"z":2,"val":0.5},{"z":3,"val":0.7},{"z":4,"val":0.7},{"z":5,"val":0.8},{"z":6,"val":1},{"z":8,"val":2},{"z":10,"val":3}],"admin_l3_width":["stops",{"z":6,"val":0.6},{"z":8,"val":1},{"z":12,"val":2}],"road_label_1_size":["stops",{"z":13,"val":11},{"z":14,"val":12},{"z":15,"val":13},{"z":16,"val":14},{"z":17,"val":16},{"z":18,"val":18}],"road_label_2_size":["stops",{"z":13,"val":11},{"z":14,"val":12},{"z":16,"val":14},{"z":18,"val":16}],"road_label_3_size":["stops",{"z":15,"val":10},{"z":16,"val":12},{"z":18,"val":14}],"fence_width":["stops",{"z":17,"val":0.6},{"z":19,"val":1}],"hedge_width":["stops",{"z":16,"val":0.6},{"z":17,"val":1.2},{"z":19,"val":1.6}],"barrier_line_land_width":["stops",{"z":14,"val":0.4},{"z":15,"val":0.75},{"z":16,"val":1.5},{"z":17,"val":3},{"z":18,"val":6},{"z":19,"val":12},{"z":20,"val":24},{"z":21,"val":48}],"country_label_size":["stops",{"z":1,"val":14},{"z":12,"val":24}],"poi_label_1-2_size":["stops",{"z":15,"val":10},{"z":16,"val":11},{"z":17,"val":12}],"poi_label_3_size":["stops",{"z":16,"val":10},{"z":17,"val":11}],"hillshade_prerender":["stops",{"z":11,"val":0},{"z":12,"val":1}],"hillshade_prerender_size":["stops",{"z":11,"val":1056},{"z":12,"val":512},{"z":13,"val":256}]},"classes":[{"name":"default","layers":{"background":{"color":"land"},"admin_maritime_cover":{"color":"water","width":5},"admin_maritime":{"color":"#c0d6d6","width":["stops",{"z":6,"val":1},{"z":8,"val":2},{"z":12,"val":3}]},"admin_l2":{"color":"#88a","width":"admin_l2_width"},"admin_l3":{"color":"#88a","dasharray":[60,20],"opacity":["stops",{"z":4,"val":0},{"z":6,"val":1}],"width":"admin_l3_width"},"waterway_river_canal":{"color":"#87abaf","width":"river_canal_width"},"waterway_stream":{"color":"#87abaf","width":"stream_width"},"barrier_line_gate":{"width":2.5,"color":"#aab"},"barrier_line_fence":{"color":"#aeada3","width":"fence_width"},"barrier_line_hedge":{"color":"#8de99b","width":"hedge_width"},"barrier_line_land":{"color":"land","width":"barrier_line_land_width"},"barrier_line_land_fill":{"color":"land"},"barrier_line_cliff":{"color":"#987","width":4},"landcover_wood":{"color":"wood","opacity":["stops",{"z":13,"val":1},{"z":14,"val":0.8},{"z":17,"val":0.2}]},"landcover_scrub":{"color":"scrub","opacity":["stops",{"z":13,"val":1},{"z":14,"val":0.8},{"z":17,"val":0.2}]},"landcover_grass":{"color":"grass","opacity":["stops",{"z":13,"val":1},{"z":14,"val":0.8},{"z":17,"val":0.2}]},"landcover_crop":{"color":"crop"},"landcover_snow":{"color":"snow"},"landuse_wood":{"color":"wood"},"landuse_scrub":{"color":"scrub"},"landuse_grass":{"color":"grass"},"landuse_crop":{"color":"crop"},"landuse_snow":{"color":"snow"},"landuse_rock":{"color":"rock"},"landuse_sand":{"color":"sand"},"landuse_park":{"color":"park"},"landuse_cemetery":{"color":"cemetery"},"landuse_hospital":{"color":"hospital"},"landuse_school":{"color":"school"},"landuse_pitch":{"color":"rgba(255,255,255,0.5)","stroke":"pitch"},"landuse_industrial":{"color":"rgba(246,250,255,0.5)"},"overlay_wetland":{"color":"rgba(210,225,225,0.2)","image":"wetland_noveg_64"},"overlay_breakwater_pier":{"color":"land"},"hillshade_full_shadow":{"color":"#103","antialias":false,"prerender":"hillshade_prerender","prerender-size":"hillshade_prerender_size","prerender-blur":1,"opacity":["stops",{"z":15,"val":0.08},{"z":16,"val":0.075},{"z":17,"val":0.05},{"z":18,"val":0.05},{"z":19,"val":0.025}]},"hillshade_medium_shadow":{"color":"#206","antialias":false,"prerender":"hillshade_prerender","prerender-size":"hillshade_prerender_size","prerender-blur":1,"opacity":["stops",{"z":15,"val":0.08},{"z":16,"val":0.075},{"z":17,"val":0.05},{"z":18,"val":0.05},{"z":19,"val":0.025}]},"hillshade_full_highlight":{"color":"#fffff3","antialias":false,"prerender":"hillshade_prerender","prerender-size":"hillshade_prerender_size","prerender-blur":1,"opacity":["stops",{"z":15,"val":0.3},{"z":16,"val":0.3},{"z":17,"val":0.2},{"z":18,"val":0.2},{"z":19,"val":0.1}]},"hillshade_medium_highlight":{"color":"#ffd","antialias":false,"prerender":"hillshade_prerender","prerender-size":"hillshade_prerender_size","prerender-blur":1,"opacity":["stops",{"z":15,"val":0.3},{"z":16,"val":0.3},{"z":17,"val":0.2},{"z":18,"val":0.2},{"z":19,"val":0.1}]},"contour_line_loud":{"color":"#008","width":0.9,"opacity":["stops",{"z":12,"val":0.05},{"z":13,"val":0.11}]},"contour_line_regular":{"color":"#008","width":0.5,"opacity":["stops",{"z":12,"val":0.05},{"z":13,"val":0.11}]},"contour_label":{"color":"text","stroke":"land","strokeWidth":0.3,"strokeBlur":3,"size":10},"water":{"color":"water","stroke":"#a2bdc0"},"aeroway_fill":{"color":"#ddd"},"aeroway_runway":{"color":"#ddd","width":"runway_width"},"aeroway_taxiway":{"color":"#ddd","width":"taxiway_width"},"building":{"color":"#ebe7db"},"building_wall":{"color":"#ebe7db","stroke":"#d5d1c6","opacity":["stops",{"z":16.5,"val":0},{"z":17,"val":0.7}]},"building_shadow":{"color":"#d5d1c6","stroke":"#d5d1c6","translate":[1,1],"opacity":["stops",{"z":16.5,"val":0},{"z":17,"val":1}]},"tunnel_motorway_casing":{"color":"case","dasharray":[6,6],"width":"motorway_casing_width","opacity":["stops",{"z":9.5,"val":0},{"z":10,"val":1}]},"tunnel_motorway":{"color":"#e6cec7","width":"motorway_width","opacity":["stops",{"z":6.5,"val":0},{"z":7,"val":1}]},"tunnel_main_casing":{"color":"case","dasharray":[6,6],"width":"main_casing_width","opacity":["stops",{"z":9,"val":0},{"z":10,"val":1}]},"tunnel_main":{"color":"#e6cec7","width":"main_width","opacity":["stops",{"z":6.5,"val":0},{"z":7,"val":1}]},"tunnel_motorway_link_casing":{"color":"case","dasharray":[6,6],"width":"motorway_link_casing_width"},"tunnel_motorway_link":{"color":"#e6cec7","width":"motorway_link_width"},"tunnel_street_casing":{"color":"#d9d5c6","width":"street_casing_width","opacity":"street_casing_opacity"},"tunnel_street":{"color":"#d9d5c6","width":"street_width"},"tunnel_service_casing":{"color":"#000","opacity":0.04,"dasharray":[6,6],"width":"service_casing_width"},"tunnel_service":{"color":"#e6cec7","width":2},"road_motorway_casing":{"color":"case","width":"motorway_casing_width","opacity":["stops",{"z":9.5,"val":0},{"z":10,"val":1}]},"road_motorway":{"color":"motorway","width":"motorway_width","opacity":["stops",{"z":6.5,"val":0},{"z":7,"val":1}]},"road_main_casing":{"color":"case","width":"main_casing_width","opacity":["stops",{"z":9,"val":0},{"z":10,"val":1}]},"road_main":{"color":"main","width":"main_width","opacity":["stops",{"z":6.5,"val":0},{"z":7,"val":1}]},"road_motorway_link_casing":{"color":"case","width":"motorway_link_casing_width"},"road_motorway_link":{"color":"motorway","width":"motorway_link_width"},"road_street_casing":{"color":"#d9d5c6","width":"street_casing_width","opacity":"street_casing_opacity"},"road_street":{"color":"street","width":"street_width"},"road_service_casing":{"color":"#000","opacity":0.04,"width":"service_casing_width"},"road_service":{"color":"street","width":2},"road_path_case":{"color":"#ffd","opacity":0.4,"width":["stops",{"z":15,"val":3},{"z":16,"val":4}]},"road_path_footway":{"color":"#bba","dasharray":[10,4],"width":"path_width"},"road_path_path":{"color":"#987","dasharray":[10,4],"opacity":0.8,"width":["stops",{"z":14,"val":0.8},{"z":15,"val":0.9},{"z":16,"val":1.2}]},"road_path_cycleway":{"color":"#488","dasharray":[10,4],"width":"path_width"},"road_path_mtb":{"color":"#488","dasharray":[12,4],"width":"path_width"},"road_path_piste":{"color":"#87b","dasharray":[8,4],"width":"path_width"},"road_path_steps":{"color":"#bba","dasharray":[10,4],"width":4},"road_major_rail":{"color":"#c8c4c0","width":0.8},"road_major_rail_hatching":{"color":"#c8c4c0","dasharray":[2,31],"width":5},"bridge_motorway_casing":{"color":"case","width":"motorway_casing_width","opacity":["stops",{"z":9.5,"val":0},{"z":10,"val":1}]},"bridge_motorway":{"color":"motorway","width":"motorway_width","opacity":["stops",{"z":6.5,"val":0},{"z":7,"val":1}]},"bridge_main_casing":{"color":"case","width":"main_casing_width","opacity":["stops",{"z":9,"val":0},{"z":10,"val":1}]},"bridge_main":{"color":"main","width":"main_width","opacity":["stops",{"z":6.5,"val":0},{"z":7,"val":1}]},"bridge_motorway_link_casing":{"color":"case","width":"motorway_link_casing_width"},"bridge_motorway_link":{"color":"motorway","width":"motorway_link_width"},"bridge_street_casing":{"color":"#d9d5c6","width":"street_casing_width","opacity":"street_casing_opacity"},"bridge_street":{"color":"street","width":"street_width"},"bridge_service_casing":{"color":"#000","opacity":0.04,"width":"service_casing_width"},"bridge_service":{"color":"street","width":2},"bridge_aerialway_casing":{"color":"white","opacity":0.5,"width":"aerialway_casing_width"},"bridge_aerialway":{"color":"#876","opacity":0.5,"width":"aerialway_width"},"country_label":{"color":"country_text","stroke":"rgba(255,255,255,0.5)","strokeWidth":0.5,"size":"country_label_size"},"country_label_line":{"color":"country_text","width":0.5,"opacity":0.5},"marine_label_line_1":{"color":"marine_text","size":["stops",{"z":3,"val":20},{"z":4,"val":25},{"z":5,"val":30},{"z":22,"val":30}],"stroke":"water"},"marine_label_line_2":{"color":"marine_text","size":["stops",{"z":3,"val":13},{"z":4,"val":14},{"z":5,"val":20},{"z":6,"val":24},{"z":22,"val":24}],"stroke":"water"},"marine_label_line_3":{"color":"marine_text","size":["stops",{"z":3,"val":12},{"z":4,"val":13},{"z":5,"val":15},{"z":6,"val":18},{"z":22,"val":18}],"stroke":"water"},"marine_label_line_other":{"color":"marine_text","size":["stops",{"z":4,"val":12},{"z":5,"val":14},{"z":6,"val":16},{"z":22,"val":16}],"stroke":"water"},"marine_label_point_1":{"color":"marine_text","size":["stops",{"z":3,"val":20},{"z":4,"val":25},{"z":5,"val":30},{"z":22,"val":30}],"stroke":"water"},"marine_label_point_2":{"color":"marine_text","size":["stops",{"z":3,"val":13},{"z":4,"val":14},{"z":5,"val":20},{"z":6,"val":24},{"z":22,"val":24}],"stroke":"water"},"marine_label_point_3":{"color":"marine_text","size":["stops",{"z":3,"val":12},{"z":4,"val":13},{"z":5,"val":15},{"z":6,"val":18},{"z":22,"val":18}],"stroke":"water"},"marine_label_point_other":{"color":"marine_text","size":["stops",{"z":4,"val":12},{"z":5,"val":14},{"z":6,"val":16},{"z":22,"val":16}],"stroke":"water"},"state_label":{"color":"#333","strokeWidth":0.4,"strokeBlur":1,"stroke":"rgba(244,239,225,0.8)","size":["stops",{"z":3.99,"val":0},{"z":4,"val":10},{"z":9.99,"val":16},{"z":10,"val":0}]},"place_label_city":{"color":"#444","strokeWidth":0.4,"stroke":"text_stroke","size":["stops",{"z":3.99,"val":0},{"z":4,"val":10},{"z":7,"val":14},{"z":14.99,"val":20},{"z":15,"val":0}]},"place_label_town":{"color":"#716656","strokeWidth":0.3,"strokeBlur":2,"stroke":"text_stroke","size":["stops",{"z":9,"val":10},{"z":12,"val":13},{"z":14,"val":17},{"z":16,"val":22}]},"place_label_village":{"color":"#635644","strokeWidth":0.3,"strokeBlur":2,"stroke":"text_stroke","size":["stops",{"z":9,"val":8},{"z":12,"val":10},{"z":14,"val":14},{"z":16,"val":16},{"z":17,"val":20}]},"place_label_other":{"color":"#7d6c55","stroke":"text_stroke","size":["stops",{"z":13,"val":11},{"z":14,"val":12},{"z":16,"val":14},{"z":18,"val":18}]},"road_label_1":{"color":"#585042","stroke":"land","strokeWidth":0.6,"strokeBlur":2,"size":"road_label_1_size"},"road_label_2":{"color":"#585042","stroke":"land","strokeWidth":0.6,"strokeBlur":2,"size":"road_label_2_size"},"road_label_3":{"color":"#585042","stroke":"land","strokeWidth":0.6,"strokeBlur":2,"size":"road_label_3_size"},"water_label":{"color":"water_dark","stroke":"rgba(255,255,255,0.75)"},"waterway_label":{"color":"water_dark","strokeWidth":0.4,"strokeBlur":2,"stroke":"text_stroke"},"poi":{"antialias":false},"poi_3":{"antialias":false,"opacity":["stops",{"z":17.5,"val":0},{"z":17.75,"val":1}]},"poi_4_4":{"antialias":false,"opacity":["stops",{"z":18,"val":0},{"z":18.25,"val":1}]},"poi_4_16":{"antialias":false,"opacity":["stops",{"z":18.5,"val":0},{"z":18.75,"val":1}]},"poi_4_all":{"antialias":false,"opacity":["stops",{"z":18.75,"val":0},{"z":19,"val":1}]},"poi_label_1-2":{"color":"#444","size":"poi_label_1-2_size","stroke":"land","strokeWidth":0.3,"strokeBlur":1},"poi_label_3":{"color":"#444","size":"poi_label_3_size","stroke":"land","strokeWidth":0.3,"strokeBlur":1,"opacity":["stops",{"z":17.5,"val":0},{"z":17.75,"val":1}]},"poi_label_4_4":{"color":"#444","size":10,"opacity":["stops",{"z":18,"val":0},{"z":17.25,"val":1}],"stroke":"land","strokeWidth":0.3,"strokeBlur":1},"poi_label_4_16":{"color":"#444","size":10,"opacity":["stops",{"z":18.25,"val":0},{"z":18.75,"val":1}],"stroke":"land","strokeWidth":0.3,"strokeBlur":1},"poi_label_4_all":{"color":"#444","size":10,"opacity":["stops",{"z":18.75,"val":0},{"z":19,"val":1}],"stroke":"land","strokeWidth":0.3,"strokeBlur":1},"poi_aerodrome":{"opacity":["stops",{"z":13,"val":0},{"z":13.25,"val":1}],"antialias":false}}},{"name":"night","layers":{"background":{"color":"land_night"},"admin_maritime_cover":{"color":"water_night","width":5},"admin_maritime":{"color":"#0a1347","width":["stops",{"z":6,"val":1},{"z":8,"val":2},{"z":12,"val":3}]},"admin_l2":{"color":"admin_night","width":"admin_l2_width"},"admin_l3":{"color":"admin_night","dasharray":[60,20],"opacity":["stops",{"z":4,"val":0},{"z":6,"val":1}],"width":"admin_l3_width"},"waterway_river_canal":{"color":"rgb(10,20,71)","width":"river_canal_width"},"waterway_stream":{"color":"rgb(10,20,71)","width":"stream_width"},"barrier_line_gate":{"width":2.5,"color":"#59596f"},"barrier_line_fence":{"color":"#014b61","width":"fence_width"},"barrier_line_hedge":{"color":"#2e7a57","width":"hedge_width"},"barrier_line_land":{"color":"land_night","width":"barrier_line_land_width"},"barrier_line_land_fill":{"color":"land_night"},"barrier_line_cliff":{"color":"#63574b","width":4},"landcover_wood":{"color":"wood_night","opacity":["stops",{"z":13,"val":1},{"z":14,"val":0.8},{"z":17,"val":0.2}]},"landcover_scrub":{"color":"scrub_night","opacity":["stops",{"z":13,"val":1},{"z":14,"val":0.8},{"z":17,"val":0.2}]},"landcover_grass":{"color":"grass_night","opacity":["stops",{"z":13,"val":1},{"z":14,"val":0.8},{"z":17,"val":0.2}]},"landcover_crop":{"color":"crop_night"},"landcover_snow":{"color":"snow_night"},"landuse_wood":{"color":"wood_night","opacity":0.8},"landuse_scrub":{"color":"scrub_night","opacity":0.8},"landuse_grass":{"color":"grass_night","opacity":0.8},"landuse_crop":{"color":"crop_night","opacity":0.8},"landuse_snow":{"color":"snow_night","opacity":0.8},"landuse_rock":{"color":"rock_night","opacity":0.8},"landuse_sand":{"color":"sand_night","opacity":0.8},"landuse_park":{"color":"park_night"},"landuse_cemetery":{"color":"cemetery_night"},"landuse_hospital":{"color":"hospital_night"},"landuse_school":{"color":"school_night"},"landuse_pitch":{"color":"pitch_night","stroke":"pitch"},"landuse_industrial":{"color":"builtup_night"},"overlay_wetland":{"color":"rgba(210,225,225,0.2)","image":"wetland_noveg_64"},"overlay_breakwater_pier":{"color":"land_night"},"hillshade_full_shadow":{"color":"#103","antialias":false,"prerender":"hillshade_prerender","prerender-size":"hillshade_prerender_size","prerender-blur":1,"opacity":["stops",{"z":16,"val":0.3},{"z":17,"val":0.2},{"z":18,"val":0.1},{"z":19,"val":0.05}]},"hillshade_medium_shadow":{"color":"#206","antialias":false,"prerender":"hillshade_prerender","prerender-size":"hillshade_prerender_size","prerender-blur":1,"opacity":["stops",{"z":16,"val":0.3},{"z":17,"val":0.2},{"z":18,"val":0.1},{"z":19,"val":0.05}]},"hillshade_full_highlight":{"color":"#fdfdad","antialias":false,"prerender":"hillshade_prerender","prerender-size":"hillshade_prerender_size","prerender-blur":1,"opacity":["stops",{"z":14,"val":0.4},{"z":15,"val":0.3},{"z":17,"val":0.2},{"z":18,"val":0.1},{"z":19,"val":0.05}]},"hillshade_medium_highlight":{"color":"#ffe1b7","antialias":false,"prerender":"hillshade_prerender","prerender-size":"hillshade_prerender_size","prerender-blur":1,"opacity":["stops",{"z":15,"val":0.3},{"z":17,"val":0.2},{"z":18,"val":0.15},{"z":19,"val":0.05}]},"contour_line_loud":{"color":"contour_night","width":0.9,"opacity":["stops",{"z":12,"val":0.1},{"z":13,"val":0.2}]},"contour_line_regular":{"color":"contour_night","width":0.5,"opacity":["stops",{"z":12,"val":0.1},{"z":13,"val":0.4}]},"contour_label":{"color":"contour_night","stroke":"land_night","strokeWidth":0.3,"strokeBlur":3,"size":10},"water":{"color":"water_night","stroke":"water_dark_night"},"aeroway_fill":{"color":"#367"},"aeroway_runway":{"color":"#367","width":"runway_width"},"aeroway_taxiway":{"color":"#367","width":"taxiway_width"},"building":{"color":"#027797"},"building_wall":{"color":"#027797","stroke":"#026688","opacity":["stops",{"z":16.5,"val":0},{"z":17,"val":0.7}]},"building_shadow":{"color":"#026688","stroke":"#026688","translate":[1,1],"opacity":["stops",{"z":16.5,"val":0},{"z":17,"val":1}]},"tunnel_motorway_casing":{"color":"case_night","dasharray":[6,6],"width":"motorway_casing_width","opacity":["stops",{"z":9.5,"val":0},{"z":10,"val":1}]},"tunnel_motorway":{"color":"#78b0c1","width":"motorway_width","opacity":["stops",{"z":6.5,"val":0},{"z":7,"val":1}]},"tunnel_main_casing":{"color":"case_night","dasharray":[6,6],"width":"main_casing_width","opacity":["stops",{"z":9,"val":0},{"z":10,"val":1}]},"tunnel_main":{"color":"#78b0c1","width":"main_width","opacity":["stops",{"z":6.5,"val":0},{"z":7,"val":1}]},"tunnel_motorway_link_casing":{"color":"case_night","dasharray":[6,6],"width":"motorway_link_casing_width"},"tunnel_motorway_link":{"color":"#78b0c1","width":"motorway_link_width"},"tunnel_street_casing":{"color":"street_case_night","width":"street_casing_width","opacity":"street_casing_opacity"},"tunnel_street":{"color":"street_night","width":"street_width"},"tunnel_service_casing":{"color":"#000","opacity":0.04,"dasharray":[6,6],"width":"service_casing_width"},"tunnel_service":{"color":"#017ca0","width":2},"road_motorway_casing":{"color":"case_night","width":"motorway_casing_width","opacity":["stops",{"z":9.5,"val":0},{"z":10,"val":1}]},"road_motorway":{"color":"motorway_night","width":"motorway_width","opacity":["stops",{"z":6.5,"val":0},{"z":7,"val":1}]},"road_main_casing":{"color":"case_night","width":"main_casing_width","opacity":["stops",{"z":9,"val":0},{"z":10,"val":1}]},"road_main":{"color":"main_night","width":"main_width","opacity":["stops",{"z":6.5,"val":0},{"z":7,"val":1}]},"road_motorway_link_casing":{"color":"case_night","width":"motorway_link_casing_width"},"road_motorway_link":{"color":"motorway_night","width":"motorway_link_width"},"road_street_casing":{"color":"street_case_night","width":"street_casing_width","opacity":"street_casing_opacity"},"road_street":{"color":"street_night","width":"street_width"},"road_service_casing":{"color":"#000","opacity":0.04,"width":"service_casing_width"},"road_service":{"color":"street_night","width":2},"road_path_case":{"color":"land_night","opacity":0.2},"road_path_footway":{"color":"#fff","dasharray":[10,4],"width":"path_width"},"road_path_path":{"color":"#fff","dasharray":[10,4],"opacity":0.8,"width":["stops",{"z":14,"val":0.8},{"z":15,"val":0.9},{"z":16,"val":1.2}]},"road_path_cycleway":{"color":"#94e6ff","dasharray":[10,4],"width":"path_width"},"road_path_mtb":{"color":"#94e6ff","dasharray":[12,4],"width":"path_width"},"road_path_piste":{"color":"#715dae","dasharray":[8,4],"width":"path_width"},"road_path_steps":{"color":"#016684","dasharray":[10,4],"opacity":0.3,"width":6},"road_major_rail":{"color":"#c8c4c0","width":0.8},"road_major_rail_hatching":{"color":"#c8c4c0","dasharray":[2,31],"width":5},"bridge_motorway_casing":{"color":"case_night","width":"motorway_casing_width","opacity":["stops",{"z":9.5,"val":0},{"z":10,"val":1}]},"bridge_motorway":{"color":"motorway_night","width":"motorway_width","opacity":["stops",{"z":6.5,"val":0},{"z":7,"val":1}]},"bridge_main_casing":{"color":"case_night","width":"main_casing_width","opacity":["stops",{"z":9,"val":0},{"z":10,"val":1}]},"bridge_main":{"color":"main_night","width":"main_width","opacity":["stops",{"z":6.5,"val":0},{"z":7,"val":1}]},"bridge_motorway_link_casing":{"color":"case_night","width":"motorway_link_casing_width"},"bridge_motorway_link":{"color":"motorway_night","width":"motorway_link_width"},"bridge_street_casing":{"color":"street_case_night","width":"street_casing_width","opacity":"street_casing_opacity"},"bridge_street":{"color":"street_night","width":"street_width"},"bridge_service_casing":{"color":"#000","opacity":0.04,"width":"service_casing_width"},"bridge_service":{"color":"street_night","width":2},"bridge_aerialway_casing":{"color":"white","opacity":0.5,"width":"aerialway_casing_width"},"bridge_aerialway":{"color":"#876","opacity":0.5,"width":"aerialway_width"},"country_label":{"color":"text_night","stroke":"text2_stroke_night","strokeWidth":0.4,"strokeBlur":2,"size":"country_label_size"},"country_label_line":{"color":"text_night","width":0.5,"opacity":0.5},"marine_label_line_1":{"color":"water_dark_night","size":["stops",{"z":3,"val":20},{"z":4,"val":25},{"z":5,"val":30},{"z":22,"val":30}],"stroke":"water_night"},"marine_label_line_2":{"color":"water_dark_night","size":["stops",{"z":3,"val":13},{"z":4,"val":14},{"z":5,"val":20},{"z":6,"val":24},{"z":22,"val":24}],"stroke":"water_night"},"marine_label_line_3":{"color":"water_dark_night","size":["stops",{"z":3,"val":12},{"z":4,"val":13},{"z":5,"val":15},{"z":6,"val":18},{"z":22,"val":18}],"stroke":"water_night"},"marine_label_line_other":{"color":"water_dark_night","size":["stops",{"z":4,"val":12},{"z":5,"val":14},{"z":6,"val":16},{"z":22,"val":16}],"stroke":"water_night"},"marine_label_point_1":{"color":"water_dark_night","size":["stops",{"z":3,"val":20},{"z":4,"val":25},{"z":5,"val":30},{"z":22,"val":30}],"stroke":"water_night"},"marine_label_point_2":{"color":"water_dark_night","size":["stops",{"z":3,"val":13},{"z":4,"val":14},{"z":5,"val":20},{"z":6,"val":24},{"z":22,"val":24}],"stroke":"water_night"},"marine_label_point_3":{"color":"water_dark_night","size":["stops",{"z":3,"val":12},{"z":4,"val":13},{"z":5,"val":15},{"z":6,"val":18},{"z":22,"val":18}],"stroke":"water_night"},"marine_label_point_other":{"color":"water_dark_night","size":["stops",{"z":4,"val":12},{"z":5,"val":14},{"z":6,"val":16},{"z":22,"val":16}],"stroke":"water_night"},"state_label":{"color":"#fff","strokeWidth":0.4,"strokeBlur":1,"stroke":"land_night","size":["stops",{"z":3.99,"val":0},{"z":4,"val":10},{"z":9.99,"val":16},{"z":10,"val":0}]},"place_label_city":{"color":"#fff","strokeWidth":0.4,"stroke":"text2_stroke_night","size":["stops",{"z":3.99,"val":0},{"z":4,"val":10},{"z":7,"val":14},{"z":14.99,"val":20},{"z":15,"val":0}]},"place_label_town":{"color":"text_night","strokeWidth":0.3,"strokeBlur":2,"stroke":"text2_stroke_night","size":["stops",{"z":9,"val":10},{"z":12,"val":13},{"z":14,"val":17},{"z":16,"val":22}]},"place_label_village":{"color":"text_night","strokeWidth":0.3,"strokeBlur":2,"stroke":"text2_stroke_night","size":["stops",{"z":9,"val":8},{"z":12,"val":10},{"z":14,"val":14},{"z":16,"val":16},{"z":17,"val":20}]},"place_label_other":{"color":"text_night","stroke":"text2_stroke_night","strokeWidth":0.3,"strokeBlur":2,"size":["stops",{"z":13,"val":11},{"z":14,"val":12},{"z":16,"val":14},{"z":18,"val":18}]},"road_label_1":{"color":"text_night","stroke":"text2_stroke_night","strokeWidth":0.5,"strokeBlur":3,"size":"road_label_1_size"},"road_label_2":{"color":"text_night","stroke":"text2_stroke_night","strokeWidth":0.5,"strokeBlur":3,"size":"road_label_2_size"},"road_label_3":{"color":"text_night","stroke":"text2_stroke_night","strokeWidth":0.5,"strokeBlur":3,"size":"road_label_3_size"},"water_label":{"color":"text_water_night","stroke":"water_night"},"waterway_label":{"color":"text_water_night","stroke":"water_night"},"poi":{"color":"white","antialias":false},"poi_3":{"antialias":false,"opacity":["stops",{"z":17.5,"val":0},{"z":17.75,"val":1}]},"poi_4_4":{"antialias":false,"opacity":["stops",{"z":18,"val":0},{"z":17.25,"val":1}]},"poi_4_16":{"antialias":false,"opacity":["stops",{"z":18.5,"val":0},{"z":18.75,"val":1}]},"poi_4_all":{"antialias":false,"opacity":["stops",{"z":18.75,"val":0},{"z":19,"val":1}]},"poi_label_1-2":{"color":"#fff","size":"poi_label_1-2_size","stroke":"text2_stroke_night","strokeWidth":0.3,"strokeBlur":1},"poi_label_3":{"color":"#fff","size":"poi_label_3_size","stroke":"text2_stroke_night","strokeWidth":0.3,"strokeBlur":1,"opacity":["stops",{"z":17.5,"val":0},{"z":17.75,"val":1}]},"poi_label_4_4":{"color":"#fff","size":10,"opacity":["stops",{"z":18,"val":0},{"z":18.25,"val":1}],"stroke":"text2_stroke_night","strokeWidth":0.3,"strokeBlur":1},"poi_label_4_16":{"color":"#fff","size":10,"opacity":["stops",{"z":18.5,"val":0},{"z":18.75,"val":1}],"stroke":"text2_stroke_night","strokeWidth":0.3,"strokeBlur":1},"poi_label_4_all":{"color":"#fff","size":10,"opacity":["stops",{"z":18.75,"val":0},{"z":19,"val":1}],"stroke":"text2_stroke_night","strokeWidth":0.3,"strokeBlur":1},"poi_aerodrome":{"opacity":["stops",{"z":13,"val":0},{"z":13.25,"val":1}],"antialias":false}}}],"sprite":"/img/maki-sprite"} \ No newline at end of file diff --git a/android/java/lib/src/main/res/values/attrs.xml b/android/java/lib/src/main/res/values/attrs.xml new file mode 100644 index 00000000000..b42f729c929 --- /dev/null +++ b/android/java/lib/src/main/res/values/attrs.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/java/lib/src/main/res/values/strings.xml b/android/java/lib/src/main/res/values/strings.xml new file mode 100644 index 00000000000..f11f7450a82 --- /dev/null +++ b/android/java/lib/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + + diff --git a/android/java/settings.gradle b/android/java/settings.gradle new file mode 100644 index 00000000000..0959cbb33ec --- /dev/null +++ b/android/java/settings.gradle @@ -0,0 +1,2 @@ +include ':app' +include ':lib'