From bff63d44c62cea75e2e5162e7c46cd1fbf8ef4c1 Mon Sep 17 00:00:00 2001 From: Leith Bade Date: Thu, 6 Nov 2014 00:04:48 +1100 Subject: [PATCH] Add java files --- android/java/.gitignore | 14 + android/java/app/build.gradle | 23 + android/java/app/lint.xml | 4 + android/java/app/src/main/AndroidManifest.xml | 33 + .../mapbox/mapboxgl/app/FragmentActivity.java | 29 + .../com/mapbox/mapboxgl/app/MainActivity.java | 98 ++ .../main/res/drawable-hdpi/ic_launcher.png | Bin 0 -> 7658 bytes .../main/res/drawable-mdpi/ic_launcher.png | Bin 0 -> 3777 bytes .../main/res/drawable-xhdpi/ic_launcher.png | Bin 0 -> 12516 bytes .../main/res/drawable-xxhdpi/ic_launcher.png | Bin 0 -> 24777 bytes .../src/main/res/layout/activity_fragment.xml | 11 + .../app/src/main/res/layout/activity_main.xml | 11 + .../app/src/main/res/values-v11/styles.xml | 11 + .../app/src/main/res/values-v14/styles.xml | 12 + .../java/app/src/main/res/values/strings.xml | 6 + .../java/app/src/main/res/values/styles.xml | 20 + android/java/build.gradle | 15 + .../java/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 49896 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + android/java/gradlew | 164 +++ android/java/gradlew.bat | 90 ++ android/java/lib/build.gradle | 31 + android/java/lib/lint.xml | 4 + android/java/lib/src/main/AndroidManifest.xml | 12 + .../gesturedetectors/BaseGestureDetector.java | 160 +++ .../gesturedetectors/MoveGestureDetector.java | 188 +++ .../RotateGestureDetector.java | 180 +++ .../ShoveGestureDetector.java | 213 +++ .../TwoFingerGestureDetector.java | 228 ++++ .../java/com/mapbox/mapboxgl/lib/LonLat.java | 91 ++ .../com/mapbox/mapboxgl/lib/LonLatZoom.java | 124 ++ .../com/mapbox/mapboxgl/lib/MapFragment.java | 150 +++ .../java/com/mapbox/mapboxgl/lib/MapView.java | 1178 +++++++++++++++++ .../mapbox/mapboxgl/lib/NativeMapView.java | 501 +++++++ .../java/lib/src/main/res/raw/style.min.js | 1 + .../java/lib/src/main/res/values/attrs.xml | 32 + .../java/lib/src/main/res/values/strings.xml | 3 + android/java/settings.gradle | 2 + 38 files changed, 3645 insertions(+) create mode 100644 android/java/.gitignore create mode 100644 android/java/app/build.gradle create mode 100644 android/java/app/lint.xml create mode 100644 android/java/app/src/main/AndroidManifest.xml create mode 100644 android/java/app/src/main/java/com/mapbox/mapboxgl/app/FragmentActivity.java create mode 100644 android/java/app/src/main/java/com/mapbox/mapboxgl/app/MainActivity.java create mode 100644 android/java/app/src/main/res/drawable-hdpi/ic_launcher.png create mode 100644 android/java/app/src/main/res/drawable-mdpi/ic_launcher.png create mode 100644 android/java/app/src/main/res/drawable-xhdpi/ic_launcher.png create mode 100644 android/java/app/src/main/res/drawable-xxhdpi/ic_launcher.png create mode 100644 android/java/app/src/main/res/layout/activity_fragment.xml create mode 100644 android/java/app/src/main/res/layout/activity_main.xml create mode 100644 android/java/app/src/main/res/values-v11/styles.xml create mode 100644 android/java/app/src/main/res/values-v14/styles.xml create mode 100644 android/java/app/src/main/res/values/strings.xml create mode 100644 android/java/app/src/main/res/values/styles.xml create mode 100644 android/java/build.gradle create mode 100644 android/java/gradle/wrapper/gradle-wrapper.jar create mode 100644 android/java/gradle/wrapper/gradle-wrapper.properties create mode 100755 android/java/gradlew create mode 100644 android/java/gradlew.bat create mode 100644 android/java/lib/build.gradle create mode 100644 android/java/lib/lint.xml create mode 100644 android/java/lib/src/main/AndroidManifest.xml create mode 100644 android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/BaseGestureDetector.java create mode 100644 android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/MoveGestureDetector.java create mode 100644 android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/RotateGestureDetector.java create mode 100644 android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/ShoveGestureDetector.java create mode 100644 android/java/lib/src/main/java/com/almeros/android/multitouch/gesturedetectors/TwoFingerGestureDetector.java create mode 100644 android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/LonLat.java create mode 100644 android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/LonLatZoom.java create mode 100644 android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/MapFragment.java create mode 100644 android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/MapView.java create mode 100644 android/java/lib/src/main/java/com/mapbox/mapboxgl/lib/NativeMapView.java create mode 100644 android/java/lib/src/main/res/raw/style.min.js create mode 100644 android/java/lib/src/main/res/values/attrs.xml create mode 100644 android/java/lib/src/main/res/values/strings.xml create mode 100644 android/java/settings.gradle diff --git a/android/java/.gitignore b/android/java/.gitignore new file mode 100644 index 000000000..478d78698 --- /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 000000000..cb3750b7d --- /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 000000000..7328a1fd1 --- /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 000000000..83995b8d3 --- /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 000000000..1b27ca2fd --- /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 000000000..43537f9ac --- /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 0000000000000000000000000000000000000000..288b66551d1efd1f13dd06f20a67534d2df57946 GIT binary patch literal 7658 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d4fb7cd9d868f1d7d9964f1686dcbc018ef9495a GIT binary patch literal 12516 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 literal 0 HcmV?d00001 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 000000000..6310106fc --- /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 000000000..e4b11e3ad --- /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 000000000..3c02242ad --- /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 000000000..a91fd0372 --- /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 000000000..f6d4e76fa --- /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 000000000..6ce89c7ba --- /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 000000000..e1e165302 --- /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 0000000000000000000000000000000000000000..8c0fb64a8698b08ecc4158d828ca593c4928e9dd GIT binary patch literal 49896 zcmagFb986H(k`5d^NVfUwr$(C?M#x1ZQHiZiEVpg+jrjgoQrerx!>1o_ul)D>ebz~ zs=Mmxr&>W81QY-S1PKWQ%N-;H^tS;2*XwVA`dej1RRn1z<;3VgfE4~kaG`A%QSPsR z#ovnZe+tS9%1MfeDyz`RirvdjPRK~p(#^q2(^5@O&NM19EHdvN-A&StN>0g6QA^VN z0Gx%Gq#PD$QMRFzmK+utjS^Y1F0e8&u&^=w5K<;4Rz|i3A=o|IKLY+g`iK6vfr9?+ z-`>gmU&i?FGSL5&F?TXFu`&Js6h;15QFkXp2M1H9|Eq~bpov-GU(uz%mH0n55wUl- zv#~ccAz`F5wlQ>e_KlJS3@{)B?^v*EQM=IxLa&76^y51a((wq|2-`qON>+4dLc{Oo z51}}o^Zen(oAjxDK7b++9_Yg`67p$bPo3~BCpGM7uAWmvIhWc5Gi+gQZ|Pwa-Gll@<1xmcPy z|NZmu6m)g5Ftu~BG&Xdxclw7Cij{xbBMBn-LMII#Slp`AElb&2^Hw+w>(3crLH!;I zN+Vk$D+wP1#^!MDCiad@vM>H#6+`Ct#~6VHL4lzmy;lSdk>`z6)=>Wh15Q2)dQtGqvn0vJU@+(B5{MUc*qs4!T+V=q=wy)<6$~ z!G>e_4dN@lGeF_$q9`Ju6Ncb*x?O7=l{anm7Eahuj_6lA{*#Gv*TaJclevPVbbVYu z(NY?5q+xxbO6%g1xF0r@Ix8fJ~u)VRUp`S%&rN$&e!Od`~s+64J z5*)*WSi*i{k%JjMSIN#X;jC{HG$-^iX+5f5BGOIHWAl*%15Z#!xntpk($-EGKCzKa zT7{siZ9;4TICsWQ$pu&wKZQTCvpI$Xvzwxoi+XkkpeE&&kFb!B?h2hi%^YlXt|-@5 zHJ~%AN!g_^tmn1?HSm^|gCE#!GRtK2(L{9pL#hp0xh zME}|DB>(5)`iE7CM)&_+S}-Bslc#@B5W4_+k4Cp$l>iVyg$KP>CN?SVGZ(&02>iZK zB<^HP$g$Lq*L$BWd?2(F?-MUbNWTJVQdW7$#8a|k_30#vHAD1Z{c#p;bETk0VnU5A zBgLe2HFJ3032$G<`m*OB!KM$*sdM20jm)It5OSru@tXpK5LT>#8)N!*skNu1$TpIw zufjjdp#lyH5bZ%|Iuo|iu9vG1HrIVWLH>278xo>aVBkPN3V$~!=KnlXQ4eDqS7%E% zQ!z^$Q$b^6Q)g#cLpwur(|<0gWHo6A6jc;n`t(V9T;LzTAU{IAu*uEQ%Ort1k+Kn+f_N`9|bxYC+~Z1 zCC1UCWv*Orx$_@ydv9mIe(liLfOr7mhbV@tKw{6)q^1DH1nmvZ0cj215R<~&I<4S| zgnr;9Cdjqpz#o8i0CQjtl`}{c*P)aSdH|abxGdrR)-3z+02-eX(k*B)Uqv6~^nh** z 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 \(.*\)$'` + 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 000000000..8a0b282aa --- /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 000000000..d6e4f141c --- /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 000000000..84598b8d4 --- /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 000000000..b947bc7d0 --- /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 000000000..86d1d50f6 --- /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 000000000..0136183ed --- /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 000000000..124fe8509 --- /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 000000000..254597105 --- /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 000000000..5215a91bb --- /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 000000000..87260c2a9 --- /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 000000000..d20cccd47 --- /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 000000000..1263ac56e --- /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 000000000..0f1961d24 --- /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 000000000..468d4713d --- /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 000000000..906bec14f --- /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 000000000..b42f729c9 --- /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 000000000..f11f7450a --- /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 000000000..0959cbb33 --- /dev/null +++ b/android/java/settings.gradle @@ -0,0 +1,2 @@ +include ':app' +include ':lib'