Merge remote-tracking branch 'origin/master'
13
OsmAndCore-sample/.gitignore
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
# Android Studio
|
||||
/.idea
|
||||
*.iml
|
||||
|
||||
# Gradle
|
||||
.gradle
|
||||
/local.properties
|
||||
|
||||
# MacOSX
|
||||
.DS_Store
|
||||
|
||||
# Output
|
||||
/build
|
24
OsmAndCore-sample/AndroidManifest.xml
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="net.osmand.core.samples.android.sample1" >
|
||||
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/sample_app"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/title_activity_main">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
92
OsmAndCore-sample/build.gradle
Normal file
|
@ -0,0 +1,92 @@
|
|||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "23.0.3"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 23
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
|
||||
// This is from OsmAndCore_android.aar - for some reason it's not inherited
|
||||
aaptOptions {
|
||||
// Don't compress any embedded resources
|
||||
noCompress "qz"
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
manifest.srcFile "AndroidManifest.xml"
|
||||
jni.srcDirs = []
|
||||
jniLibs.srcDirs = ["libs"]
|
||||
java.srcDirs = ["src"]
|
||||
resources.srcDirs = ["src"]
|
||||
renderscript.srcDirs = ["src"]
|
||||
res.srcDirs = ["res"]
|
||||
assets.srcDirs = ["assets"]
|
||||
}
|
||||
}
|
||||
|
||||
productFlavors {
|
||||
x86 {
|
||||
ndk {
|
||||
abiFilter "x86"
|
||||
}
|
||||
}
|
||||
mips {
|
||||
ndk {
|
||||
abiFilter "mips"
|
||||
}
|
||||
}
|
||||
armv7 {
|
||||
ndk {
|
||||
abiFilter "armeabi-v7a"
|
||||
}
|
||||
}
|
||||
armv5 {
|
||||
ndk {
|
||||
abiFilter "armeabi"
|
||||
}
|
||||
}
|
||||
fat
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
signingConfig android.signingConfigs.debug
|
||||
}
|
||||
nativeDebug {
|
||||
signingConfig android.signingConfigs.debug
|
||||
}
|
||||
release {
|
||||
signingConfig android.signingConfigs.debug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
ivy {
|
||||
name = "OsmAndBinariesIvy"
|
||||
url = "http://builder.osmand.net"
|
||||
layout "pattern" , {
|
||||
artifact "ivy/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(path: ':OsmAnd-java', configuration: 'android')
|
||||
compile 'com.android.support:appcompat-v7:23.3.0'
|
||||
compile fileTree(dir: "libs", include: ["*.jar"])
|
||||
compile "net.osmand:OsmAndCore_android:0.1-SNAPSHOT@aar"
|
||||
debugCompile "net.osmand:OsmAndCore_androidNativeRelease:0.1-SNAPSHOT@aar"
|
||||
nativeDebugCompile "net.osmand:OsmAndCore_androidNativeDebug:0.1-SNAPSHOT@aar"
|
||||
releaseCompile "net.osmand:OsmAndCore_androidNativeRelease:0.1-SNAPSHOT@aar"
|
||||
}
|
BIN
OsmAndCore-sample/res/drawable-hdpi/bg_contextmenu_shadow.9.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
OsmAndCore-sample/res/drawable-hdpi/ic_action_remove_dark.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
OsmAndCore-sample/res/drawable-hdpi/map_bt_round_1_shadow.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
OsmAndCore-sample/res/drawable-hdpi/map_compass_niu.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
OsmAndCore-sample/res/drawable-hdpi/map_zoom_in.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
OsmAndCore-sample/res/drawable-hdpi/map_zoom_out.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
OsmAndCore-sample/res/drawable-mdpi/bg_contextmenu_shadow.9.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
OsmAndCore-sample/res/drawable-mdpi/ic_action_remove_dark.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
OsmAndCore-sample/res/drawable-mdpi/map_bt_round_1_shadow.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
OsmAndCore-sample/res/drawable-mdpi/map_compass_niu.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
OsmAndCore-sample/res/drawable-mdpi/map_zoom_in.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
OsmAndCore-sample/res/drawable-mdpi/map_zoom_out.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
OsmAndCore-sample/res/drawable-xhdpi/bg_contextmenu_shadow.9.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
OsmAndCore-sample/res/drawable-xhdpi/ic_action_remove_dark.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
OsmAndCore-sample/res/drawable-xhdpi/map_bt_round_1_shadow.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
OsmAndCore-sample/res/drawable-xhdpi/map_compass_niu.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
OsmAndCore-sample/res/drawable-xhdpi/map_zoom_in.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
OsmAndCore-sample/res/drawable-xhdpi/map_zoom_out.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.1 KiB |
BIN
OsmAndCore-sample/res/drawable-xxhdpi/ic_action_remove_dark.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
OsmAndCore-sample/res/drawable-xxhdpi/map_bt_round_1_shadow.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
OsmAndCore-sample/res/drawable-xxhdpi/map_compass_niu.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
OsmAndCore-sample/res/drawable-xxhdpi/map_zoom_in.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
OsmAndCore-sample/res/drawable-xxhdpi/map_zoom_out.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
5
OsmAndCore-sample/res/drawable/btn_circle.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/btn_circle_p" android:state_pressed="true"/>
|
||||
<item android:drawable="@drawable/btn_circle_n"></item>
|
||||
</selector>
|
22
OsmAndCore-sample/res/drawable/btn_circle_n.xml
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<item>
|
||||
<bitmap
|
||||
android:gravity="fill"
|
||||
android:src="@drawable/map_bt_round_1_shadow" />
|
||||
</item>
|
||||
<item>
|
||||
|
||||
<inset
|
||||
android:insetBottom="2dp"
|
||||
android:insetLeft="2dp"
|
||||
android:insetRight="2dp"
|
||||
android:insetTop="2dp" >
|
||||
<shape android:shape="oval" >
|
||||
<solid android:color="#fff" />
|
||||
</shape>
|
||||
</inset>
|
||||
</item>
|
||||
|
||||
</layer-list>
|
21
OsmAndCore-sample/res/drawable/btn_circle_p.xml
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
<item>
|
||||
<bitmap
|
||||
android:gravity="fill"
|
||||
android:src="@drawable/map_bt_round_1_shadow" />
|
||||
</item>
|
||||
<item>
|
||||
|
||||
<inset
|
||||
android:insetBottom="2dp"
|
||||
android:insetLeft="2dp"
|
||||
android:insetRight="2dp"
|
||||
android:insetTop="2dp" >
|
||||
<shape android:shape="oval" >
|
||||
<solid android:color="#fff" />
|
||||
</shape>
|
||||
</inset>
|
||||
</item>
|
||||
</layer-list>
|
120
OsmAndCore-sample/res/layout/activity_main.xml
Executable file
|
@ -0,0 +1,120 @@
|
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context="net.osmand.core.samples.android.sample1.MainActivity">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/colorPrimary"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/clearButton"
|
||||
style="@style/Widget.AppCompat.ActionButton"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:contentDescription="@string/shared_string_close"
|
||||
android:src="@drawable/ic_action_remove_dark"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/searchEditText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:background="@null"
|
||||
android:hint="@string/search_hint"
|
||||
android:gravity="center_vertical"
|
||||
android:lines="1"
|
||||
android:textColor="@color/titleTextColor"
|
||||
tools:text="Search request"/>
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:foreground="@drawable/bg_contextmenu_shadow"
|
||||
android:foregroundGravity="top|fill_horizontal">
|
||||
|
||||
<net.osmand.core.android.AtlasMapRendererView
|
||||
android:id="@+id/mapRendererView"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|right"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_marginTop="10dp">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/map_azimuth_north_button"
|
||||
android:layout_width="52dp"
|
||||
android:layout_height="52dp"
|
||||
android:background="@drawable/btn_circle"
|
||||
android:contentDescription="@string/azimuthNorth"
|
||||
android:src="@drawable/map_compass_niu"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|right"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_marginRight="10dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_zoom"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="10"
|
||||
android:textColor="#000"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/map_zoom_in_button"
|
||||
android:layout_width="52dp"
|
||||
android:layout_height="52dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:background="@drawable/btn_circle"
|
||||
android:contentDescription="@string/zoomIn"
|
||||
android:src="@drawable/map_zoom_in"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/map_zoom_out_button"
|
||||
android:layout_width="52dp"
|
||||
android:layout_height="52dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:background="@drawable/btn_circle"
|
||||
android:contentDescription="@string/zoomOut"
|
||||
android:src="@drawable/map_zoom_out"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<ListView
|
||||
android:id="@android:id/list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:drawSelectorOnTop="true"
|
||||
android:background="@color/listBackgroundColor"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
58
OsmAndCore-sample/res/layout/search_list_item.xml
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?attr/listPreferredItemHeight"
|
||||
android:orientation="horizontal"
|
||||
android:paddingBottom="12dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingTop="8dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:paddingRight="16dp"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
|
||||
tools:text="Amsterdam"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/distance"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="right"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
tools:text="100 km"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subtitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
tools:text="City"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
BIN
OsmAndCore-sample/res/mipmap-hdpi/sample_app.png
Executable file
After Width: | Height: | Size: 8 KiB |
BIN
OsmAndCore-sample/res/mipmap-mdpi/sample_app.png
Executable file
After Width: | Height: | Size: 4.8 KiB |
BIN
OsmAndCore-sample/res/mipmap-xhdpi/sample_app.png
Executable file
After Width: | Height: | Size: 11 KiB |
BIN
OsmAndCore-sample/res/mipmap-xxhdpi/sample_app.png
Executable file
After Width: | Height: | Size: 21 KiB |
BIN
OsmAndCore-sample/res/mipmap-xxxhdpi/sample_app.png
Executable file
After Width: | Height: | Size: 41 KiB |
6
OsmAndCore-sample/res/values-w820dp/dimens.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
|
||||
(such as screen margins) for screens with more than 820dp of available width. This
|
||||
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
|
||||
<dimen name="activity_horizontal_margin">64dp</dimen>
|
||||
</resources>
|
17
OsmAndCore-sample/res/values/colors.xml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#ff8f00</color>
|
||||
<color name="colorPrimaryDark">#e68200</color>
|
||||
<color name="colorAccent">#FF4081</color>
|
||||
|
||||
<color name="dividerColor">#F0F0F0</color>
|
||||
<color name="windowBackgroundColor">#EAEAEA</color>
|
||||
<color name="itemBackgroundColor">#FFF</color>
|
||||
<color name="iconColor">#727272</color>
|
||||
<color name="dialogTitleBackgroundColor">#2f7af5</color>
|
||||
|
||||
<color name="listBackgroundColor">#EAEAEA</color>
|
||||
<color name="titleTextColor">#FFF</color>
|
||||
<color name="listTextColor">#000</color>
|
||||
|
||||
</resources>
|
6
OsmAndCore-sample/res/values/dimens.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
<dimen name="list_content_padding">16dp</dimen>
|
||||
</resources>
|
10
OsmAndCore-sample/res/values/strings.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">OsmAnd Core API sample 1 for Android OS</string>
|
||||
<string name="title_activity_main">OsmAnd Core Sample 1</string>
|
||||
<string name="zoomIn">Zoom in</string>
|
||||
<string name="zoomOut">Zoom out</string>
|
||||
<string name="azimuthNorth">Azimuth to North</string>
|
||||
<string name="search_hint">Type and Search</string>
|
||||
<string name="shared_string_close">Close</string>
|
||||
</resources>
|
11
OsmAndCore-sample/res/values/styles.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<resources>
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
|
||||
<item name="android:windowBackground">@color/windowBackgroundColor</item>
|
||||
<item name="android:colorBackground">@color/windowBackgroundColor</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
|
@ -0,0 +1,546 @@
|
|||
package net.osmand.core.samples.android.sample1;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.graphics.PointF;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v4.view.ViewPropertyAnimatorListener;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.GestureDetector.SimpleOnGestureListener;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import net.osmand.core.android.AtlasMapRendererView;
|
||||
import net.osmand.core.android.CoreResourcesFromAndroidAssets;
|
||||
import net.osmand.core.android.NativeCore;
|
||||
import net.osmand.core.jni.AreaI;
|
||||
import net.osmand.core.jni.IMapLayerProvider;
|
||||
import net.osmand.core.jni.IMapStylesCollection;
|
||||
import net.osmand.core.jni.LatLon;
|
||||
import net.osmand.core.jni.LogSeverityLevel;
|
||||
import net.osmand.core.jni.Logger;
|
||||
import net.osmand.core.jni.MapObjectsSymbolsProvider;
|
||||
import net.osmand.core.jni.MapPresentationEnvironment;
|
||||
import net.osmand.core.jni.MapPrimitivesProvider;
|
||||
import net.osmand.core.jni.MapPrimitiviser;
|
||||
import net.osmand.core.jni.MapRasterLayerProvider_Software;
|
||||
import net.osmand.core.jni.MapStylesCollection;
|
||||
import net.osmand.core.jni.ObfMapObjectsProvider;
|
||||
import net.osmand.core.jni.ObfsCollection;
|
||||
import net.osmand.core.jni.PointI;
|
||||
import net.osmand.core.jni.QIODeviceLogSink;
|
||||
import net.osmand.core.jni.ResolvedMapStyle;
|
||||
import net.osmand.core.jni.Utilities;
|
||||
import net.osmand.core.samples.android.sample1.MultiTouchSupport.MultiTouchZoomListener;
|
||||
import net.osmand.core.samples.android.sample1.SearchAPI.SearchAPICallback;
|
||||
import net.osmand.core.samples.android.sample1.SearchAPI.SearchItem;
|
||||
import net.osmand.core.samples.android.sample1.SearchUIHelper.SearchListAdapter;
|
||||
import net.osmand.core.samples.android.sample1.SearchUIHelper.SearchRow;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public class MainActivity extends Activity {
|
||||
private static final String TAG = "OsmAndCoreSample";
|
||||
|
||||
private float displayDensityFactor;
|
||||
private int referenceTileSize;
|
||||
private int rasterTileSize;
|
||||
private IMapStylesCollection mapStylesCollection;
|
||||
private ResolvedMapStyle mapStyle;
|
||||
private ObfsCollection obfsCollection;
|
||||
private MapPresentationEnvironment mapPresentationEnvironment;
|
||||
private MapPrimitiviser mapPrimitiviser;
|
||||
private ObfMapObjectsProvider obfMapObjectsProvider;
|
||||
private MapPrimitivesProvider mapPrimitivesProvider;
|
||||
private MapObjectsSymbolsProvider mapObjectsSymbolsProvider;
|
||||
private IMapLayerProvider mapLayerProvider0;
|
||||
private IMapLayerProvider mapLayerProvider1;
|
||||
private QIODeviceLogSink fileLogSink;
|
||||
|
||||
private AtlasMapRendererView mapView;
|
||||
private TextView textZoom;
|
||||
private ImageButton azimuthNorthButton;
|
||||
|
||||
private GestureDetector gestureDetector;
|
||||
private PointI target31;
|
||||
private float zoom;
|
||||
private float azimuth;
|
||||
private float elevationAngle;
|
||||
private MultiTouchSupport multiTouchSupport;
|
||||
|
||||
private SearchAPI searchAPI;
|
||||
private ListView searchListView;
|
||||
private SearchListAdapter adapter;
|
||||
private final static int MAX_SEARCH_RESULTS = 50;
|
||||
|
||||
// Germany
|
||||
private final static float INIT_LAT = 49.353953f;
|
||||
private final static float INIT_LON = 11.214384f;
|
||||
// Kyiv
|
||||
//private final static float INIT_LAT = 50.450117f;
|
||||
//private final static float INIT_LON = 30.524142f;
|
||||
private final static float INIT_ZOOM = 6.0f;
|
||||
private final static float INIT_AZIMUTH = 0.0f;
|
||||
private final static float INIT_ELEVATION_ANGLE = 90.0f;
|
||||
private final static int MIN_ZOOM_LEVEL = 2;
|
||||
private final static int MAX_ZOOM_LEVEL = 22;
|
||||
|
||||
private static final String PREF_MAP_CENTER_LAT = "MAP_CENTER_LAT";
|
||||
private static final String PREF_MAP_CENTER_LON = "MAP_CENTER_LON";
|
||||
private static final String PREF_MAP_AZIMUTH = "MAP_AZIMUTH";
|
||||
private static final String PREF_MAP_ZOOM = "MAP_ZOOM";
|
||||
private static final String PREF_MAP_ELEVATION_ANGLE = "MAP_ELEVATION_ANGLE";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
gestureDetector = new GestureDetector(this, new MapViewOnGestureListener());
|
||||
multiTouchSupport = new MultiTouchSupport(this, new MapViewMultiTouchZoomListener());
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
// Initialize native core prior (if needed)
|
||||
if (NativeCore.isAvailable() && !NativeCore.isLoaded())
|
||||
NativeCore.load(CoreResourcesFromAndroidAssets.loadFromCurrentApplication(this));
|
||||
|
||||
Logger.get().setSeverityLevelThreshold(LogSeverityLevel.Debug);
|
||||
|
||||
// Inflate views
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
// Get map view
|
||||
mapView = (AtlasMapRendererView) findViewById(R.id.mapRendererView);
|
||||
|
||||
textZoom = (TextView) findViewById(R.id.text_zoom);
|
||||
azimuthNorthButton = (ImageButton) findViewById(R.id.map_azimuth_north_button);
|
||||
|
||||
azimuthNorthButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
setAzimuth(0f);
|
||||
}
|
||||
});
|
||||
|
||||
findViewById(R.id.map_zoom_in_button).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
setZoom(zoom + 1f);
|
||||
}
|
||||
});
|
||||
|
||||
findViewById(R.id.map_zoom_out_button).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
setZoom(zoom - 1f);
|
||||
}
|
||||
});
|
||||
|
||||
// Additional log sink
|
||||
fileLogSink = QIODeviceLogSink.createFileLogSink(
|
||||
Environment.getExternalStorageDirectory() + "/osmand/osmandcore.log");
|
||||
Logger.get().addLogSink(fileLogSink);
|
||||
|
||||
// Get device display density factor
|
||||
DisplayMetrics displayMetrics = new DisplayMetrics();
|
||||
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
|
||||
displayDensityFactor = displayMetrics.densityDpi / 160.0f;
|
||||
referenceTileSize = (int)(256 * displayDensityFactor);
|
||||
rasterTileSize = Integer.highestOneBit(referenceTileSize - 1) * 2;
|
||||
Log.i(TAG, "displayDensityFactor = " + displayDensityFactor);
|
||||
Log.i(TAG, "referenceTileSize = " + referenceTileSize);
|
||||
Log.i(TAG, "rasterTileSize = " + rasterTileSize);
|
||||
|
||||
Log.i(TAG, "Going to resolve default embedded style...");
|
||||
mapStylesCollection = new MapStylesCollection();
|
||||
mapStyle = mapStylesCollection.getResolvedStyleByName("default");
|
||||
if (mapStyle == null)
|
||||
{
|
||||
Log.e(TAG, "Failed to resolve style 'default'");
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
Log.i(TAG, "Going to prepare OBFs collection");
|
||||
obfsCollection = new ObfsCollection();
|
||||
Log.i(TAG, "Will load OBFs from " + Environment.getExternalStorageDirectory() + "/osmand");
|
||||
obfsCollection.addDirectory(Environment.getExternalStorageDirectory() + "/osmand", false);
|
||||
|
||||
Log.i(TAG, "Going to prepare all resources for renderer");
|
||||
mapPresentationEnvironment = new MapPresentationEnvironment(
|
||||
mapStyle,
|
||||
displayDensityFactor,
|
||||
1.0f,
|
||||
1.0f,
|
||||
MapUtils.LANGUAGE);
|
||||
//mapPresentationEnvironment->setSettings(configuration.styleSettings);
|
||||
mapPrimitiviser = new MapPrimitiviser(
|
||||
mapPresentationEnvironment);
|
||||
obfMapObjectsProvider = new ObfMapObjectsProvider(
|
||||
obfsCollection);
|
||||
mapPrimitivesProvider = new MapPrimitivesProvider(
|
||||
obfMapObjectsProvider,
|
||||
mapPrimitiviser,
|
||||
rasterTileSize);
|
||||
mapObjectsSymbolsProvider = new MapObjectsSymbolsProvider(
|
||||
mapPrimitivesProvider,
|
||||
rasterTileSize);
|
||||
|
||||
mapView.setReferenceTileSizeOnScreenInPixels(referenceTileSize);
|
||||
mapView.addSymbolsProvider(mapObjectsSymbolsProvider);
|
||||
|
||||
restoreMapState();
|
||||
|
||||
mapLayerProvider0 = new MapRasterLayerProvider_Software(mapPrimitivesProvider);
|
||||
mapView.setMapLayerProvider(0, mapLayerProvider0);
|
||||
|
||||
System.out.println("NATIVE_INITIALIZED = " + (System.currentTimeMillis() - startTime) / 1000f);
|
||||
|
||||
//Setup search
|
||||
|
||||
searchAPI = new SearchAPI(obfsCollection);
|
||||
|
||||
final EditText searchEditText = (EditText) findViewById(R.id.searchEditText);
|
||||
searchEditText.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
if (s.length() > 2) {
|
||||
runSearch(getScreenBounds31(), s.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
searchEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
if (hasFocus && adapter.getCount() > 0 && isSearchListHidden()) {
|
||||
showSearchList();
|
||||
LatLon latLon = Utilities.convert31ToLatLon(target31);
|
||||
adapter.updateDistance(latLon.getLatitude(), latLon.getLongitude());
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ImageButton clearButton = (ImageButton) findViewById(R.id.clearButton);
|
||||
clearButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
searchEditText.setText("");
|
||||
adapter.clear();
|
||||
adapter.notifyDataSetChanged();
|
||||
hideSearchList();
|
||||
}
|
||||
});
|
||||
|
||||
searchListView = (ListView) findViewById(android.R.id.list);
|
||||
adapter = new SearchListAdapter(this);
|
||||
searchListView.setAdapter(adapter);
|
||||
searchListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
hideSearchList();
|
||||
mapView.requestFocus();
|
||||
SearchRow item = adapter.getItem(position);
|
||||
PointI target = Utilities.convertLatLonTo31(new LatLon(item.getLatitude(), item.getLongitude()));
|
||||
setTarget(target);
|
||||
setZoom(17f);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
mapView.handleOnResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
saveMapState();
|
||||
mapView.handleOnPause();
|
||||
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
mapView.handleOnDestroy();
|
||||
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
private AreaI getScreenBounds31() {
|
||||
PointI topLeftPoint = new PointI();
|
||||
PointI bottomRightPoint = new PointI();
|
||||
mapView.getLocationFromScreenPoint(new PointI(0, 0), topLeftPoint);
|
||||
mapView.getLocationFromScreenPoint(new PointI(mapView.getWidth(), mapView.getHeight()), bottomRightPoint);
|
||||
return new AreaI(topLeftPoint, bottomRightPoint);
|
||||
}
|
||||
|
||||
private boolean isSearchListHidden() {
|
||||
return searchListView.getVisibility() != View.VISIBLE;
|
||||
}
|
||||
|
||||
private void showSearchList() {
|
||||
if (isSearchListHidden()) {
|
||||
ViewCompat.setAlpha(searchListView, 0f);
|
||||
searchListView.setVisibility(View.VISIBLE);
|
||||
ViewCompat.animate(searchListView).alpha(1f).setListener(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void hideSearchList() {
|
||||
ViewCompat.animate(searchListView).alpha(0f).setListener(new ViewPropertyAnimatorListener() {
|
||||
@Override
|
||||
public void onAnimationStart(View view) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(View view) {
|
||||
searchListView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(View view) {
|
||||
searchListView.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void saveMapState() {
|
||||
SharedPreferences prefs = getPreferences(MODE_PRIVATE);
|
||||
Editor edit = prefs.edit();
|
||||
LatLon latLon = Utilities.convert31ToLatLon(target31);
|
||||
edit.putFloat(PREF_MAP_CENTER_LAT, (float)latLon.getLatitude());
|
||||
edit.putFloat(PREF_MAP_CENTER_LON, (float)latLon.getLongitude());
|
||||
edit.putFloat(PREF_MAP_AZIMUTH, azimuth);
|
||||
edit.putFloat(PREF_MAP_ZOOM, zoom);
|
||||
edit.putFloat(PREF_MAP_ELEVATION_ANGLE, elevationAngle);
|
||||
edit.commit();
|
||||
}
|
||||
|
||||
public void restoreMapState() {
|
||||
SharedPreferences prefs = getPreferences(MODE_PRIVATE);
|
||||
float prefLat = prefs.getFloat(PREF_MAP_CENTER_LAT, INIT_LAT);
|
||||
float prefLon = prefs.getFloat(PREF_MAP_CENTER_LON, INIT_LON);
|
||||
float prefAzimuth = prefs.getFloat(PREF_MAP_AZIMUTH, INIT_AZIMUTH);
|
||||
float prefZoom = prefs.getFloat(PREF_MAP_ZOOM, INIT_ZOOM);
|
||||
float prefElevationAngle = prefs.getFloat(PREF_MAP_ELEVATION_ANGLE, INIT_ELEVATION_ANGLE);
|
||||
|
||||
setAzimuth(prefAzimuth);
|
||||
setElevationAngle(prefElevationAngle);
|
||||
setTarget(Utilities.convertLatLonTo31(new LatLon(prefLat, prefLon)));
|
||||
setZoom(prefZoom);
|
||||
}
|
||||
|
||||
public boolean setTarget(PointI pointI) {
|
||||
target31 = pointI;
|
||||
return mapView.setTarget(pointI);
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
public boolean setZoom(float zoom) {
|
||||
if (zoom < MIN_ZOOM_LEVEL) {
|
||||
zoom = MIN_ZOOM_LEVEL;
|
||||
} else if (zoom > MAX_ZOOM_LEVEL) {
|
||||
zoom = MAX_ZOOM_LEVEL;
|
||||
}
|
||||
this.zoom = zoom;
|
||||
textZoom.setText(String.format("%.0f", zoom));
|
||||
return mapView.setZoom(zoom);
|
||||
}
|
||||
|
||||
public void setAzimuth(float angle) {
|
||||
angle = MapUtils.unifyRotationTo360(angle);
|
||||
this.azimuth = angle;
|
||||
mapView.setAzimuth(angle);
|
||||
|
||||
if (angle == 0f && azimuthNorthButton.getVisibility() == View.VISIBLE) {
|
||||
azimuthNorthButton.setVisibility(View.INVISIBLE);
|
||||
} else if (angle != 0f && azimuthNorthButton.getVisibility() == View.INVISIBLE) {
|
||||
azimuthNorthButton.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
public void setElevationAngle(float angle) {
|
||||
if (angle < 35f) {
|
||||
angle = 35f;
|
||||
} else if (angle > 90f) {
|
||||
angle = 90f;
|
||||
}
|
||||
this.elevationAngle = angle;
|
||||
mapView.setElevationAngle(angle);
|
||||
}
|
||||
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
return multiTouchSupport.onTouchEvent(event)
|
||||
|| gestureDetector.onTouchEvent(event);
|
||||
}
|
||||
|
||||
private void runSearch(AreaI bounds31, String keyword) {
|
||||
|
||||
searchAPI.setObfAreaFilter(bounds31);
|
||||
searchAPI.startSearch(keyword, MAX_SEARCH_RESULTS, new SearchAPICallback() {
|
||||
@Override
|
||||
public void onSearchFinished(List<SearchItem> searchItems, boolean cancelled) {
|
||||
if (searchItems != null && !cancelled) {
|
||||
LatLon latLon = Utilities.convert31ToLatLon(target31);
|
||||
List<SearchRow> rows = new ArrayList<>();
|
||||
for (SearchItem item : searchItems) {
|
||||
SearchRow row = new SearchRow(item);
|
||||
rows.add(row);
|
||||
}
|
||||
|
||||
adapter.clear();
|
||||
adapter.addAll(rows);
|
||||
adapter.updateDistance(latLon.getLatitude(), latLon.getLongitude());
|
||||
adapter.sort(new Comparator<SearchRow>() {
|
||||
@Override
|
||||
public int compare(SearchRow lhs, SearchRow rhs) {
|
||||
int res = Double.compare(lhs.getDistance(), rhs.getDistance());
|
||||
if (res == 0) {
|
||||
return lhs.getSearchItem().getLocalizedName().compareToIgnoreCase(rhs.getSearchItem().getLocalizedName());
|
||||
} else {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
});
|
||||
adapter.notifyDataSetChanged();
|
||||
if (adapter.getCount() > 0) {
|
||||
searchListView.setSelection(0);
|
||||
}
|
||||
|
||||
showSearchList();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private class MapViewOnGestureListener extends SimpleOnGestureListener {
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapUp(MotionEvent e) {
|
||||
mapView.requestFocus();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
||||
float fromX = e2.getX() + distanceX;
|
||||
float fromY = e2.getY() + distanceY;
|
||||
float toX = e2.getX();
|
||||
float toY = e2.getY();
|
||||
|
||||
float dx = (fromX - toX);
|
||||
float dy = (fromY - toY);
|
||||
|
||||
PointI newTarget = new PointI();
|
||||
mapView.getLocationFromScreenPoint(new PointI(mapView.getWidth() / 2 + (int)dx, mapView.getHeight() / 2 + (int)dy), newTarget);
|
||||
|
||||
setTarget(newTarget);
|
||||
|
||||
mapView.requestFocus();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private class MapViewMultiTouchZoomListener implements MultiTouchZoomListener {
|
||||
|
||||
private float initialZoom;
|
||||
private float initialAzimuth;
|
||||
private float initialElevation;
|
||||
private PointF centerPoint;
|
||||
|
||||
@Override
|
||||
public void onGestureFinished(float scale, float rotation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGestureInit(float x1, float y1, float x2, float y2) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onZoomStarted(PointF centerPoint) {
|
||||
initialZoom = zoom;
|
||||
initialAzimuth = azimuth;
|
||||
this.centerPoint = centerPoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onZoomingOrRotating(float scale, float rotation) {
|
||||
|
||||
PointI centerLocationBefore = new PointI();
|
||||
mapView.getLocationFromScreenPoint(
|
||||
new PointI((int)centerPoint.x, (int)centerPoint.y), centerLocationBefore);
|
||||
|
||||
// Change zoom
|
||||
setZoom(initialZoom + (float)(Math.log(scale) / Math.log(2)));
|
||||
|
||||
// Adjust current target position to keep touch center the same
|
||||
PointI centerLocationAfter = new PointI();
|
||||
mapView.getLocationFromScreenPoint(
|
||||
new PointI((int)centerPoint.x, (int)centerPoint.y), centerLocationAfter);
|
||||
PointI centerLocationDelta = new PointI(
|
||||
centerLocationAfter.getX() - centerLocationBefore.getX(),
|
||||
centerLocationAfter.getY() - centerLocationBefore.getY());
|
||||
|
||||
setTarget(new PointI(target31.getX() - centerLocationDelta.getX(), target31.getY() - centerLocationDelta.getY()));
|
||||
|
||||
/*
|
||||
// Convert point from screen to location
|
||||
PointI centerLocation = new PointI();
|
||||
mapView.getLocationFromScreenPoint(
|
||||
new PointI((int)centerPoint.x, (int)centerPoint.y), centerLocation);
|
||||
|
||||
// Rotate current target around center location
|
||||
PointI target = new PointI(xI - centerLocation.getX(), yI - centerLocation.getY());
|
||||
double cosAngle = Math.cos(-Math.toRadians(rotation));
|
||||
double sinAngle = Math.sin(-Math.toRadians(rotation));
|
||||
|
||||
PointI newTarget = new PointI(
|
||||
(int)(target.getX() * cosAngle - target.getY() * sinAngle + centerLocation.getX()),
|
||||
(int)(target.getX() * sinAngle + target.getY() * cosAngle + centerLocation.getY()));
|
||||
|
||||
setTarget(newTarget);
|
||||
*/
|
||||
|
||||
// Set rotation
|
||||
setAzimuth(initialAzimuth - rotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChangeViewAngleStarted() {
|
||||
initialElevation = elevationAngle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChangingViewAngle(float angle) {
|
||||
setElevationAngle(initialElevation - angle);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package net.osmand.core.samples.android.sample1;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class MapUtils {
|
||||
|
||||
public static final String LANGUAGE;
|
||||
|
||||
static {
|
||||
LANGUAGE = getLanguage();
|
||||
}
|
||||
|
||||
public static float unifyRotationTo360(float rotate) {
|
||||
while (rotate < -180) {
|
||||
rotate += 360;
|
||||
}
|
||||
while (rotate > +180) {
|
||||
rotate -= 360;
|
||||
}
|
||||
return rotate;
|
||||
}
|
||||
|
||||
private static String getLanguage() {
|
||||
String langCode = Locale.getDefault().getLanguage();
|
||||
if (langCode.isEmpty()) {
|
||||
langCode = "en";
|
||||
}
|
||||
return langCode;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
package net.osmand.core.samples.android.sample1;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.PointF;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class MultiTouchSupport {
|
||||
|
||||
private static final String TAG = "MultiTouchSupport";
|
||||
|
||||
public static final int ACTION_MASK = 255;
|
||||
protected final Context ctx;
|
||||
private final MultiTouchZoomListener listener;
|
||||
protected Method getPointerCount;
|
||||
protected Method getX;
|
||||
protected Method getY;
|
||||
protected Method getPointerId;
|
||||
|
||||
private float initialAngle;
|
||||
private float rotation;
|
||||
private static final float ROTATION_THRESHOLD_DEG = 15.0f;
|
||||
private boolean isRotating;
|
||||
|
||||
private boolean multiTouchAPISupported = false;
|
||||
|
||||
private boolean inTiltMode = false;
|
||||
private PointF firstFingerStart = new PointF();
|
||||
private PointF secondFingerStart = new PointF();
|
||||
private static final int TILT_X_THRESHOLD_PX = 40;
|
||||
private static final int TILT_Y_THRESHOLD_PX = 40;
|
||||
private static final int TILT_DY_THRESHOLD_PX = 40;
|
||||
|
||||
private boolean inZoomMode = false;
|
||||
private float initialDistance = 100;
|
||||
private float scale = 1;
|
||||
private PointF centerPoint = new PointF();
|
||||
|
||||
private boolean multiTouch;
|
||||
|
||||
public MultiTouchSupport(Context ctx, MultiTouchZoomListener listener) {
|
||||
this.ctx = ctx;
|
||||
this.listener = listener;
|
||||
initMethods();
|
||||
}
|
||||
|
||||
public boolean isMultiTouchSupported(){
|
||||
return multiTouchAPISupported;
|
||||
}
|
||||
|
||||
public boolean isInZoomMode(){
|
||||
return inZoomMode;
|
||||
}
|
||||
|
||||
public boolean isInTiltMode() {
|
||||
return inTiltMode;
|
||||
}
|
||||
|
||||
private void initMethods() {
|
||||
try {
|
||||
getPointerCount = MotionEvent.class.getMethod("getPointerCount"); //$NON-NLS-1$
|
||||
getPointerId = MotionEvent.class.getMethod("getPointerId", Integer.TYPE); //$NON-NLS-1$
|
||||
getX = MotionEvent.class.getMethod("getX", Integer.TYPE); //$NON-NLS-1$
|
||||
getY = MotionEvent.class.getMethod("getY", Integer.TYPE); //$NON-NLS-1$
|
||||
multiTouchAPISupported = true;
|
||||
} catch (Exception e) {
|
||||
multiTouchAPISupported = false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onTouchEvent(MotionEvent event){
|
||||
if(!isMultiTouchSupported()){
|
||||
return false;
|
||||
}
|
||||
int actionCode = event.getAction() & ACTION_MASK;
|
||||
try {
|
||||
if (actionCode == MotionEvent.ACTION_UP || actionCode == MotionEvent.ACTION_CANCEL) {
|
||||
multiTouch = false;
|
||||
}
|
||||
Integer pointCount = (Integer) getPointerCount.invoke(event);
|
||||
if (pointCount < 2) {
|
||||
if (inZoomMode || inTiltMode) {
|
||||
listener.onGestureFinished(scale, rotation);
|
||||
inZoomMode = false;
|
||||
inTiltMode = false;
|
||||
}
|
||||
return multiTouch;
|
||||
} else {
|
||||
multiTouch = true;
|
||||
}
|
||||
|
||||
Float x1 = (Float) getX.invoke(event, 0);
|
||||
Float x2 = (Float) getX.invoke(event, 1);
|
||||
Float y1 = (Float) getY.invoke(event, 0);
|
||||
Float y2 = (Float) getY.invoke(event, 1);
|
||||
float distance = (float) Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
|
||||
float angle = 0;
|
||||
boolean angleDefined = false;
|
||||
if (x1.floatValue() != x2.floatValue() || y1.floatValue() != y2.floatValue()) {
|
||||
angleDefined = true;
|
||||
angle = (float) (Math.atan2(y2 - y1, x2 -x1) * 180 / Math.PI);
|
||||
}
|
||||
|
||||
switch (actionCode) {
|
||||
|
||||
case MotionEvent.ACTION_POINTER_DOWN: {
|
||||
|
||||
centerPoint = new PointF((x1 + x2) / 2, (y1 + y2) / 2);
|
||||
firstFingerStart = new PointF(x1, y1);
|
||||
secondFingerStart = new PointF(x2, y2);
|
||||
listener.onGestureInit(x1, y1, x2, y2);
|
||||
return true;
|
||||
}
|
||||
case MotionEvent.ACTION_POINTER_UP: {
|
||||
|
||||
if (inZoomMode || inTiltMode) {
|
||||
listener.onGestureFinished(scale, rotation);
|
||||
inZoomMode = false;
|
||||
inTiltMode = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
|
||||
if (inZoomMode) {
|
||||
if (angleDefined) {
|
||||
float a = MapUtils.unifyRotationTo360(angle - initialAngle);
|
||||
if (!isRotating && Math.abs(a) > ROTATION_THRESHOLD_DEG) {
|
||||
isRotating = true;
|
||||
initialAngle = angle;
|
||||
} else if (isRotating) {
|
||||
rotation = a;
|
||||
}
|
||||
}
|
||||
scale = distance / initialDistance;
|
||||
|
||||
listener.onZoomingOrRotating(scale, rotation);
|
||||
return true;
|
||||
|
||||
} else if (inTiltMode) {
|
||||
float dy2 = secondFingerStart.y - y2;
|
||||
float viewAngle = dy2 / 8f;
|
||||
listener.onChangingViewAngle(viewAngle);
|
||||
|
||||
} else {
|
||||
float dx1 = Math.abs(firstFingerStart.x - x1);
|
||||
float dx2 = Math.abs(secondFingerStart.x - x2);
|
||||
float dy1 = Math.abs(firstFingerStart.y - y1);
|
||||
float dy2 = Math.abs(secondFingerStart.y - y2);
|
||||
float startDy = Math.abs(secondFingerStart.y - firstFingerStart.y);
|
||||
if (dx1 < TILT_X_THRESHOLD_PX && dx2 < TILT_X_THRESHOLD_PX
|
||||
&& dy1 > TILT_Y_THRESHOLD_PX && dy2 > TILT_Y_THRESHOLD_PX
|
||||
&& startDy < TILT_Y_THRESHOLD_PX * 6
|
||||
&& Math.abs(dy2 - dy1) < TILT_DY_THRESHOLD_PX) {
|
||||
listener.onChangeViewAngleStarted();
|
||||
inTiltMode = true;
|
||||
|
||||
} else if (dx1 > TILT_X_THRESHOLD_PX || dx2 > TILT_X_THRESHOLD_PX
|
||||
|| Math.abs(dy2 - dy1) > TILT_DY_THRESHOLD_PX
|
||||
|| Math.abs(dy1 - dy2) > TILT_DY_THRESHOLD_PX) {
|
||||
listener.onZoomStarted(centerPoint);
|
||||
initialDistance = distance;
|
||||
initialAngle = angle;
|
||||
rotation = 0;
|
||||
scale = 0;
|
||||
isRotating = false;
|
||||
inZoomMode = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Multi touch exception" , e); //$NON-NLS-1$
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public PointF getCenterPoint() {
|
||||
return centerPoint;
|
||||
}
|
||||
|
||||
public interface MultiTouchZoomListener {
|
||||
|
||||
void onZoomStarted(PointF centerPoint);
|
||||
|
||||
void onZoomingOrRotating(float scale, float rotation);
|
||||
|
||||
void onChangeViewAngleStarted();
|
||||
|
||||
void onChangingViewAngle(float angle);
|
||||
|
||||
void onGestureFinished(float scale, float rotation);
|
||||
|
||||
void onGestureInit(float x1, float y1, float x2, float y2);
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,306 @@
|
|||
package net.osmand.core.samples.android.sample1;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import net.osmand.core.jni.AmenitiesByNameSearch;
|
||||
import net.osmand.core.jni.Amenity;
|
||||
import net.osmand.core.jni.Amenity.DecodedCategory;
|
||||
import net.osmand.core.jni.AreaI;
|
||||
import net.osmand.core.jni.DecodedCategoryList;
|
||||
import net.osmand.core.jni.IObfsCollection;
|
||||
import net.osmand.core.jni.IQueryController;
|
||||
import net.osmand.core.jni.ISearch;
|
||||
import net.osmand.core.jni.LatLon;
|
||||
import net.osmand.core.jni.NullableAreaI;
|
||||
import net.osmand.core.jni.ObfInfo;
|
||||
import net.osmand.core.jni.ObfsCollection;
|
||||
import net.osmand.core.jni.PointI;
|
||||
import net.osmand.core.jni.QStringStringHash;
|
||||
import net.osmand.core.jni.Utilities;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SearchAPI {
|
||||
|
||||
private ObfsCollection obfsCollection;
|
||||
private AreaI searchableArea;
|
||||
private AreaI obfAreaFilter;
|
||||
private SearchRequestExecutor executor;
|
||||
|
||||
interface SearchAPICallback {
|
||||
void onSearchFinished(List<SearchItem> searchItems, boolean cancelled);
|
||||
}
|
||||
|
||||
public SearchAPI(ObfsCollection obfsCollection) {
|
||||
this.obfsCollection = obfsCollection;
|
||||
executor = new SearchRequestExecutor();
|
||||
}
|
||||
|
||||
public AreaI getSearchableArea() {
|
||||
return searchableArea;
|
||||
}
|
||||
|
||||
public void setSearchableArea(AreaI searchableArea) {
|
||||
this.searchableArea = searchableArea;
|
||||
}
|
||||
|
||||
public AreaI getObfAreaFilter() {
|
||||
return obfAreaFilter;
|
||||
}
|
||||
|
||||
public void setObfAreaFilter(AreaI obfAreaFilter) {
|
||||
this.obfAreaFilter = obfAreaFilter;
|
||||
}
|
||||
|
||||
public void startSearch(String keyword, int maxSearchResults, SearchAPICallback apiCallback) {
|
||||
executor.run(new SearchRequest(keyword, maxSearchResults, apiCallback), true);
|
||||
}
|
||||
|
||||
public void cancelSearch() {
|
||||
executor.cancel();
|
||||
}
|
||||
|
||||
|
||||
public class SearchRequestExecutor {
|
||||
|
||||
private SearchRequest ongoingSearchRequest;
|
||||
private SearchRequest nextSearchRequest;
|
||||
|
||||
public void run(SearchRequest searchRequest, boolean cancelCurrentRequest) {
|
||||
if (ongoingSearchRequest != null) {
|
||||
nextSearchRequest = searchRequest;
|
||||
if (cancelCurrentRequest) {
|
||||
ongoingSearchRequest.cancel();
|
||||
}
|
||||
} else {
|
||||
ongoingSearchRequest = searchRequest;
|
||||
nextSearchRequest = null;
|
||||
searchRequest.setOnFinishedCallback(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
operationFinished();
|
||||
}
|
||||
});
|
||||
searchRequest.run();
|
||||
}
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
if (nextSearchRequest != null) {
|
||||
nextSearchRequest = null;
|
||||
}
|
||||
if (ongoingSearchRequest != null) {
|
||||
ongoingSearchRequest.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private void operationFinished() {
|
||||
ongoingSearchRequest = null;
|
||||
if (nextSearchRequest != null) {
|
||||
run(nextSearchRequest, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class SearchRequest {
|
||||
private String keyword;
|
||||
private int maxSearchResults;
|
||||
private Runnable onFinished;
|
||||
private SearchAPICallback apiCallback;
|
||||
|
||||
private boolean cancelled;
|
||||
private int resCount;
|
||||
|
||||
public SearchRequest(String keyword, int maxSearchResults, SearchAPICallback apiCallback) {
|
||||
this.keyword = keyword;
|
||||
this.maxSearchResults = maxSearchResults;
|
||||
this.apiCallback = apiCallback;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
|
||||
new AsyncTask<String, Void, List<SearchItem>>() {
|
||||
@Override
|
||||
protected List<SearchItem> doInBackground(String... params) {
|
||||
return doSearch(params[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(List<SearchItem> searchItems) {
|
||||
|
||||
if (onFinished != null) {
|
||||
onFinished.run();
|
||||
}
|
||||
|
||||
if (apiCallback != null) {
|
||||
apiCallback.onSearchFinished(searchItems, cancelled);
|
||||
}
|
||||
}
|
||||
}.execute(keyword);
|
||||
}
|
||||
|
||||
private List<SearchItem> doSearch(String keyword) {
|
||||
System.out.println("=== Start search");
|
||||
resCount = 0;
|
||||
|
||||
final List<SearchItem> searchItems = new ArrayList<>();
|
||||
|
||||
AmenitiesByNameSearch byNameSearch = new AmenitiesByNameSearch(obfsCollection);
|
||||
AmenitiesByNameSearch.Criteria criteria = new AmenitiesByNameSearch.Criteria();
|
||||
criteria.setName(keyword);
|
||||
if (obfAreaFilter != null) {
|
||||
criteria.setObfInfoAreaFilter(new NullableAreaI(new AreaI(obfAreaFilter)));
|
||||
}
|
||||
|
||||
ISearch.INewResultEntryCallback newResultEntryCallback = new ISearch.INewResultEntryCallback() {
|
||||
@Override
|
||||
public void method(ISearch.Criteria criteria, ISearch.IResultEntry resultEntry) {
|
||||
Amenity amenity = new ResultEntry(resultEntry).getAmenity();
|
||||
searchItems.add(new AmenitySearchItem(amenity));
|
||||
System.out.println("Poi found === " + amenity.getNativeName());
|
||||
resCount++;
|
||||
/*
|
||||
QStringStringHash locNames = amenity.getLocalizedNames();
|
||||
if (locNames.size() > 0) {
|
||||
QStringList keys = locNames.keys();
|
||||
StringBuilder sb = new StringBuilder("=== Localized names: ");
|
||||
for (int i = 0; i < keys.size(); i++) {
|
||||
String key = keys.get(i);
|
||||
sb.append(key).append("=").append(locNames.get(key)).append(" | ");
|
||||
}
|
||||
System.out.println(sb.toString());
|
||||
}
|
||||
*/
|
||||
}
|
||||
};
|
||||
|
||||
byNameSearch.performSearch(criteria, newResultEntryCallback.getBinding(), new IQueryController() {
|
||||
@Override
|
||||
public boolean isAborted() {
|
||||
return resCount >= maxSearchResults || cancelled;
|
||||
}
|
||||
});
|
||||
|
||||
System.out.println("=== Finish search");
|
||||
|
||||
return searchItems;
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
cancelled = true;
|
||||
}
|
||||
|
||||
public void setOnFinishedCallback(Runnable onFinished) {
|
||||
this.onFinished = onFinished;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ResultEntry extends AmenitiesByNameSearch.ResultEntry {
|
||||
protected ResultEntry(ISearch.IResultEntry resultEntry) {
|
||||
super(ISearch.IResultEntry.getCPtr(resultEntry), false);
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class SearchItem {
|
||||
|
||||
protected double latitude;
|
||||
protected double longitude;
|
||||
|
||||
public SearchItem(double latitude, double longitude) {
|
||||
this.latitude = latitude;
|
||||
this.longitude = longitude;
|
||||
}
|
||||
|
||||
public SearchItem(PointI location31) {
|
||||
LatLon latLon = Utilities.convert31ToLatLon(location31);
|
||||
latitude = latLon.getLatitude();
|
||||
longitude = latLon.getLongitude();
|
||||
}
|
||||
|
||||
public abstract String getNativeName();
|
||||
|
||||
public abstract String getLocalizedName();
|
||||
|
||||
public abstract String getTypeName();
|
||||
|
||||
public abstract String getSubTypeName();
|
||||
|
||||
public double getLatitude() {
|
||||
return latitude;
|
||||
}
|
||||
|
||||
public double getLongitude() {
|
||||
return longitude;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getNativeName() + " {lat:" + getLatitude() + " lon: " + getLongitude() + "}";
|
||||
}
|
||||
}
|
||||
|
||||
public static class AmenitySearchItem extends SearchItem {
|
||||
|
||||
private String nativeName;
|
||||
private String localizedName;
|
||||
private String category;
|
||||
private String subcategory;
|
||||
|
||||
public AmenitySearchItem(Amenity amenity) {
|
||||
super(amenity.getPosition31());
|
||||
|
||||
nativeName = amenity.getNativeName();
|
||||
QStringStringHash locNames = amenity.getLocalizedNames();
|
||||
if (locNames.has_key(MapUtils.LANGUAGE)) {
|
||||
localizedName = locNames.get(MapUtils.LANGUAGE);
|
||||
}
|
||||
/*
|
||||
if (locNames.size() > 0) {
|
||||
QStringList keys = locNames.keys();
|
||||
for (int i = 0; i < keys.size(); i++) {
|
||||
String key = keys.get(i);
|
||||
localizedNamesMap.put(key, locNames.get(key));
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
DecodedCategoryList catList = amenity.getDecodedCategories();
|
||||
if (catList.size() > 0) {
|
||||
DecodedCategory decodedCategory = catList.get(0);
|
||||
category = decodedCategory.getCategory();
|
||||
subcategory = decodedCategory.getSubcategory();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNativeName() {
|
||||
return nativeName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLocalizedName() {
|
||||
return localizedName != null ? localizedName : nativeName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeName() {
|
||||
return category;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSubTypeName() {
|
||||
return subcategory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getLatitude() {
|
||||
return latitude;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getLongitude() {
|
||||
return longitude;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
package net.osmand.core.samples.android.sample1;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import net.osmand.core.jni.Utilities;
|
||||
import net.osmand.core.samples.android.sample1.SearchAPI.SearchItem;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
|
||||
public class SearchUIHelper {
|
||||
|
||||
public static Drawable getIcon(Context ctx, SearchItem item) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static class SearchRow {
|
||||
|
||||
private SearchItem searchItem;
|
||||
private double distance;
|
||||
|
||||
public SearchRow(SearchItem searchItem) {
|
||||
this.searchItem = searchItem;
|
||||
}
|
||||
|
||||
public SearchItem getSearchItem() {
|
||||
return searchItem;
|
||||
}
|
||||
|
||||
public double getLatitude() {
|
||||
return searchItem.getLatitude();
|
||||
}
|
||||
|
||||
public double getLongitude() {
|
||||
return searchItem.getLongitude();
|
||||
}
|
||||
|
||||
public double getDistance() {
|
||||
return distance;
|
||||
}
|
||||
|
||||
public void setDistance(double distance) {
|
||||
this.distance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SearchListAdapter extends ArrayAdapter<SearchRow> {
|
||||
|
||||
private Context ctx;
|
||||
|
||||
public SearchListAdapter(Context ctx) {
|
||||
super(ctx, R.layout.search_list_item);
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
public void updateDistance(double latitude, double longitude) {
|
||||
for (int i = 0; i < getCount(); i++) {
|
||||
SearchRow item = getItem(i);
|
||||
item.setDistance(Utilities.distance(
|
||||
longitude, latitude,
|
||||
item.getLongitude(), item.getLatitude()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
SearchRow item = getItem(position);
|
||||
|
||||
LinearLayout view;
|
||||
if (convertView == null) {
|
||||
LayoutInflater inflater = (LayoutInflater) ctx
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
view = (LinearLayout) inflater.inflate(
|
||||
R.layout.search_list_item, null);
|
||||
} else {
|
||||
view = (LinearLayout) convertView;
|
||||
}
|
||||
|
||||
TextView title = (TextView) view.findViewById(R.id.title);
|
||||
TextView subtitle = (TextView) view.findViewById(R.id.subtitle);
|
||||
TextView distance = (TextView) view.findViewById(R.id.distance);
|
||||
title.setText(item.searchItem.getLocalizedName());
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (!item.searchItem.getSubTypeName().isEmpty()) {
|
||||
sb.append(getNiceString(item.searchItem.getSubTypeName()));
|
||||
}
|
||||
if (!item.searchItem.getTypeName().isEmpty()) {
|
||||
if (sb.length() > 0) {
|
||||
sb.append(" — ");
|
||||
}
|
||||
sb.append(getNiceString(item.searchItem.getTypeName()));
|
||||
}
|
||||
subtitle.setText(sb.toString());
|
||||
if (item.getDistance() == 0) {
|
||||
distance.setText("");
|
||||
} else {
|
||||
distance.setText(getFormattedDistance(item.getDistance()));
|
||||
}
|
||||
|
||||
//text1.setTextColor(ctx.getResources().getColor(R.color.listTextColor));
|
||||
//view.setCompoundDrawablesWithIntrinsicBounds(getIcon(ctx, item), null, null, null);
|
||||
//view.setCompoundDrawablePadding(ctx.getResources().getDimensionPixelSize(R.dimen.list_content_padding));
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
public static String capitalizeFirstLetterAndLowercase(String s) {
|
||||
if (s != null && s.length() > 1) {
|
||||
// not very efficient algorithm
|
||||
return Character.toUpperCase(s.charAt(0)) + s.substring(1).toLowerCase();
|
||||
} else {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getNiceString(String s) {
|
||||
return capitalizeFirstLetterAndLowercase(s.replaceAll("_", " "));
|
||||
}
|
||||
|
||||
public static String getFormattedDistance(double meters) {
|
||||
double mainUnitInMeters = 1000;
|
||||
String mainUnitStr = "km";
|
||||
if (meters >= 100 * mainUnitInMeters) {
|
||||
return (int) (meters / mainUnitInMeters + 0.5) + " " + mainUnitStr;
|
||||
} else if (meters > 9.99f * mainUnitInMeters) {
|
||||
return MessageFormat.format("{0,number,#.#} " + mainUnitStr, ((float) meters) / mainUnitInMeters).replace('\n', ' ');
|
||||
} else if (meters > 0.999f * mainUnitInMeters) {
|
||||
return MessageFormat.format("{0,number,#.##} " + mainUnitStr, ((float) meters) / mainUnitInMeters).replace('\n', ' ');
|
||||
} else {
|
||||
return ((int) (meters + 0.5)) + " m";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
include ':OsmAnd-java'
|
||||
include ':OsmAnd-java', ':OsmAndCore-sample'
|
||||
include ':OsmAnd'
|
||||
include ':plugins:OsmAnd-AddressPlugin'
|
||||
include ':plugins:Osmand-ParkingPlugin'
|
||||
|
|