依赖文件

This commit is contained in:
浅念
2025-03-20 17:35:53 +08:00
parent 634fe57349
commit 021ffc6052
1134 changed files with 137518 additions and 132 deletions
@@ -0,0 +1,106 @@
def searchFileInParents(target) {
def searchPath = target
for (int i = 0; i < 8; i++) {
println(searchPath)
def targetFile = file(searchPath)
if(!targetFile.canRead()) {
searchPath = '../' + searchPath;
} else {
return targetFile;
}
}
return null;
}
android {
// #1. Find (project)/config.xml file,
// because ionic capacitor does not copy <preference> nodes
def configXmlFile = searchFileInParents("config.xml")
if (configXmlFile == null) {
// #2. Find (project)/app/src/main/res/xml/config.xml file,
// cordova should copy <preference> to this file
configXmlFile = searchFileInParents("app/src/main/res/xml/config.xml")
}
if (configXmlFile == null) {
throw new GradleException("Can not find config.xml file.")
}
// parse xml file
def widget = new XmlParser().parse(configXmlFile)
if (widget.preference == null) {
throw new GradleException("widget is null.")
}
if (widget.preference.size() == 0) {
throw new GradleException("Please add '<preference name=\"GOOGLE_MAPS_ANDROID_API_KEY\" />' to the config.xml file.")
}
println("preference = " + widget.preference.size())
Properties props = new Properties()
def isKeyFound = 0
def useBetaSdk = 0
props.setProperty("GOOGLE_MAPS_PLAY_SERVICES_VERSION", "16.0.0");
props.setProperty("ANDROID_SUPPORT_V4_VERSION", "27.1.1");
props.setProperty("GOOGLE_MAPS_ANDROID_SDK", "");
widget.preference.each { pref ->
def name = pref.attributes().name
def value = pref.attributes().value
println("name = " + name);
if (name == "GOOGLE_MAPS_ANDROID_API_KEY") {
isKeyFound = 1
props.setProperty(name, value)
}
if (name == "GOOGLE_MAPS_PLAY_SERVICES_VERSION" ||
name == "ANDROID_SUPPORT_V4_VERSION") {
props.setProperty(name, value)
}
if (name == "GOOGLE_MAPS_ANDROID_SDK") {
useBetaSdk = 1
props.setProperty("GOOGLE_MAPS_ANDROID_SDK", value)
}
}
if (isKeyFound == 0) {
throw new GradleException("Please add '<preference name=\"GOOGLE_MAPS_ANDROID_API_KEY\" />' to the config.xml file.")
}
if (useBetaSdk == 1) {
def libsDirPath = System.getProperty("user.dir")
def libsDir = new File("${libsDirPath}/libs/")
if (!libsDir.exists()) {
libsDir.mkdirs()
}
def mapsSDK = props.getProperty("GOOGLE_MAPS_ANDROID_SDK")
def f = new File("${libsDir.absolutePath}/${mapsSDK}.aar")
println("---->lib = " + f.absolutePath);
if (!f.exists()) {
new URL("https://dl.google.com/dl/geosdk/${mapsSDK}.aar").withInputStream{ i -> f.withOutputStream{ it << i }}
}
dependencies {
implementation name: mapsSDK, ext:'aar'
}
}
def PLAY_SERVICES_VERSION = props.get("GOOGLE_MAPS_PLAY_SERVICES_VERSION")
def ANDROID_SUPPORT_V4_VERSION = props.get("ANDROID_SUPPORT_V4_VERSION")
println("---->PLAY_SERVICES_VERSION = ${PLAY_SERVICES_VERSION}")
println("---->ANDROID_SUPPORT_V4_VERSION = ${ANDROID_SUPPORT_V4_VERSION}")
dependencies {
implementation "com.google.android.gms:play-services-maps:${PLAY_SERVICES_VERSION}"
implementation "com.google.android.gms:play-services-location:${PLAY_SERVICES_VERSION}"
implementation "com.android.support:support-core-utils:${ANDROID_SUPPORT_V4_VERSION}"
}
buildTypes {
debug {
manifestPlaceholders.GOOGLE_MAPS_ANDROID_API_KEY = props.getProperty("GOOGLE_MAPS_ANDROID_API_KEY")
}
release {
manifestPlaceholders.GOOGLE_MAPS_ANDROID_API_KEY = props.getProperty("GOOGLE_MAPS_ANDROID_API_KEY")
}
}
}
Binary file not shown.
@@ -0,0 +1,22 @@
repositories {
maven { url 'https://maven.google.com' }
mavenCentral()
jcenter()
def libsDirPath = System.getProperty("user.dir")
flatDir{
dirs 'src/main/libs', 'libs', "${libsDirPath}/libs"
}
}
dependencies {
implementation(name:'tbxml-android', ext:'aar')
}
android {
compileSdkVersion 26
packagingOptions {
exclude 'README'
exclude 'LICENSE'
}
}
@@ -0,0 +1,576 @@
package plugin.google.maps;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.os.AsyncTask;
import android.util.Log;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaResourceApi;
import org.apache.cordova.CordovaWebView;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
public class AsyncLoadImage extends AsyncTask<Void, Void, AsyncLoadImage.AsyncLoadImageResult> {
private AsyncLoadImageInterface callback;
private float density = Resources.getSystem().getDisplayMetrics().density;
private AsyncLoadImageOptions mOptions;
private String userAgent;
private String currentPageUrl;
// Get max available VM memory, exceeding this amount will throw an
// OutOfMemory exception.
static int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// Use 1/8th of the available memory for this memory cache.
public static BitmapCache mIconCache = new BitmapCache(maxMemory / 8);
private final String TAG = "AsyncLoadImage";
private CordovaWebView webView;
private CordovaInterface cordova;
public static class AsyncLoadImageOptions {
String url;
int width;
int height;
boolean noCaching;
}
public static class AsyncLoadImageResult {
Bitmap image;
boolean cacheHit;
String cacheKey;
}
public AsyncLoadImage(CordovaInterface cordova, CordovaWebView webView, AsyncLoadImageOptions options, AsyncLoadImageInterface callback) {
this.callback = callback;
this.mOptions = options;
this.webView = webView;
this.cordova = cordova;
}
public static String getCacheKey(String url, int width, int height) {
if (url == null) {
return null;
}
try {
return getCacheKey(new URL(url), width, height);
} catch (MalformedURLException e) {
return url.hashCode() + "/" + width + "x" + height;
}
}
public static String getCacheKey(URL url, int width, int height) {
return url.hashCode() + "/" + width + "x" + height;
}
public static void addBitmapToMemoryCache(String key, Bitmap image) {
if (getBitmapFromMemCache(key) == null) {
mIconCache.put(key, image.copy(image.getConfig(), true));
}
}
public static void removeBitmapFromMemCahce(String key) {
Bitmap image = mIconCache.remove(key);
if (image == null || image.isRecycled()) {
return;
}
image.recycle();
}
public static Bitmap getBitmapFromMemCache(String key) {
Bitmap image = mIconCache.get(key);
if (image == null || image.isRecycled()) {
return null;
}
return image.copy(image.getConfig(), true);
}
@Override
protected void onCancelled(AsyncLoadImageResult result) {
super.onCancelled(result);
if (result == null) {
return;
}
if (!result.image.isRecycled()) {
result.image.recycle();
}
result.image = null;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
//----------------------------------------------------------------
// If icon url contains "cdvfile://", convert to physical path.
//----------------------------------------------------------------
if (mOptions.url.indexOf("cdvfile://") == 0) {
CordovaResourceApi resourceApi = webView.getResourceApi();
mOptions.url = PluginUtil.getAbsolutePathFromCDVFilePath(resourceApi, mOptions.url);
}
String currentPage = webView.getUrl();
if (currentPage == null) {
// Maybe someone close the map page.
this.cancel(true);
return;
}
currentPage = currentPage.replaceAll("#.*$", "");
currentPage = currentPage.replaceAll("\\?.*$", "");
currentPage = currentPage.replaceAll("[^\\/]*$", "");
this.currentPageUrl = currentPage;
//Log.d(TAG, "-->currentPageUrl = " + this.currentPageUrl);
//View browserView = webView.getView();
//String browserViewName = browserView.getClass().getName();
this.userAgent = "Mozilla";
/*
if("org.xwalk.core.XWalkView".equals(browserViewName) ||
"org.crosswalk.engine.XWalkCordovaView".equals(browserViewName)) {
CordovaPreferences preferences = webView.getPreferences();
// Set xwalk webview settings by Cordova preferences.
String xwalkUserAgent = preferences == null ? "" : preferences.getString("xwalkUserAgent", "");
if (!xwalkUserAgent.isEmpty()) {
this.userAgent = xwalkUserAgent;
}
String appendUserAgent = preferences.getString("AppendUserAgent", "");
if (!appendUserAgent.isEmpty()) {
this.userAgent = this.userAgent + " " + appendUserAgent;
}
if ("".equals(this.userAgent)) {
this.userAgent = "Mozilla";
}
} else {
this.userAgent = ((WebView) webView.getEngine().getView()).getSettings().getUserAgentString();
}
*/
}
protected AsyncLoadImageResult doInBackground(Void... params) {
int mWidth = mOptions.width;
int mHeight = mOptions.height;
String iconUrl = mOptions.url;
String orgIconUrl = iconUrl;
Bitmap image = null;
if (iconUrl == null) {
return null;
}
String cacheKey = null;
cacheKey = getCacheKey(orgIconUrl, mWidth, mHeight);
image = getBitmapFromMemCache(cacheKey);
if (image != null) {
AsyncLoadImageResult result = new AsyncLoadImageResult();
result.image = image;
result.cacheHit = true;
result.cacheKey = cacheKey;
return result;
}
// Log.d(TAG, String.format("---->iconURL = %s", iconUrl));
//--------------------------------
// Load image from local path
//--------------------------------
if (!iconUrl.startsWith("data:image")) {
if (iconUrl.startsWith("http://localhost") ||
iconUrl.startsWith("http://127.0.0.1")) {
// Log.d(TAG, String.format("---->(201)iconURL = %s", iconUrl));
if (iconUrl.contains("://")) {
iconUrl = iconUrl.replaceAll("http://.+?/", "file:///android_asset/www/");
} else {
// Avoid WebViewLocalServer (because can not make a connection for some reason)
iconUrl = "file:///android_asset/www/".concat(iconUrl);
}
}
if (!iconUrl.contains("://") &&
!iconUrl.startsWith("/") &&
!iconUrl.startsWith("www/") &&
!iconUrl.startsWith("data:image") &&
!iconUrl.startsWith("./") &&
!iconUrl.startsWith("../")) {
iconUrl = "./" + iconUrl;
//Log.d(TAG, "--> iconUrl = " + iconUrl);
}
if (iconUrl.startsWith("./") || iconUrl.startsWith("../")) {
iconUrl = iconUrl.replace("(\\.\\/)+", "./");
String currentPage = this.currentPageUrl;
currentPage = currentPage.replaceAll("[^\\/]*$", "");
currentPage = currentPage.replaceAll("#.*$", "");
currentPage = currentPage.replaceAll("\\/[^\\/]+\\.[^\\/]+$", "");
if (!currentPage.endsWith("/")) {
currentPage = currentPage + "/";
}
iconUrl = currentPage + iconUrl;
iconUrl = iconUrl.replaceAll("(\\/\\.\\/+)+", "/");
//Log.d(TAG, "--> iconUrl = " + iconUrl);
}
if (iconUrl.indexOf("file://") == 0 &&
!iconUrl.contains("file:///android_asset/")) {
iconUrl = iconUrl.replace("file://", "");
} else {
//Log.d(TAG, "--> iconUrl = " + iconUrl);
// if (iconUrl.indexOf("file:///android_asset/") == 0) {
// iconUrl = iconUrl.replace("file:///android_asset/", "");
// }
//Log.d(TAG, "iconUrl(222) = " + iconUrl);
if (iconUrl.contains("./")) {
try {
boolean isAbsolutePath = iconUrl.startsWith("/");
File relativePath = new File(iconUrl);
iconUrl = relativePath.getCanonicalPath();
//Log.d(TAG, "iconUrl = " + iconUrl);
if (!isAbsolutePath) {
iconUrl = iconUrl.substring(1);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
cacheKey = getCacheKey(iconUrl, mWidth, mHeight);
image = getBitmapFromMemCache(cacheKey);
if (image != null) {
AsyncLoadImageResult result = new AsyncLoadImageResult();
result.image = image;
result.cacheHit = true;
result.cacheKey = cacheKey;
return result;
}
cacheKey = getCacheKey(orgIconUrl, mWidth, mHeight);
if (iconUrl.indexOf("http") == 0) {
//--------------------------------
// Load image from the Internet
//--------------------------------
try {
URL url = new URL(iconUrl);
boolean redirect = true;
HttpURLConnection http = null;
String cookies = null;
int redirectCnt = 0;
while(redirect && redirectCnt < 10) {
redirect = false;
http = (HttpURLConnection)url.openConnection();
http.setRequestMethod("GET");
if (cookies != null) {
http.setRequestProperty("Cookie", cookies);
}
http.addRequestProperty("Accept-Language", "en-US,en;q=0.8");
http.addRequestProperty("User-Agent", this.userAgent);
http.setInstanceFollowRedirects(true);
HttpURLConnection.setFollowRedirects(true);
// normally, 3xx is redirect
try {
int status = http.getResponseCode();
if (status != HttpURLConnection.HTTP_OK) {
if (status == HttpURLConnection.HTTP_MOVED_TEMP
|| status == HttpURLConnection.HTTP_MOVED_PERM
|| status == HttpURLConnection.HTTP_SEE_OTHER)
redirect = true;
}
if (redirect) {
// get redirect url from "location" header field
url = new URL(http.getHeaderField("Location"));
// get the cookie if need, for login
cookies = http.getHeaderField("Set-Cookie");
// Disconnect the current connection
http.disconnect();
redirectCnt++;
continue;
}
if (status == HttpURLConnection.HTTP_OK) {
break;
} else {
return null;
}
} catch (Exception e) {
Log.e(TAG, "can not connect to " + iconUrl, e);
}
}
Bitmap myBitmap = null;
InputStream inputStream = http.getInputStream();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int nRead;
byte[] data = new byte[16384];
while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
buffer.flush();
inputStream.close();
byte[] imageBytes = buffer.toByteArray();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
options.inPreferredConfig = Config.ARGB_8888;
// The below line just checking the bitmap size (width,height).
// Returned value is always null.
BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, options);
if (mWidth < 1 && mHeight < 1) {
mWidth = options.outWidth;
mHeight = options.outHeight;
}
// Resize
int newWidth = (int)(mWidth * density);
int newHeight = (int)(mHeight * density);
if (newWidth > 2000 || newHeight > 2000) {
float rationResize;
if (newWidth >= newHeight) {
rationResize = 2000.0f / ((float) newWidth);
} else {
rationResize = 2000.0f / ((float) newHeight);
}
newWidth = (int)(((float)newWidth) * rationResize);
newHeight = (int)(((float)newHeight) * rationResize);
Log.w(TAG, "Since the image size is too large, the image size resizes down mandatory");
}
/**
* http://stackoverflow.com/questions/4821488/bad-image-quality-after-resizing-scaling-bitmap#7468636
*/
Bitmap scaledBitmap = Bitmap.createBitmap(newWidth, newHeight, Config.ARGB_8888);
float ratioX = newWidth / (float) options.outWidth;
float ratioY = newHeight / (float) options.outHeight;
float middleX = newWidth / 2.0f;
float middleY = newHeight / 2.0f;
options.inJustDecodeBounds = false;
//options.inSampleSize = (int) Math.max(ratioX, ratioY);
options.outWidth = newWidth;
options.outHeight = newHeight;
Matrix scaleMatrix = new Matrix();
scaleMatrix.setScale(ratioX, ratioY, middleX, middleY);
Canvas canvas = new Canvas(scaledBitmap);
canvas.setMatrix(scaleMatrix);
myBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, options);
canvas.drawBitmap(myBitmap, middleX - options.outWidth / 2, middleY - options.outHeight / 2, new Paint(Paint.FILTER_BITMAP_FLAG));
myBitmap.recycle();
myBitmap = null;
canvas = null;
imageBytes = null;
AsyncLoadImageResult result = new AsyncLoadImageResult();
result.image = scaledBitmap;
result.cacheHit = false;
if (!mOptions.noCaching) {
result.cacheKey = cacheKey;
addBitmapToMemoryCache(cacheKey, scaledBitmap);
}
return result;
} catch (Exception e) {
e.printStackTrace();
return null;
}
} else {
//Log.d(TAG, "--> iconUrl = " + iconUrl);
if (iconUrl.indexOf("data:image/") == 0 && iconUrl.contains(";base64,")) {
String[] tmp = iconUrl.split(",");
image = PluginUtil.getBitmapFromBase64encodedImage(tmp[1]);
} else {
try {
InputStream inputStream = null;
if (iconUrl.startsWith("file:/android_asset/")) {
AssetManager assetManager = cordova.getActivity().getAssets();
iconUrl = iconUrl.replace("file:/android_asset/", "");
inputStream = assetManager.open(iconUrl);
//Log.d(TAG, "--> iconUrl = " + iconUrl);
} else if (iconUrl.startsWith("file:///android_asset/")) {
AssetManager assetManager = cordova.getActivity().getAssets();
iconUrl = iconUrl.replace("file:///android_asset/", "");
inputStream = assetManager.open(iconUrl);
//Log.d(TAG, "--> iconUrl = " + iconUrl);
} else if (iconUrl.startsWith("/")) {
File file = new File(iconUrl);
inputStream = new FileInputStream(file);
}
if (inputStream != null) {
image = BitmapFactory.decodeStream(inputStream);
inputStream.close();
} else {
Log.e(TAG, "Can not load the file from '" + iconUrl + "'");
return null;
}
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
/*
//--------------------------------
// Load image from local path
//--------------------------------
if (!iconUrl.contains("://") &&
!iconUrl.startsWith("/") &&
!iconUrl.startsWith("www/") &&
!iconUrl.startsWith("data:image") &&
!iconUrl.startsWith("./") &&
!iconUrl.startsWith("../")) {
iconUrl = "./" + iconUrl;
Log.d(TAG, "--> iconUrl = " + iconUrl);
}
if (iconUrl.startsWith("./") || iconUrl.startsWith("../")) {
iconUrl = iconUrl.replace("(\\.\\/)+", "./");
String currentPage = this.currentPageUrl;
currentPage = currentPage.replaceAll("[^\\/]*$", "");
currentPage = currentPage.replaceAll("#.*$", "");
currentPage = currentPage.replaceAll("\\/[^\\/]+\\.[^\\/]+$", "");
if (!currentPage.endsWith("/")) {
currentPage = currentPage + "/";
}
iconUrl = currentPage + iconUrl;
iconUrl = iconUrl.replaceAll("(\\/\\.\\/+)+", "/");
Log.d(TAG, "--> iconUrl = " + iconUrl);
}
if (iconUrl.indexOf("data:image/") == 0 && iconUrl.contains(";base64,")) {
cacheKey = getCacheKey(iconUrl, mWidth, mHeight);
image = getBitmapFromMemCache(cacheKey);
if (image != null) {
AsyncLoadImageResult result = new AsyncLoadImageResult();
result.image = image;
result.cacheHit = true;
result.cacheKey = cacheKey;
return result;
}
String[] tmp = iconUrl.split(",");
image = PluginUtil.getBitmapFromBase64encodedImage(tmp[1]);
} else if (iconUrl.indexOf("file://") == 0 &&
!iconUrl.contains("file:///android_asset/")) {
iconUrl = iconUrl.replace("file://", "");
File tmp = new File(iconUrl);
if (tmp.exists()) {
cacheKey = getCacheKey(iconUrl, mWidth, mHeight);
image = getBitmapFromMemCache(cacheKey);
if (image != null) {
AsyncLoadImageResult result = new AsyncLoadImageResult();
result.image = image;
result.cacheHit = true;
result.cacheKey = cacheKey;
return result;
}
image = BitmapFactory.decodeFile(iconUrl);
} else {
//if (PluginMarker.this.mapCtrl.mPluginLayout.isDebug) {
Log.w(TAG, "icon is not found (" + iconUrl + ")");
//}
return null;
}
} else {
Log.d(TAG, "--> iconUrl = " + iconUrl);
cacheKey = getCacheKey(iconUrl, mWidth, mHeight);
image = getBitmapFromMemCache(cacheKey);
if (image != null) {
AsyncLoadImageResult result = new AsyncLoadImageResult();
result.image = image;
result.cacheHit = true;
result.cacheKey = cacheKey;
return result;
}
Log.d(TAG, "iconUrl = " + iconUrl);
if (iconUrl.indexOf("file:///android_asset/") == 0) {
iconUrl = iconUrl.replace("file:///android_asset/", "");
}
Log.d(TAG, "iconUrl = " + iconUrl);
if (iconUrl.contains("./")) {
try {
boolean isAbsolutePath = iconUrl.startsWith("/");
File relativePath = new File(iconUrl);
iconUrl = relativePath.getCanonicalPath();
Log.d(TAG, "iconUrl = " + iconUrl);
if (!isAbsolutePath) {
iconUrl = iconUrl.substring(1);
}
Log.d(TAG, "iconUrl = " + iconUrl);
} catch (Exception e) {
e.printStackTrace();
}
}
*/
//}
if (mWidth > 0 && mHeight > 0) {
mWidth = Math.round(mWidth * density);
mHeight = Math.round(mHeight * density);
image = PluginUtil.resizeBitmap(image, mWidth, mHeight);
} else {
image = PluginUtil.scaleBitmapForDevice(image);
}
AsyncLoadImageResult result = new AsyncLoadImageResult();
result.image = image;
result.cacheHit = false;
if (!mOptions.noCaching) {
result.cacheKey = cacheKey;
addBitmapToMemoryCache(cacheKey, image);
}
return result;
}
}
@Override
protected void onPostExecute(AsyncLoadImageResult result) {
this.callback.onPostExecute(result);
}
}
@@ -0,0 +1,5 @@
package plugin.google.maps;
public interface AsyncLoadImageInterface {
public void onPostExecute(AsyncLoadImage.AsyncLoadImageResult result) ;
}
@@ -0,0 +1,26 @@
package plugin.google.maps;
import android.graphics.Bitmap;
import android.util.LruCache;
public class BitmapCache extends LruCache<String, Bitmap> {
public BitmapCache(int maxSize) {
super(maxSize);
}
@Override
protected int sizeOf(String key, Bitmap bitmap) {
// The cache size will be measured in kilobytes rather than
// number of items.
return bitmap.getByteCount() / 1024;
}
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldBitmap, Bitmap newBitmap) {
if (!oldBitmap.isRecycled()) {
oldBitmap.recycle();
oldBitmap = null;
}
}
}
@@ -0,0 +1,584 @@
package plugin.google.maps;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.RectF;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.maps.MapsInitializer;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.LOG;
import org.apache.cordova.PluginEntry;
import org.apache.cordova.PluginManager;
import org.apache.cordova.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Set;
@SuppressWarnings("deprecation")
public class CordovaGoogleMaps extends CordovaPlugin implements ViewTreeObserver.OnScrollChangedListener{
private final String TAG = "GoogleMapsPlugin";
private Activity activity;
public ViewGroup root;
public MyPluginLayout mPluginLayout = null;
public boolean initialized = false;
public PluginManager pluginManager;
private static final Object timerLock = new Object();
@SuppressLint("NewApi") @Override
public void initialize(final CordovaInterface cordova, final CordovaWebView webView) {
super.initialize(cordova, webView);
if (root != null) {
return;
}
LOG.setLogLevel(LOG.ERROR);
activity = cordova.getActivity();
final View view = webView.getView();
view.getViewTreeObserver().addOnScrollChangedListener(CordovaGoogleMaps.this);
root = (ViewGroup) view.getParent();
pluginManager = webView.getPluginManager();
cordova.getActivity().runOnUiThread(new Runnable() {
@SuppressLint("NewApi")
public void run() {
// Enable this, webView makes draw cache on the Android action bar issue.
//View view = webView.getView();
//if (Build.VERSION.SDK_INT >= 21 || "org.xwalk.core.XWalkView".equals(view.getClass().getName())){
// view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
// Log.d("Layout", "--> view =" + view.isHardwareAccelerated()); //always false
//}
// ------------------------------
// Check of Google Play Services
// ------------------------------
int checkGooglePlayServices = GooglePlayServicesUtil.isGooglePlayServicesAvailable(activity);
Log.d(TAG, "----> checkGooglePlayServices = " + (ConnectionResult.SUCCESS == checkGooglePlayServices));
if (checkGooglePlayServices != ConnectionResult.SUCCESS) {
// google play services is missing!!!!
/*
* Returns status code indicating whether there was an error. Can be one
* of following in ConnectionResult: SUCCESS, SERVICE_MISSING,
* SERVICE_VERSION_UPDATE_REQUIRED, SERVICE_DISABLED, SERVICE_INVALID.
*/
Log.e(TAG, "---Google Play Services is not available: " + GooglePlayServicesUtil.getErrorString(checkGooglePlayServices));
boolean isNeedToUpdate = false;
String errorMsg = PluginUtil.getPgmStrings(activity, "pgm_google_play_error");
switch (checkGooglePlayServices) {
case ConnectionResult.DEVELOPER_ERROR:
errorMsg = PluginUtil.getPgmStrings(activity,"pgm_google_play_developer_error");
break;
case ConnectionResult.INTERNAL_ERROR:
errorMsg = PluginUtil.getPgmStrings(activity,"pgm_google_play_internal_error");
break;
case ConnectionResult.INVALID_ACCOUNT:
errorMsg = PluginUtil.getPgmStrings(activity,"pgm_google_play_invalid_account");
break;
case ConnectionResult.LICENSE_CHECK_FAILED:
errorMsg = PluginUtil.getPgmStrings(activity,"pgm_google_play_license_check_failed");
break;
case ConnectionResult.NETWORK_ERROR:
errorMsg = PluginUtil.getPgmStrings(activity,"pgm_google_play_network_error");
break;
case ConnectionResult.SERVICE_DISABLED:
errorMsg = PluginUtil.getPgmStrings(activity,"pgm_google_play_service_disabled");
break;
case ConnectionResult.SERVICE_INVALID:
errorMsg = PluginUtil.getPgmStrings(activity,"pgm_google_play_service_invalid");
isNeedToUpdate = true;
break;
case ConnectionResult.SERVICE_MISSING:
errorMsg = PluginUtil.getPgmStrings(activity,"pgm_google_play_service_missing");
isNeedToUpdate = true;
break;
case ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED:
errorMsg = PluginUtil.getPgmStrings(activity,"pgm_google_play_service_update_required");
isNeedToUpdate = true;
break;
case ConnectionResult.SIGN_IN_REQUIRED:
errorMsg = PluginUtil.getPgmStrings(activity,"pgm_google_play_sign_in_required");
break;
default:
isNeedToUpdate = true;
break;
}
final boolean finalIsNeedToUpdate = isNeedToUpdate;
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(activity);
alertDialogBuilder
.setMessage(errorMsg)
.setCancelable(false)
.setPositiveButton(PluginUtil.getPgmStrings(activity,"pgm_google_close_button"), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,int id) {
dialog.dismiss();
if (finalIsNeedToUpdate) {
try {
activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=com.google.android.gms")));
} catch (android.content.ActivityNotFoundException anfe) {
activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://play.google.com/store/apps/details?id=com.google.android.gms")));
}
}
}
});
AlertDialog alertDialog = alertDialogBuilder.create();
// show it
alertDialog.show();
Log.e(TAG, "Google Play Services is not available.");
return;
}
webView.getView().setBackgroundColor(Color.TRANSPARENT);
webView.getView().setOverScrollMode(View.OVER_SCROLL_NEVER);
mPluginLayout = new MyPluginLayout(webView, activity);
mPluginLayout.stopTimer();
// Check the API key
ApplicationInfo appliInfo = null;
try {
appliInfo = activity.getPackageManager().getApplicationInfo(activity.getPackageName(), PackageManager.GET_META_DATA);
} catch (NameNotFoundException e) {}
String API_KEY = appliInfo.metaData.getString("com.google.android.maps.v2.API_KEY");
if ("API_KEY_FOR_ANDROID".equals(API_KEY)) {
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(activity);
alertDialogBuilder
.setMessage(PluginUtil.getPgmStrings(activity,"pgm_api_key_error"))
.setCancelable(false)
.setPositiveButton(PluginUtil.getPgmStrings(activity,"pgm_google_close_button"), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,int id) {
dialog.dismiss();
}
});
AlertDialog alertDialog = alertDialogBuilder.create();
// show it
alertDialog.show();
}
//------------------------------
// Initialize Google Maps SDK
//------------------------------
if (!initialized) {
try {
MapsInitializer.initialize(cordova.getActivity());
initialized = true;
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
}
@Override
public boolean onOverrideUrlLoading(String url) {
mPluginLayout.stopTimer();
/*
this.activity.runOnUiThread(new Runnable() {
@Override
public void run() {
webView.loadUrl("javascript:if(window.cordova){cordova.fireDocumentEvent('plugin_url_changed', {});}");
}
});
*/
return false;
}
@Override
public void onScrollChanged() {
if (mPluginLayout == null) {
return;
}
View view = webView.getView();
int scrollX = view.getScrollX();
int scrollY = view.getScrollY();
mPluginLayout.scrollTo(scrollX, scrollY);
}
@Override
public boolean execute(final String action, final JSONArray args, final CallbackContext callbackContext) throws JSONException {
cordova.getThreadPool().submit(new Runnable() {
@Override
public void run() {
try {
if (action.equals("putHtmlElements")) {
CordovaGoogleMaps.this.putHtmlElements(args, callbackContext);
} else if ("clearHtmlElements".equals(action)) {
CordovaGoogleMaps.this.clearHtmlElements(args, callbackContext);
} else if ("pause".equals(action)) {
CordovaGoogleMaps.this.pause(args, callbackContext);
} else if ("resume".equals(action)) {
CordovaGoogleMaps.this.resume(args, callbackContext);
} else if ("getMap".equals(action)) {
CordovaGoogleMaps.this.getMap(args, callbackContext);
} else if ("getPanorama".equals(action)) {
CordovaGoogleMaps.this.getPanorama(args, callbackContext);
} else if ("removeMap".equals(action)) {
CordovaGoogleMaps.this.removeMap(args, callbackContext);
} else if ("backHistory".equals(action)) {
CordovaGoogleMaps.this.backHistory(args, callbackContext);
} else if ("updateMapPositionOnly".equals(action)) {
CordovaGoogleMaps.this.updateMapPositionOnly(args, callbackContext);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
});
return true;
}
public void updateMapPositionOnly(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
final JSONObject elements = args.getJSONObject(0);
Bundle elementsBundle = PluginUtil.Json2Bundle(elements);
float zoomScale = Resources.getSystem().getDisplayMetrics().density;
Iterator<String> domIDs = elementsBundle.keySet().iterator();
String domId;
Bundle domInfo, size, currentDomInfo;
while (domIDs.hasNext()) {
domId = domIDs.next();
domInfo = elementsBundle.getBundle(domId);
size = domInfo.getBundle("size");
RectF rectF = new RectF();
rectF.left = (float)(Double.parseDouble(size.get("left") + "") * zoomScale);
rectF.top = (float)(Double.parseDouble(size.get("top") + "") * zoomScale);
rectF.right = rectF.left + (float)(Double.parseDouble(size.get("width") + "") * zoomScale);
rectF.bottom = rectF.top + (float)(Double.parseDouble(size.get("height") + "") * zoomScale);
mPluginLayout.HTMLNodeRectFs.put(domId, rectF);
}
if (mPluginLayout.isSuspended) {
mPluginLayout.updateMapPositions();
}
callbackContext.success();
}
public void backHistory(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
if (!webView.backHistory()) {
// If no more history back, exit the app
cordova.getActivity().finish();
}
}
});
}
public synchronized void pause(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
synchronized (timerLock) {
if (mPluginLayout == null) {
callbackContext.success();
return;
}
mPluginLayout.stopTimer();
callbackContext.success();
}
}
public synchronized void resume(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
synchronized (timerLock) {
if (mPluginLayout == null) {
callbackContext.success();
return;
}
if (mPluginLayout.isSuspended) {
mPluginLayout.startTimer();
}
callbackContext.success();
//On resume reapply background because it might have been changed by some other plugin
webView.getView().setBackgroundColor(Color.TRANSPARENT);
}
}
public void clearHtmlElements(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
if (mPluginLayout == null) {
callbackContext.success();
return;
}
mPluginLayout.clearHtmlElements();
callbackContext.success();
}
public void putHtmlElements(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
final JSONObject elements = args.getJSONObject(0);
if (mPluginLayout == null) {
callbackContext.success();
return;
}
//Log.d(TAG, "--->stopFlag = " + mPluginLayout.stopFlag + ", mPluginLayout.needUpdatePosition = " + mPluginLayout.needUpdatePosition);
if (!mPluginLayout.stopFlag || mPluginLayout.needUpdatePosition) {
mPluginLayout.putHTMLElements(elements);
}
//mPluginLayout.updateMapPositions();
//mPluginLayout.startTimer();
callbackContext.success();
}
@Override
public void onReset() {
super.onReset();
if (mPluginLayout == null || mPluginLayout.pluginOverlays == null) {
return;
}
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
mPluginLayout.setBackgroundColor(Color.WHITE);
Set<String> mapIds = mPluginLayout.pluginOverlays.keySet();
IPluginView pluginOverlay;
// prevent the ConcurrentModificationException error.
String[] mapIdArray= mapIds.toArray(new String[mapIds.size()]);
for (String mapId : mapIdArray) {
if (mPluginLayout.pluginOverlays.containsKey(mapId)) {
pluginOverlay = mPluginLayout.removePluginOverlay(mapId);
pluginOverlay.remove(null, null);
pluginOverlay.onDestroy();
mPluginLayout.HTMLNodes.remove(mapId);
}
}
mPluginLayout.HTMLNodes.clear();
mPluginLayout.pluginOverlays.clear();
System.gc();
Runtime.getRuntime().gc();
}
});
}
public void removeMap(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String mapId = args.getString(0);
if (mPluginLayout.pluginOverlays.containsKey(mapId)) {
IPluginView pluginOverlay = mPluginLayout.removePluginOverlay(mapId);
if (pluginOverlay != null) {
pluginOverlay.remove(null, null);
pluginOverlay.onDestroy();
mPluginLayout.HTMLNodes.remove(mapId);
pluginOverlay = null;
}
try {
Field pluginMapField = pluginManager.getClass().getDeclaredField("pluginMap");
pluginMapField.setAccessible(true);
LinkedHashMap<String, CordovaPlugin> pluginMapInstance = (LinkedHashMap<String, CordovaPlugin>) pluginMapField.get(pluginManager);
pluginMapInstance.remove(mapId);
Field entryMapField = pluginManager.getClass().getDeclaredField("entryMap");
entryMapField.setAccessible(true);
LinkedHashMap<String, PluginEntry> entryMapInstance = (LinkedHashMap<String, PluginEntry>) entryMapField.get(pluginManager);
entryMapInstance.remove(mapId);
} catch (Exception e) {
e.printStackTrace();
}
}
System.gc();
Runtime.getRuntime().gc();
callbackContext.success();
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void getMap(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
//------------------------------------------
// Create an instance of PluginMap class.
//------------------------------------------
JSONObject meta = args.getJSONObject(0);
String mapId = meta.getString("__pgmId");
PluginMap pluginMap = new PluginMap();
pluginMap.privateInitialize(mapId, cordova, webView, null);
pluginMap.initialize(cordova, webView);
pluginMap.mapCtrl = CordovaGoogleMaps.this;
pluginMap.self = pluginMap;
PluginEntry pluginEntry = new PluginEntry(mapId, pluginMap);
pluginManager.addService(pluginEntry);
pluginMap.getMap(args, callbackContext);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void getPanorama(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
//------------------------------------------
// Create an instance of PluginStreetView class.
//------------------------------------------
JSONObject meta = args.getJSONObject(0);
String mapId = meta.getString("__pgmId");
Log.d(TAG, "---> mapId = " + mapId);
PluginStreetViewPanorama pluginStreetView = new PluginStreetViewPanorama();
pluginStreetView.privateInitialize(mapId, cordova, webView, null);
pluginStreetView.initialize(cordova, webView);
pluginStreetView.mapCtrl = CordovaGoogleMaps.this;
pluginStreetView.self = pluginStreetView;
PluginEntry pluginEntry = new PluginEntry(mapId, pluginStreetView);
pluginManager.addService(pluginEntry);
pluginStreetView.getPanorama(args, callbackContext);
}
@Override
public void onStart() {
super.onStart();
Collection<PluginEntry>pluginEntries = pluginManager.getPluginEntries();
for (PluginEntry pluginEntry: pluginEntries) {
if (pluginEntry.service.startsWith("map_")) {
pluginEntry.plugin.onStart();
}
}
}
@Override
public void onStop() {
super.onStop();
Collection<PluginEntry>pluginEntries = pluginManager.getPluginEntries();
for (PluginEntry pluginEntry: pluginEntries) {
if (pluginEntry.service.startsWith("map_")) {
pluginEntry.plugin.onStop();
}
}
}
@Override
public void onPause(boolean multitasking) {
super.onPause(multitasking);
if (mPluginLayout != null) {
mPluginLayout.stopTimer();
}
Collection<PluginEntry>pluginEntries = pluginManager.getPluginEntries();
for (PluginEntry pluginEntry: pluginEntries) {
if (pluginEntry.service.startsWith("map_")) {
pluginEntry.plugin.onPause(multitasking);
}
}
}
@Override
public void onResume(boolean multitasking) {
Collection<PluginEntry>pluginEntries = pluginManager.getPluginEntries();
for (Iterator<PluginEntry> iterator = pluginEntries.iterator(); iterator.hasNext();) {
PluginEntry pluginEntry = iterator.next();
if (pluginEntry.service.startsWith("map_")) {
pluginEntry.plugin.onResume(multitasking);
}
}
}
@Override
public void onDestroy() {
super.onDestroy();
Collection<PluginEntry>pluginEntries = pluginManager.getPluginEntries();
for (PluginEntry pluginEntry: pluginEntries) {
if (pluginEntry.service.startsWith("map_")) {
pluginEntry.plugin.onDestroy();
}
}
}
/**
* Called by the system when the device configuration changes while your activity is running.
*
* @param newConfig The new device configuration
*/
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
PluginMap pluginMap;
Collection<PluginEntry> collection = pluginManager.getPluginEntries();
for (PluginEntry entry: collection) {
if ("plugin.google.maps.PluginMap".equals(entry.pluginClass) && entry.plugin != null) {
pluginMap = (PluginMap)entry.plugin;
if (pluginMap.map != null) {
// Trigger the CAMERA_MOVE_END mandatory
pluginMap.onCameraIdle();
}
}
}
}
}, 500);
/*
// Checks the orientation of the screen
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
Toast.makeText(activity, "landscape", Toast.LENGTH_SHORT).show();
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
Toast.makeText(activity, "portrait", Toast.LENGTH_SHORT).show();
}
*/
}
}
@@ -0,0 +1,24 @@
package plugin.google.maps;
import android.view.View;
import android.view.ViewGroup;
import org.apache.cordova.CallbackContext;
import org.json.JSONArray;
public interface IPluginView {
boolean getVisible();
boolean getClickable();
String getDivId();
String getOverlayId();
ViewGroup getView();
int getViewDepth();
void onDestroy();
void onStart();
void onStop();
void onPause(boolean multitasking);
void onResume(boolean multitasking);
void remove(JSONArray args, final CallbackContext callbackContext);
void attachToWebView(JSONArray args, final CallbackContext callbackContext);
void detachFromWebView(JSONArray args, final CallbackContext callbackContext);
}
@@ -0,0 +1,244 @@
package plugin.google.maps;
import android.annotation.SuppressLint;
import android.content.res.Resources;
import android.util.Log;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.Circle;
import com.google.android.gms.maps.model.GroundOverlay;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.Polygon;
import com.google.android.gms.maps.model.Polyline;
import com.google.android.gms.maps.model.TileOverlay;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.PluginEntry;
import org.json.JSONArray;
import org.json.JSONException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyPlugin extends CordovaPlugin implements MyPluginInterface {
public MyPlugin self = null;
public final Map<String, Method> methods = new ConcurrentHashMap<String, Method>();
protected static ExecutorService executorService = null;
public CordovaGoogleMaps mapCtrl = null;
public GoogleMap map = null;
public PluginMap pluginMap = null;
protected boolean isRemoved = false;
protected static float density = Resources.getSystem().getDisplayMetrics().density;
public void setPluginMap(PluginMap pluginMap) {
this.pluginMap = pluginMap;
this.mapCtrl = pluginMap.mapCtrl;
this.map = pluginMap.map;
}
protected String TAG = "";
@SuppressLint("UseSparseArrays")
@Override
public void initialize(CordovaInterface cordova, final CordovaWebView webView) {
super.initialize(cordova, webView);
TAG = this.getServiceName();
if (executorService == null) {
cordova.getThreadPool().execute(new Runnable() {
@Override
public void run() {
executorService = Executors.newCachedThreadPool();
}
});
}
}
@Override
public boolean execute(final String action, final JSONArray args, final CallbackContext callbackContext) {
self = this;
executorService.submit(new Runnable() {
@Override
public void run() {
if (isRemoved) {
// Ignore every execute calls.
return;
}
synchronized (methods) {
if (methods.size() == 0) {
TAG = MyPlugin.this.getServiceName();
if (!TAG.contains("-")) {
if (TAG.startsWith("map")) {
mapCtrl.mPluginLayout.pluginOverlays.put(TAG, (PluginMap) MyPlugin.this);
} else if (TAG.startsWith("streetview")) {
mapCtrl.mPluginLayout.pluginOverlays.put(TAG, (PluginStreetViewPanorama) MyPlugin.this);
}
} else {
PluginEntry pluginEntry = new PluginEntry(TAG, MyPlugin.this);
pluginMap.plugins.put(TAG, pluginEntry);
}
//CordovaPlugin plugin = mapCtrl.webView.getPluginManager().getPlugin(this.getServiceName());
// Log.d("MyPlugin", "---> this = " + this);
// Log.d("MyPlugin", "---> plugin = " + plugin);
Method[] classMethods = self.getClass().getMethods();
for (Method classMethod : classMethods) {
methods.put(classMethod.getName(), classMethod);
}
}
// this.create(args, callbackContext);
// return true;
if (methods.containsKey(action)) {
if (self.mapCtrl.mPluginLayout.isDebug) {
try {
if (args != null && args.length() > 0) {
Log.d(TAG, "(debug)action=" + action + " args[0]=" + args.getString(0));
} else {
Log.d(TAG, "(debug)action=" + action);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
Method method = methods.get(action);
try {
if (isRemoved) {
// Ignore every execute calls.
return;
}
method.invoke(self, args, callbackContext);
} catch (IllegalAccessException e) {
e.printStackTrace();
callbackContext.error("Cannot access to the '" + action + "' method.");
} catch (InvocationTargetException e) {
e.printStackTrace();
callbackContext.error("Cannot access to the '" + action + "' method.");
}
}
}
}
});
return true;
}
protected void create(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
// dummy
}
protected synchronized Circle getCircle(String id) {
if (!pluginMap.objects.containsKey(id)) {
//Log.e(TAG, "---> can not find the circle : " + id);
return null;
}
return (Circle)pluginMap.objects.get(id);
}
protected synchronized GroundOverlay getGroundOverlay(String id) {
if (!pluginMap.objects.containsKey(id)) {
//Log.e(TAG, "---> can not find the ground overlay : " + id);
return null;
}
return (GroundOverlay)pluginMap.objects.get(id);
}
protected synchronized Marker getMarker(String id) {
if (!pluginMap.objects.containsKey(id)) {
//Log.e(TAG, "---> can not find the maker : " + id);
return null;
}
return (Marker)pluginMap.objects.get(id);
}
protected synchronized Polyline getPolyline(String id) {
if (!pluginMap.objects.containsKey(id)) {
//Log.e(TAG, "---> can not find the polyline : " + id);
return null;
}
return (Polyline)pluginMap.objects.get(id);
}
protected synchronized Polygon getPolygon(String id) {
if (!pluginMap.objects.containsKey(id)) {
//Log.e(TAG, "---> can not find the polygon : " + id);
return null;
}
return (Polygon)pluginMap.objects.get(id);
}
protected synchronized TileOverlay getTileOverlay(String id) {
if (!pluginMap.objects.containsKey(id)) {
//Log.e(TAG, "---> can not find the tileoverlay : " + id);
return null;
}
return (TileOverlay)pluginMap.objects.get(id);
}
protected void setInt(String methodName, String id, int value, final CallbackContext callbackContext) throws JSONException {
this.setValue(methodName, int.class, id, value, callbackContext);
}
protected void setFloat(String methodName, String id, float value, final CallbackContext callbackContext) throws JSONException {
this.setValue(methodName, float.class, id, value, callbackContext);
}
protected void setDouble(String methodName, String id, float value, final CallbackContext callbackContext) throws JSONException {
this.setValue(methodName, double.class, id, value, callbackContext);
}
protected void setString(String methodName, String id, String value, final CallbackContext callbackContext) throws JSONException {
this.setValue(methodName, String.class, id, value, callbackContext);
}
protected void setBoolean(String methodName, String id, Boolean value, final CallbackContext callbackContext) throws JSONException {
this.setValue(methodName, boolean.class, id, value, callbackContext);
}
private void setValue(String methodName, Class<?> methodClass, String id, final Object value, final CallbackContext callbackContext) throws JSONException {
if (!pluginMap.objects.containsKey(id)) {
return;
}
final Object object = pluginMap.objects.get(id);
try {
final Method method = object.getClass().getDeclaredMethod(methodName, methodClass);
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
try {
method.invoke(object, value);
callbackContext.success();
} catch (Exception e) {
e.printStackTrace();
callbackContext.error(e.getMessage());
}
}
});
} catch (Exception e) {
e.printStackTrace();
callbackContext.error(e.getMessage());
}
}
protected void clear() {
String[] keys = pluginMap.objects.keys.toArray(new String[pluginMap.objects.size()]);
Object object;
for (String key : keys) {
object = pluginMap.objects.remove(key);
object = null;
}
pluginMap.objects.clear();
}
protected void onOverlayEvent(String eventName, String overlayId, LatLng point) {
webView.loadUrl("javascript:plugin.google.maps.Map." +
"_onOverlayEvent(" +
"'" + eventName + "','" + overlayId + "', " +
"new window.plugin.google.maps.LatLng(" + point.latitude + "," + point.longitude + ")" +
")");
}
}
@@ -0,0 +1,16 @@
package plugin.google.maps;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaArgs;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaWebView;
import org.json.JSONArray;
import org.json.JSONException;
public interface MyPluginInterface {
public void initialize(CordovaInterface cordova, CordovaWebView webView);
public void setPluginMap(PluginMap pluginMap);
public boolean execute(String action, String rawArgs, CallbackContext callbackContext) throws JSONException;
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException;
public boolean execute(String action, CordovaArgs args, CallbackContext callbackContext) throws JSONException;
}
@@ -0,0 +1,716 @@
package plugin.google.maps;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.RectF;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.AbsoluteLayout;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import org.apache.cordova.CordovaWebView;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
@SuppressWarnings("deprecation")
public class MyPluginLayout extends FrameLayout implements ViewTreeObserver.OnScrollChangedListener, ViewTreeObserver.OnGlobalLayoutListener {
private static final String TAG = "MyPluginLayout";
private View browserView;
private Context context;
private FrontLayerLayout frontLayer;
private ScrollView scrollView;
public FrameLayout scrollFrameLayout;
public Map<String, IPluginView> pluginOverlays = new ConcurrentHashMap<String, IPluginView>();
private Map<String, TouchableWrapper> touchableWrappers = new ConcurrentHashMap<String, TouchableWrapper>();
private boolean isScrolling = false;
public boolean isDebug = false;
public final Object _lockHtmlNodes = new Object();
public Map<String, String> CACHE_FIND_DOM = new ConcurrentHashMap<String, String>();
public Map<String, Bundle> HTMLNodes = new ConcurrentHashMap<String, Bundle>();
public Map<String, RectF> HTMLNodeRectFs = new ConcurrentHashMap<String, RectF>();
private Activity mActivity = null;
private Paint debugPaint = new Paint();
public boolean stopFlag = false;
public boolean needUpdatePosition = false;
public boolean isSuspended = false;
private float zoomScale;
private static final Object timerLock = new Object();
private static Timer redrawTimer;
@Override
public void onGlobalLayout() {
ViewTreeObserver observer = browserView.getViewTreeObserver();
observer.removeGlobalOnLayoutListener(this);
observer.addOnScrollChangedListener(this);
}
private Runnable resizeWorker = new Runnable() {
@Override
public void run() {
final int scrollY = browserView.getScrollY();
final int scrollX = browserView.getScrollX();
final int webviewWidth = browserView.getWidth();
final int webviewHeight = browserView.getHeight();
Set<String> keySet = pluginOverlays.keySet();
String[] toArrayBuf = new String[pluginOverlays.size()];
String[] mapIds = keySet.toArray(toArrayBuf);
String mapId;
IPluginView pluginOverlay;
RectF drawRect;
for (int i = 0; i < mapIds.length; i++) {
mapId = mapIds[i];
if (mapId == null) {
continue;
}
pluginOverlay = pluginOverlays.get(mapId);
if (pluginOverlay == null || pluginOverlay.getDivId() == null) {
continue;
}
drawRect = HTMLNodeRectFs.get(pluginOverlay.getDivId());
if (drawRect == null || drawRect.left == 0 && drawRect.top == 0 && drawRect.width() == 0 && drawRect.height() == 0) {
continue;
}
int width = (int)drawRect.width();
int height = (int)drawRect.height();
int x = (int) drawRect.left;
int y = (int) drawRect.top + scrollY;
if (pluginOverlay.getVisible()) {
if (pluginOverlay.getOverlayId().startsWith("streetview_")) {
if (y + height >= scrollY &&
x + width >= scrollX &&
y < scrollY + webviewHeight &&
x < scrollX + webviewWidth) {
MyPluginLayout.this.addPluginOverlay(pluginOverlay);
} else {
scrollFrameLayout.removeView(pluginOverlay.getView());
pluginOverlay.getView().removeView(touchableWrappers.remove(pluginOverlay.getOverlayId()));
}
}
}
ViewGroup.LayoutParams lParams = pluginOverlay.getView().getLayoutParams();
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) lParams;
//Log.d("MyPluginLayout", "-->FrameLayout x = " + x + ", y = " + y + ", w = " + width + ", h = " + height);
if (params.leftMargin == x && params.topMargin == y &&
params.width == width && params.height == height) {
continue;
}
params.width = width;
params.height = height;
params.leftMargin = x;
params.topMargin = y;
pluginOverlay.getView().setLayoutParams(params);
}
}
};
public void updateMapPositions() {
mActivity.runOnUiThread(resizeWorker);
}
private class ResizeTask extends TimerTask {
@Override
public void run() {
mActivity.runOnUiThread(resizeWorker);
}
}
@SuppressLint("NewApi")
public MyPluginLayout(CordovaWebView webView, Activity activity) {
super(webView.getView().getContext());
this.browserView = webView.getView();
browserView.getViewTreeObserver().addOnGlobalLayoutListener(this);
mActivity = activity;
ViewGroup root = (ViewGroup) browserView.getParent();
this.context = browserView.getContext();
//if (Build.VERSION.SDK_INT >= 21 || "org.xwalk.core.XWalkView".equals(browserView.getClass().getName())) {
// browserView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
//}
zoomScale = Resources.getSystem().getDisplayMetrics().density;
Log.e(TAG, "--> zoomScale = " + zoomScale);
frontLayer = new FrontLayerLayout(this.context);
scrollView = new ScrollView(this.context);
scrollView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
root.removeView(browserView);
frontLayer.addView(browserView);
scrollFrameLayout = new FrameLayout(this.context);
scrollFrameLayout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
View dummyView = new View(this.context);
dummyView.setLayoutParams(new LayoutParams(1, 99999));
scrollFrameLayout.addView(dummyView);
scrollView.setHorizontalScrollBarEnabled(true);
scrollView.setVerticalScrollBarEnabled(true);
scrollView.addView(scrollFrameLayout);
browserView.setDrawingCacheEnabled(false);
this.addView(scrollView);
this.addView(frontLayer);
root.addView(this);
browserView.setBackgroundColor(Color.TRANSPARENT);
/*
if("org.xwalk.core.XWalkView".equals(browserView.getClass().getName())
|| "org.crosswalk.engine.XWalkCordovaView".equals(browserView.getClass().getName())) {
try {
// view.setZOrderOnTop(true)
// Called just in time as with root.setBackground(...) the color
// come in front and take the whole screen
browserView.getClass().getMethod("setZOrderOnTop", boolean.class)
.invoke(browserView, true);
}
catch(Exception e) {
e.printStackTrace();
}
}
*/
scrollView.setHorizontalScrollBarEnabled(false);
scrollView.setVerticalScrollBarEnabled(false);
startTimer();
}
public synchronized void stopTimer() {
synchronized (timerLock) {
try {
if (redrawTimer != null) {
redrawTimer.cancel();
redrawTimer.purge();
ResizeTask task = new ResizeTask();
task.run();
isSuspended = true;
}
} catch (Exception e) {
e.printStackTrace();
}
redrawTimer = null;
}
}
public synchronized void startTimer() {
synchronized (timerLock) {
if (redrawTimer != null) {
return;
}
redrawTimer = new Timer();
redrawTimer.scheduleAtFixedRate(new ResizeTask(), 0, 25);
//mActivity.getWindow().getDecorView().requestFocus(); <--- This changes thread number sometimes. Do not use this!
isSuspended = false;
}
}
public void clearHtmlElements() {
synchronized (_lockHtmlNodes) {
String[] keys = HTMLNodes.keySet().toArray(new String[HTMLNodes.size()]);
for (int i = 0; i < HTMLNodes.size(); i++) {
HTMLNodes.remove(keys[i]);
HTMLNodeRectFs.remove(keys[i]);
}
}
}
public void putHTMLElements(JSONObject elements) {
Map<String, Bundle> newBuffer = new ConcurrentHashMap<String, Bundle>();
Map<String, RectF> newBufferRectFs = new ConcurrentHashMap<String, RectF>();
Bundle elementsBundle = PluginUtil.Json2Bundle(elements);
Iterator<String> domIDs = elementsBundle.keySet().iterator();
String domId;
Bundle domInfo, size;
while (domIDs.hasNext()) {
domId = domIDs.next();
domInfo = elementsBundle.getBundle(domId);
if (domInfo == null) {
continue;
}
size = domInfo.getBundle("size");
if (size == null) {
continue;
}
RectF rectF = new RectF();
rectF.left = (float)(Double.parseDouble(size.get("left") + "") * zoomScale);
rectF.top = (float)(Double.parseDouble(size.get("top") + "") * zoomScale);
rectF.right = (float)(Double.parseDouble(size.get("right") + "") * zoomScale);
rectF.bottom = (float)(Double.parseDouble(size.get("bottom") + "") * zoomScale);
newBufferRectFs.put(domId, rectF);
domInfo.remove("size");
newBuffer.put(domId, domInfo);
}
Map<String, Bundle> oldBuffer = HTMLNodes;
Map<String, RectF> oldBufferRectFs = HTMLNodeRectFs;
synchronized (_lockHtmlNodes) {
HTMLNodes = newBuffer;
HTMLNodeRectFs = newBufferRectFs;
}
String[] keys = oldBuffer.keySet().toArray(new String[oldBuffer.size()]);
for (int i = 0; i < oldBuffer.size(); i++) {
oldBuffer.remove(keys[i]);
oldBufferRectFs.remove(keys[i]);
}
}
public void setDebug(final boolean debug) {
this.isDebug = debug;
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (debug) {
inValidate();
}
}
});
}
public IPluginView removePluginOverlay(final String overlayId) {
if (pluginOverlays == null || !pluginOverlays.containsKey(overlayId)) {
return null;
}
final IPluginView pluginOverlay = pluginOverlays.remove(overlayId);
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
scrollFrameLayout.removeView(pluginOverlay.getView());
pluginOverlay.getView().removeView(touchableWrappers.remove(overlayId));
//Log.d("MyPluginLayout", "--> removePluginMap / overlayId = " + overlayId);
//mActivity.getWindow().getDecorView().requestFocus();
} catch (Exception e) {
// ignore
//e.printStackTrace();
}
}
});
return pluginOverlay;
}
public void addPluginOverlay(final IPluginView pluginOverlay) {
if (pluginOverlay.getDivId() == null) {
return;
}
if (!HTMLNodes.containsKey(pluginOverlay.getDivId())) {
Log.e(TAG, "----> 200x200");
Bundle dummyInfo = new Bundle();
dummyInfo.putDouble("offsetX", 0);
dummyInfo.putDouble("offsetY", 3000);
dummyInfo.putBoolean("isDummy", true);
HTMLNodes.put(pluginOverlay.getDivId(), dummyInfo);
HTMLNodeRectFs.put(pluginOverlay.getDivId(), new RectF(0, 3000, 200, 200));
}
pluginOverlays.put(pluginOverlay.getOverlayId(), pluginOverlay);
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (pluginOverlay.getView().getParent() == null) {
TouchableWrapper wrapper = new TouchableWrapper(context);
touchableWrappers.put(pluginOverlay.getOverlayId(), wrapper);
pluginOverlay.getView().addView(wrapper);
int depth = pluginOverlay.getViewDepth();
int childCnt = scrollFrameLayout.getChildCount();
View view;
int index = childCnt;
for (int i = childCnt - 1; i >= 0; i--) {
view = scrollFrameLayout.getChildAt(i);
if (view.getTag() == null) {
continue;
}
if (Integer.parseInt(view.getTag() + "") < depth) {
index = i;
break;
}
}
scrollFrameLayout.addView(pluginOverlay.getView(), (childCnt - index));
}
//mActivity.getWindow().getDecorView().requestFocus();
//updateViewPosition(pluginMap.mapId);
}
});
}
public void scrollTo(int x, int y) {
this.scrollView.scrollTo(x, y);
}
public void inValidate() {
this.frontLayer.invalidate();
}
@Override
public void onScrollChanged() {
//Log.d("Layout", "---> onScrollChanged");
scrollView.scrollTo(browserView.getScrollX(), browserView.getScrollY());
}
private class Overflow {
boolean cropX;
boolean cropY;
RectF rect;
}
private class FrontLayerLayout extends FrameLayout {
public FrontLayerLayout(Context context) {
super(context);
this.setWillNotDraw(false);
}
private String findClickedDom(String domId, PointF clickPoint, boolean isMapChild, Overflow overflow) {
if (CACHE_FIND_DOM.containsKey(domId)) {
String cacheResult = CACHE_FIND_DOM.get(domId);
if ("(null)".equals(cacheResult)) {
cacheResult = null;
}
return cacheResult;
}
Overflow overflow1 = null;
if (overflow != null) {
overflow1 = new Overflow();
overflow1.cropX = overflow.cropX;
overflow1.cropY = overflow.cropY;
overflow1.rect = new RectF(overflow.rect.left, overflow.rect.top, overflow.rect.right, overflow.rect.bottom);
}
//Log.d(TAG, "-(findClickedDom)domId = " + domId + ", clickPoint = " + clickPoint.x + ", " + clickPoint.y);
String maxDomId = null;
RectF rect, rect2, rect3;
Bundle domInfo = HTMLNodes.get(domId);
Bundle zIndexProp;
int containMapCnt = 0;
if (domInfo.containsKey("containMapIDs")) {
Set<String> keys = domInfo.getBundle("containMapIDs").keySet();
if (keys != null) {
containMapCnt = keys.size();
}
}
isMapChild = isMapChild || domInfo.getBoolean("isMap", false);
String pointerEvents = domInfo.getString("pointerEvents");
String overflowX = domInfo.getString("overflowX");
String overflowY = domInfo.getString("overflowY");
if ("hidden".equals(overflowX) || "scroll".equals(overflowX) ||
"hidden".equals(overflowY) || "scroll".equals(overflowY)) {
overflow1 = new Overflow();
overflow1.cropX = "hidden".equals(overflowX) || "scroll".equals(overflowX);
overflow1.cropY = "hidden".equals(overflowY) || "scroll".equals(overflowY);
rect3 = HTMLNodeRectFs.get(domId);
overflow1.rect = new RectF(rect3.left, rect3.top, rect3.right, rect3.bottom);
} else if ("visible".equals(overflowX) || "visible".equals(overflowY)) {
if (overflow1 != null) {
overflow1.cropX = !"visible".equals(overflowX);
overflow1.cropY = !"visible".equals(overflowY);
}
}
//if (overflow1 != null) {
// Log.d(TAG, "--domId = " + domId + ", domInfo = " + domInfo + "\n" +
// " overflow1 cropX= " + overflow1.cropX + ", cropY = " + overflow1.cropY + ",\n" +
// " rect = " + overflow1.rect.toShortString());
//} else {
// Log.d(TAG, "--domId = " + domId + ", domInfo = " + domInfo);
//}
zIndexProp = domInfo.getBundle("zIndex");
ArrayList<String> children = domInfo.getStringArrayList("children");
if ((containMapCnt > 0 || isMapChild || "none".equals(pointerEvents) || zIndexProp.getBoolean("isInherit")) && children != null && children.size() > 0) {
int maxZindex = (int) Double.NEGATIVE_INFINITY;
int zIndexValue;
String childId, grandChildId;
ArrayList<String> grandChildren;
for (int i = children.size() - 1; i >= 0; i--) {
childId = children.get(i);
domInfo = HTMLNodes.get(childId);
if (domInfo == null) {
continue;
}
zIndexProp = domInfo.getBundle("zIndex");
zIndexValue = zIndexProp.getInt("z");
if (maxZindex < zIndexValue || zIndexProp.getBoolean("isInherit")) {
grandChildren = domInfo.getStringArrayList("children");
if (grandChildren == null || grandChildren.size() == 0) {
rect = HTMLNodeRectFs.get(childId);
rect2 = new RectF(rect.left, rect.top, rect.right, rect.bottom);
if (overflow1 != null && !"root".equals(domId) ) {
overflow1.rect = new RectF(HTMLNodeRectFs.get(domId));
if (overflow1.cropX) {
rect2.left = Math.max(rect2.left, overflow1.rect.left);
rect2.right = Math.min(rect2.right, overflow1.rect.right);
}
if (overflow1.cropY) {
rect2.top = Math.max(rect2.top, overflow1.rect.top);
rect2.bottom = Math.min(rect2.bottom, overflow1.rect.bottom);
}
}
if (!rect2.contains(clickPoint.x, clickPoint.y)) {
continue;
}
//Log.d(TAG, "---childId = " + childId + ", domInfo = " + domInfo);
if ("none".equals(domInfo.getString("pointerEvents"))) {
continue;
}
//Log.d(TAG, "----childId = " + childId + ", maxZindex = " + maxZindex + ", zIndexValue = " + zIndexValue);
if (maxZindex < zIndexValue) {
maxZindex = zIndexValue;
maxDomId = childId;
//Log.i(TAG, "-----current = " + childId);
}
} else {
if (zIndexValue < maxZindex) {
//Log.i(TAG, "-----skip because " + childId + " is " + zIndexValue + " < " + maxZindex);
continue;
}
grandChildId = findClickedDom(childId, clickPoint, isMapChild, overflow1);
if (grandChildId == null) {
grandChildId = childId;
} else {
zIndexValue = HTMLNodes.get(grandChildId).getBundle("zIndex").getInt("z");
}
rect = HTMLNodeRectFs.get(grandChildId);
//rect2 = new RectF(rect);
//Log.d(TAG, "---findClickedDom("+ childId + ") -> " + grandChildId + ",rect = " + rect2.toShortString());
/*
if (overflow1 != null && !"root".equals(domId) ) {
overflow1.rect = new RectF(HTMLNodeRectFs.get(domId));
if (overflow1.cropX) {
rect2.left = Math.max(rect2.left, overflow1.rect.left);
rect2.right = Math.min(rect2.right, overflow1.rect.right);
}
if (overflow1.cropY) {
rect2.top = Math.max(rect2.top, overflow1.rect.top);
rect2.bottom = Math.min(rect2.bottom, overflow1.rect.bottom);
}
//Log.d(TAG, "---overflow(" + domId + ") -> " + overflow1.rect.toShortString() + ", \n"+
// " str=" + HTMLNodeRectFs.get(domId) + ",\n" +
// " overflowX = " + overflowX + ", overflowY = " + overflowY +
// " cropX = " + overflow1.cropX + ", cropY = " + overflow1.cropY);
}
*/
if (!rect.contains(clickPoint.x, clickPoint.y)) {
//Log.e(TAG, "----the click point is not in this element(" +grandChildId + "), rect = " + rect2.toShortString() + ", click = " + clickPoint.x + "," + clickPoint.y );
continue;
}
domInfo = HTMLNodes.get(grandChildId);
//Log.d(TAG, "----grandChildId = " + grandChildId + ", domInfo = " + domInfo);
if ("none".equals(domInfo.getString("pointerEvents"))) {
//Log.e(TAG, "-----grandChildId(" + grandChildId + ") is " + domInfo.getString("pointerEvents"));
continue;
}
//Log.d(TAG, "----grandChildId = " + grandChildId + ", maxZindex = " + maxZindex + ", zIndexValue = " + zIndexValue);
if (maxZindex < zIndexValue) {
maxZindex = zIndexValue;
maxDomId = grandChildId;
//Log.i(TAG, "-----current = " + grandChildId);
}
}
}
}
}
if (maxDomId == null && !"root".equals(domId)) {
if ("none".equals(pointerEvents)) {
CACHE_FIND_DOM.put(domId, "(null)");
return null;
}
rect = HTMLNodeRectFs.get(domId);
rect2 = new RectF(rect.left, rect.top, rect.right, rect.bottom);
// if (overflow1 != null ) {
// overflow1.rect = new RectF(HTMLNodeRectFs.get(domId));
// if (overflow1.cropX) {
// rect2.left = Math.max(rect2.left, overflow1.rect.left);
// rect2.right = Math.min(rect2.right, overflow1.rect.right);
// }
// if (overflow1.cropY) {
// rect2.top = Math.max(rect2.top, overflow1.rect.top);
// rect2.bottom = Math.min(rect2.bottom, overflow1.rect.bottom);
// }
// }
if (!rect2.contains(clickPoint.x, clickPoint.y)) {
//Log.e(TAG, "--the click point is not in this element(" +domId + "), rect = " + rect2.toShortString() + ", click = " + clickPoint.x + "," + clickPoint.y );
CACHE_FIND_DOM.put(domId, "(null)");
return null;
}
maxDomId = domId;
}
if (maxDomId == null) {
CACHE_FIND_DOM.put(domId, "(null)");
} else {
CACHE_FIND_DOM.put(domId, maxDomId);
}
//Log.i(TAG, "-maxDomId = " + maxDomId + ", domId = " + domId);
return maxDomId;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (pluginOverlays == null || pluginOverlays.size() == 0) {
return false;
}
MyPluginLayout.this.stopFlag = true;
int action = event.getAction();
//Log.d("FrontLayerLayout", "----> action = " + action + ", isScrolling = " + isScrolling);
// The scroll action that started in the browser region is end.
isScrolling = action != MotionEvent.ACTION_UP && isScrolling;
if (isScrolling) {
MyPluginLayout.this.stopFlag = false;
return false;
}
IPluginView pluginOverlay;
Iterator<Map.Entry<String, IPluginView>> iterator = pluginOverlays.entrySet().iterator();
Entry<String, IPluginView> entry;
PointF clickPoint = new PointF(event.getX(), event.getY());
RectF drawRect;
synchronized (_lockHtmlNodes) {
CACHE_FIND_DOM.clear();
String clickedDomId = findClickedDom("root", clickPoint, false, null);
//Log.d(TAG, "----clickedDomId = " + clickedDomId);
while (iterator.hasNext()) {
entry = iterator.next();
pluginOverlay = entry.getValue();
//-----------------------
// Is the map clickable?
//-----------------------
if (!pluginOverlay.getVisible() || !pluginOverlay.getClickable()) {
continue;
}
if (pluginOverlay.getDivId() == null) {
continue;
}
//------------------------------------------------
// Is the clicked point is in the map rectangle?
//------------------------------------------------
drawRect = HTMLNodeRectFs.get(pluginOverlay.getDivId());
if (drawRect == null || !drawRect.contains(clickPoint.x, clickPoint.y)) {
continue;
}
if (pluginOverlay.getDivId().equals(clickedDomId)) {
return true;
}
}
}
isScrolling = (action == MotionEvent.ACTION_DOWN) || isScrolling;
//browserView.requestFocus(View.FOCUS_DOWN);
return false;
}
@Override
protected void onDraw(Canvas canvas) {
if (isSuspended || HTMLNodes.isEmpty() || !isDebug) {
return;
}
IPluginView pluginOverlay;
Iterator<Map.Entry<String, IPluginView>> iterator = pluginOverlays.entrySet().iterator();
Entry<String, IPluginView> entry;
RectF mapRect;
synchronized (_lockHtmlNodes) {
while (iterator.hasNext()) {
entry = iterator.next();
pluginOverlay = entry.getValue();
if (pluginOverlay.getDivId() == null) {
continue;
}
mapRect = HTMLNodeRectFs.get(pluginOverlay.getDivId());
debugPaint.setColor(Color.argb(100, 0, 255, 0));
canvas.drawRect(mapRect, debugPaint);
}
}
}
}
private class TouchableWrapper extends FrameLayout {
public TouchableWrapper(Context context) {
super(context);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_UP) {
scrollView.requestDisallowInterceptTouchEvent(true);
}
return super.dispatchTouchEvent(event);
}
}
}
@@ -0,0 +1,46 @@
package plugin.google.maps;
import android.util.LruCache;
import java.util.HashSet;
public class ObjectCache {
public final HashSet<String> keys = new HashSet<String>();
private LruCache<String, Object> objects = new LruCache<String, Object>(1024 * 1024 * 10);
public boolean containsKey(String key) {
return keys.contains(key);
}
public void put(String key, Object object) {
keys.add(key);
objects.put(key, object);
}
public Object remove(String key) {
keys.remove(key);
return objects.remove(key);
}
public Object get(String key) {
return objects.get(key);
}
public int size() {
return objects.size();
}
public void clear() {
for (String key : keys) {
objects.remove(key);
}
keys.clear();
}
public void destroy() {
objects.evictAll();
}
}
@@ -0,0 +1,6 @@
package plugin.google.maps;
public interface PluginAsyncInterface {
public void onPostExecute(Object object);
public void onError(String errorMsg);
}
@@ -0,0 +1,267 @@
package plugin.google.maps;
import com.google.android.gms.maps.model.Circle;
import com.google.android.gms.maps.model.CircleOptions;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import org.apache.cordova.CallbackContext;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class PluginCircle extends MyPlugin implements MyPluginInterface {
private String circleHashCode;
/**
* Create circle
* @param args
* @param callbackContext
* @throws JSONException
*/
public void create(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
final CircleOptions circleOptions = new CircleOptions();
int color;
final JSONObject properties = new JSONObject();
JSONObject opts = args.getJSONObject(1);
final String hashCode = args.getString(2);
circleHashCode = hashCode;
if (opts.has("center")) {
JSONObject center = opts.getJSONObject("center");
circleOptions.center(new LatLng(center.getDouble("lat"), center.getDouble("lng")));
}
if (opts.has("radius")) {
circleOptions.radius(opts.getDouble("radius"));
}
if (opts.has("strokeColor")) {
color = PluginUtil.parsePluginColor(opts.getJSONArray("strokeColor"));
circleOptions.strokeColor(color);
}
if (opts.has("fillColor")) {
color = PluginUtil.parsePluginColor(opts.getJSONArray("fillColor"));
circleOptions.fillColor(color);
}
if (opts.has("strokeWidth")) {
circleOptions.strokeWidth((int)(opts.getDouble("strokeWidth") * density));
}
if (opts.has("visible")) {
circleOptions.visible(opts.getBoolean("visible"));
}
if (opts.has("zIndex")) {
circleOptions.zIndex(opts.getInt("zIndex"));
}
if (opts.has("clickable")) {
properties.put("isClickable", opts.getBoolean("clickable"));
} else {
properties.put("isClickable", true);
}
properties.put("isVisible", circleOptions.isVisible());
// Since this plugin provide own click detection,
// disable default clickable feature.
circleOptions.clickable(false);
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
Circle circle = map.addCircle(circleOptions);
circle.setTag(hashCode);
pluginMap.objects.put("circle_" + hashCode, circle);
pluginMap.objects.put("circle_property_" + hashCode, properties);
// Recalculate the circle bounds
LatLngBounds bounds = PluginUtil.getBoundsFromCircle(circleOptions.getCenter(), circleOptions.getRadius());
pluginMap.objects.put("circle_bounds_" + hashCode, bounds);
JSONObject result = new JSONObject();
try {
result.put("hashCode", hashCode);
result.put("__pgmId", "circle_" + hashCode);
callbackContext.success(result);
} catch (JSONException e) {
e.printStackTrace();
callbackContext.error(e.getMessage() + "");
}
}
});
}
/**
* set center
* @param args
* @param callbackContext
* @throws JSONException
*/
@SuppressWarnings("unused")
public void setCenter(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
final LatLng center = new LatLng(args.getDouble(1), args.getDouble(2));
final Circle circle = this.getCircle(id);
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
// Recalculate the circle bounds
String propertyId = "circle_bounds_" + circleHashCode;
LatLngBounds bounds = PluginUtil.getBoundsFromCircle(circle.getCenter(), circle.getRadius());
pluginMap.objects.put(propertyId, bounds);
circle.setCenter(center);
callbackContext.success();
}
});
}
/**
* set fill color
* @param args
* @param callbackContext
* @throws JSONException
*/
@SuppressWarnings("unused")
public void setFillColor(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
int color = PluginUtil.parsePluginColor(args.getJSONArray(1));
this.setInt("setFillColor", id, color, callbackContext);
}
/**
* set stroke color
* @param args
* @param callbackContext
* @throws JSONException
*/
@SuppressWarnings("unused")
public void setStrokeColor(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
int color = PluginUtil.parsePluginColor(args.getJSONArray(1));
this.setInt("setStrokeColor", id, color, callbackContext);
}
/**
* set stroke width
* @param args
* @param callbackContext
* @throws JSONException
*/
@SuppressWarnings("unused")
public void setStrokeWidth(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
float width = (float)(args.getDouble(1) * density);
this.setFloat("setStrokeWidth", id, width, callbackContext);
}
/**
* set radius
* @param args
* @param callbackContext
* @throws JSONException
*/
@SuppressWarnings("unused")
public void setRadius(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
final float radius = (float) args.getDouble(1);
final Circle circle = this.getCircle(id);
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
// Recalculate the circle bounds
String propertyId = "circle_bounds_" + circleHashCode;
LatLngBounds bounds = PluginUtil.getBoundsFromCircle(circle.getCenter(), circle.getRadius());
pluginMap.objects.put(propertyId, bounds);
// Update the overlay
circle.setRadius(radius);
callbackContext.success();
}
});
}
/**
* set z-index
* @param args
* @param callbackContext
* @throws JSONException
*/
@SuppressWarnings("unused")
public void setZIndex(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
float zIndex = (float) args.getDouble(1);
this.setFloat("setZIndex", id, zIndex, callbackContext);
}
/**
* Set visibility for the object
* @param args
* @param callbackContext
* @throws JSONException
*/
public void setVisible(JSONArray args, CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
final boolean isVisible = args.getBoolean(1);
final Circle circle = this.getCircle(id);
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
circle.setVisible(isVisible);
}
});
String propertyId = "circle_property_" + circleHashCode;
JSONObject properties = (JSONObject)pluginMap.objects.get(propertyId);
properties.put("isVisible", isVisible);
pluginMap.objects.put(propertyId, properties);
callbackContext.success();
}
/**
* Set clickable for the object
* @param args
* @param callbackContext
* @throws JSONException
*/
public void setClickable(JSONArray args, CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
final boolean clickable = args.getBoolean(1);
String propertyId = id.replace("circle_", "circle_property_");
JSONObject properties = (JSONObject)pluginMap.objects.get(propertyId);
properties.put("isClickable", clickable);
pluginMap.objects.put(propertyId, properties);
callbackContext.success();
}
/**
* Remove the circle
* @param args
* @param callbackContext
* @throws JSONException
*/
public void remove(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
final String id = args.getString(0);
final Circle circle = this.getCircle(id);
if (circle == null) {
callbackContext.success();
return;
}
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
circle.remove();
if (pluginMap.objects != null) {
pluginMap.objects.remove(id);
}
callbackContext.success();
}
});
}
}
@@ -0,0 +1,133 @@
package plugin.google.maps;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.os.Handler;
import android.util.Log;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.PluginManager;
import org.apache.cordova.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import java.lang.reflect.Method;
public class PluginEnvironment extends CordovaPlugin {
public PluginManager pluginManager;
@Override
public void initialize(final CordovaInterface cordova, final CordovaWebView webView) {
pluginManager = webView.getPluginManager();
}
@Override
public boolean execute(final String action, final JSONArray args, final CallbackContext callbackContext) {
cordova.getThreadPool().submit(new Runnable() {
@Override
public void run() {
try {
Method method = PluginEnvironment.this.getClass().getDeclaredMethod(action, JSONArray.class, CallbackContext.class);
if (!method.isAccessible()) {
method.setAccessible(true);
}
method.invoke(PluginEnvironment.this, args, callbackContext);
} catch (Exception e) {
Log.e("CordovaLog", "An error occurred", e);
callbackContext.error(e.toString());
}
}
});
return true;
}
@SuppressWarnings("unused")
public void isAvailable(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
// ------------------------------
// Check of Google Play Services
// ------------------------------
int checkGooglePlayServices =
GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(cordova.getActivity());
if (checkGooglePlayServices != ConnectionResult.SUCCESS) {
String errorMsg = GoogleApiAvailability.getInstance().getErrorString(checkGooglePlayServices);
callbackContext.error(errorMsg);
try {
cordova.getActivity().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=com.google.android.gms")));
} catch (android.content.ActivityNotFoundException anfe) {
cordova.getActivity().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://play.google.com/store/apps/details?id=com.google.android.gms")));
}
// End the app (in order to prevent lots of crashes)
cordova.getActivity().finish();
return;
}
// ------------------------------
// Check of Google Maps Android API v2
// ------------------------------
try {
@SuppressWarnings({ "rawtypes" })
Class GoogleMapsClass = Class.forName("com.google.android.gms.maps.GoogleMap");
} catch (Exception e) {
Log.e("GoogleMaps", "Error", e);
callbackContext.error(e.getMessage());
return;
}
callbackContext.success();
}
/**
* Set the app background
* @param args Parameters given from JavaScript side
* @param callbackContext Callback contect for sending back the result.
* @throws JSONException
*/
@SuppressWarnings("unused")
public void setBackGroundColor(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
JSONArray rgba = args.getJSONArray(0);
int backgroundColor = Color.WHITE;
if (rgba != null && rgba.length() == 4) {
backgroundColor = PluginUtil.parsePluginColor(rgba);
}
final int finalBackgroundColor = backgroundColor;
final CordovaGoogleMaps googleMaps = (CordovaGoogleMaps) pluginManager.getPlugin("CordovaGoogleMaps");
Handler handler = new Handler(cordova.getActivity().getMainLooper());
handler.postDelayed(new Runnable() {
public void run() {
googleMaps.mPluginLayout.setBackgroundColor(finalBackgroundColor);
callbackContext.success();
}
}, googleMaps.initialized ? 0 : 250);
}
@SuppressWarnings("unused")
public Boolean getLicenseInfo(JSONArray args, final CallbackContext callbackContext) {
callbackContext.success("Google Maps Android API v2 does not need this method anymore. But for iOS, you still need to display the lincense.");
return true;
}
@SuppressWarnings("unused")
public Boolean setEnv(JSONArray args, final CallbackContext callbackContext) {
// stub
callbackContext.success();
return true;
}
}
@@ -0,0 +1,252 @@
package plugin.google.maps;
import android.location.Address;
import android.location.Geocoder;
import android.os.Bundle;
import com.google.android.gms.maps.model.LatLngBounds;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class PluginGeocoder extends CordovaPlugin {
// In order to prevent the TOO_MANY_REQUEST_ERROR (block by Google because too many request in short period),
// restricts the number of parallel threads.
//
// According from my tests, 5 threads are the best setting.
private static ExecutorService executorService = Executors.newFixedThreadPool(5);
private static Geocoder geocoder = null;
public void initialize(CordovaInterface cordova, final CordovaWebView webView) {
super.initialize(cordova, webView);
if (geocoder == null) {
geocoder = new Geocoder(cordova.getActivity());
}
}
@Override
public boolean execute(final String action, final JSONArray args, final CallbackContext callbackContext) {
executorService.submit(new Runnable() {
@Override
public void run() {
try {
if ("geocode".equals(action)) {
PluginGeocoder.this.geocode(args, callbackContext);
} else {
callbackContext.error("Method: Geocoder." + action + "() is not found.");
}
} catch (Exception e) {
e.printStackTrace();
callbackContext.error(e.getMessage() + "");
}
}
});
return true;
}
@SuppressWarnings("unused")
private void geocode(final JSONArray args,
final CallbackContext callbackContext) throws JSONException, IOException {
JSONObject opts = args.getJSONObject(0);
List<Address> geoResults = null;
JSONArray results = new JSONArray();
Iterator<Address> iterator = null;
int retryLimit = 10;
// Geocoding
if (!opts.has("position") && opts.has("address")) {
String address = opts.getString("address");
if (opts.has("bounds")) {
if (opts.has("bounds")) {
JSONArray points = opts.getJSONArray("bounds");
LatLngBounds bounds = PluginUtil.JSONArray2LatLngBounds(points);
boolean retry = true;
while (retry && retryLimit > 0) {
retry = false;
try {
geoResults = geocoder.getFromLocationName(address, 5,
bounds.southwest.latitude, bounds.southwest.longitude,
bounds.northeast.latitude, bounds.northeast.longitude);
} catch (IOException e) {
if ("Timed out waiting for response from server".equals(e.getMessage())) {
retry = true;
retryLimit--;
try {
Thread.sleep((int)(150 + Math.random() * 100)); // wait (150 + random)ms before retry
} catch (InterruptedException e1) {
//e1.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
if (geoResults.size() == 0) {
JSONObject methodResult = new JSONObject();
methodResult.put("idx", opts.getInt("idx"));
methodResult.put("results", new JSONArray());
PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, methodResult);
callbackContext.sendPluginResult(pluginResult);
return;
}
iterator = geoResults.iterator();
}
} else {
boolean retry = true;
while (retry) {
retry = false;
try {
geoResults = geocoder.getFromLocationName(address, 5);
} catch (IOException e) {
if ("Timed out waiting for response from server".equals(e.getMessage())) {
retry = true;
try {
Thread.sleep((int)(150 + Math.random() * 100)); // wait (150 + random)ms before retry
} catch (InterruptedException e1) {
//e1.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
if (geoResults == null || geoResults.size() == 0) {
JSONObject methodResult = new JSONObject();
methodResult.put("idx", opts.getInt("idx"));
methodResult.put("results", new JSONArray());
PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, methodResult);
callbackContext.sendPluginResult(pluginResult);
return;
}
iterator = geoResults.iterator();
}
}
// Reverse geocoding
if (opts.has("position") && !opts.has("address")) {
JSONObject position = opts.getJSONObject("position");
boolean retry = true;
while (retry && retryLimit > 0) {
retry = false;
try {
geoResults = geocoder.getFromLocation(
position.getDouble("lat"),
position.getDouble("lng"), 5);
} catch (IOException e) {
if ("Timed out waiting for response from server".equals(e.getMessage())) {
retry = true;
retryLimit--;
try {
Thread.sleep((int)(150 + Math.random() * 100)); // wait (150 + random)ms before retry
} catch (InterruptedException e1) {
//e1.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
if (geoResults == null || geoResults.size() == 0) {
//-------------
// No results
//-------------
JSONObject methodResult = new JSONObject();
methodResult.put("idx", opts.getInt("idx"));
methodResult.put("results", new JSONArray());
PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, methodResult);
callbackContext.sendPluginResult(pluginResult);
return;
}
iterator = geoResults.iterator();
}
if (iterator == null) {
callbackContext.error( "Invalid request for geocoder");
return;
}
while(iterator.hasNext()) {
JSONObject result = new JSONObject();
Address addr = iterator.next();
JSONObject position = new JSONObject();
position.put("lat", addr.getLatitude());
position.put("lng", addr.getLongitude());
result.put("position", position);
result.put("locality", addr.getLocality());
result.put("adminArea", addr.getAdminArea());
result.put("country", addr.getCountryName());
result.put("countryCode", addr.getCountryCode());
result.put("locale", addr.getLocale());
result.put("postalCode", addr.getPostalCode());
result.put("subAdminArea", addr.getSubAdminArea());
result.put("subLocality", addr.getSubLocality());
result.put("subThoroughfare", addr.getSubThoroughfare());
result.put("thoroughfare", addr.getThoroughfare());
JSONObject extra = new JSONObject();
extra.put("featureName", addr.getFeatureName());
extra.put("phone", addr.getPhone());
extra.put("permises", addr.getPremises());
extra.put("url", addr.getUrl());
JSONArray lines = new JSONArray();
// Handled cases where we have only one line address.
for (int i = 0; i <= addr.getMaxAddressLineIndex(); i++) {
lines.put(addr.getAddressLine(i));
}
extra.put("lines", lines);
Bundle extraInfo = addr.getExtras();
if (extraInfo != null) {
Set<String> keys = extraInfo.keySet();
Iterator<String> keyIterator = keys.iterator();
String key;
while(keyIterator.hasNext()) {
key = keyIterator.next();
extra.put(key, extraInfo.get(key));
}
}
result.put("extra", extra);
results.put(result);
}
JSONObject methodResult = new JSONObject();
methodResult.put("idx", opts.getInt("idx"));
methodResult.put("results", results);
PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, methodResult);
callbackContext.sendPluginResult(pluginResult);
}
}
@@ -0,0 +1,661 @@
package plugin.google.maps;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import com.google.android.gms.maps.model.BitmapDescriptor;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.GroundOverlay;
import com.google.android.gms.maps.model.GroundOverlayOptions;
import com.google.android.gms.maps.model.LatLngBounds;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaWebView;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.Set;
public class PluginGroundOverlay extends MyPlugin implements MyPluginInterface {
private HashMap<Integer, AsyncTask> imageLoadingTasks = new HashMap<Integer, AsyncTask>();
private final Object semaphore = new Object();
private HashMap<String, Bitmap> overlayImage = new HashMap<String, Bitmap>();
private boolean _clearDone = false;
@Override
public void initialize(final CordovaInterface cordova, final CordovaWebView webView) {
super.initialize(cordova, webView);
}
/**
* Create ground overlay
*
* @param args
* @param callbackContext
* @throws JSONException
*/
public void create(JSONArray args, CallbackContext callbackContext) throws JSONException {
JSONObject opts = args.getJSONObject(1);
String hashCode = args.getString(2);
_createGroundOverlay(hashCode, opts, callbackContext);
}
public void _createGroundOverlay(final String idBase, final JSONObject opts, final CallbackContext callbackContext) throws JSONException {
final GroundOverlayOptions options = new GroundOverlayOptions();
final JSONObject properties = new JSONObject();
options.anchor(0.5f, 0.5f);
if (opts.has("anchor")) {
JSONArray anchor = opts.getJSONArray("anchor");
options.anchor((float)anchor.getDouble(0), (float)anchor.getDouble(1));
}
if (opts.has("bearing")) {
options.bearing((float)opts.getDouble("bearing"));
}
if (opts.has("opacity")) {
options.transparency(1 - (float)opts.getDouble("opacity"));
}
if (opts.has("zIndex")) {
options.zIndex((float)opts.getDouble("zIndex"));
}
if (opts.has("visible")) {
options.visible(opts.getBoolean("visible"));
}
if (opts.has("bounds")) {
JSONArray points = opts.getJSONArray("bounds");
LatLngBounds bounds = PluginUtil.JSONArray2LatLngBounds(points);
options.positionFromBounds(bounds);
}
if (opts.has("clickable")) {
properties.put("isClickable", opts.getBoolean("clickable"));
} else {
properties.put("isClickable", true);
}
properties.put("isVisible", options.isVisible());
// Since this plugin provide own click detection,
// disable default clickable feature.
options.clickable(false);
// Load image
final String imageUrl = opts.getString("url");
setImage_(imageUrl, new PluginAsyncInterface() {
@Override
public void onPostExecute(Object object) {
if (object == null) {
callbackContext.error("Cannot create a ground overlay");
return;
}
AsyncLoadImage.AsyncLoadImageResult result = (AsyncLoadImage.AsyncLoadImageResult)object;
BitmapDescriptor bitmapDescriptor = BitmapDescriptorFactory.fromBitmap(result.image);
options.image(bitmapDescriptor);
//options.zIndex(Calendar.getInstance().getTimeInMillis());
GroundOverlay groundOverlay = self.map.addGroundOverlay(options);
overlayImage.put("groundoverlay_" + idBase, result.image);
groundOverlay.setTag(idBase);
pluginMap.objects.put("groundoverlay_" + idBase, groundOverlay);
pluginMap.objects.put("groundoverlay_bounds_" + idBase, groundOverlay.getBounds());
pluginMap.objects.put("groundoverlay_property_" + idBase, properties);
pluginMap.objects.put("groundoverlay_initOpts_" + idBase, opts);
JSONObject resultJSON = new JSONObject();
try {
resultJSON.put("hashCode", idBase);
resultJSON.put("__pgmId", "groundoverlay_" + idBase);
} catch (Exception e) {
e.printStackTrace();
}
callbackContext.success(resultJSON);
}
@Override
public void onError(String errorMsg) {
callbackContext.error(errorMsg);
}
});
}
@Override
protected void clear() {
_clearDone = false;
synchronized (semaphore) {
//--------------------------------------
// clean up properties as much as possible
//--------------------------------------
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
Set<String> keySet = pluginMap.objects.keys;
if (keySet.size() > 0) {
String[] objectIdArray = keySet.toArray(new String[keySet.size()]);
synchronized (pluginMap.objects) {
Bitmap image;
for (String objectId : objectIdArray) {
if (pluginMap.objects.containsKey(objectId)) {
if (objectId.startsWith("groundoverlay_") &&
!objectId.startsWith("groundoverlay_property_") &&
!objectId.startsWith("groundoverlay_initOpts_") &&
!objectId.startsWith("groundoverlay_bounds_")) {
GroundOverlay groundOverlay = (GroundOverlay) pluginMap.objects.remove(objectId);
image = overlayImage.remove(objectId);
if (image != null && !image.isRecycled()) {
image.recycle();
}
groundOverlay.setTag(null);
groundOverlay.remove();
groundOverlay = null;
} else {
Object object = pluginMap.objects.remove(objectId);
object = null;
}
}
}
}
synchronized (semaphore) {
_clearDone = true;
semaphore.notify();
}
}
}
});
Set<String> keySet = pluginMap.objects.keys;
if (keySet.size() > 0) {
try {
if (!_clearDone) {
semaphore.wait(1000);
}
} catch (InterruptedException e) {
// ignore
//e.printStackTrace();
}
}
}
}
/**
* Remove this tile layer
* @param args
* @param callbackContext
* @throws JSONException
*/
public void remove(JSONArray args, final CallbackContext callbackContext) throws JSONException {
final String id = args.getString(0);
final GroundOverlay groundOverlay = (GroundOverlay)pluginMap.objects.get(id);
if (groundOverlay == null) {
callbackContext.success();
return;
}
synchronized (pluginMap.objects) {
pluginMap.objects.remove(id.replace("groundoverlay_", "groundoverlay_property_"));
pluginMap.objects.remove(id.replace("groundoverlay_", "groundoverlay_initOpts_"));
pluginMap.objects.remove(id.replace("groundoverlay_", "groundoverlay_bounds_"));
}
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
synchronized (pluginMap.objects) {
GroundOverlay groundOverlay = (GroundOverlay) pluginMap.objects.remove(id);
if (groundOverlay != null) {
Bitmap image = overlayImage.remove(id);
if (image != null && !image.isRecycled()) {
image.recycle();
}
groundOverlay.remove();
}
}
callbackContext.success();
}
});
}
/**
* Set image of the ground-overlay
* @param args
* @param callbackContext
* @throws JSONException
*/
public void setImage(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
final String id = args.getString(0);
final String url = args.getString(1);
final String idBase = id.replace("groundoverlay_", "");
String propertyId = "groundoverlay_initOpts_" + idBase;
final JSONObject opts = (JSONObject) pluginMap.objects.get(propertyId);
opts.put("url", url);
pluginMap.objects.put(propertyId, opts);
setImage_(url, new PluginAsyncInterface() {
@Override
public void onPostExecute(Object object) {
if (object == null) {
callbackContext.error("[error]groundoverlay.setImage(" + url + ")");
return;
}
AsyncLoadImage.AsyncLoadImageResult result = (AsyncLoadImage.AsyncLoadImageResult) object;
GroundOverlay groundOverlay = getGroundOverlay(id);
if (groundOverlay != null) {
Bitmap currentBmp = overlayImage.remove(id);
if (currentBmp != null) {
currentBmp.recycle();
}
}
if (result.image != null) {
overlayImage.put(id, result.image);
groundOverlay.setImage(BitmapDescriptorFactory.fromBitmap(result.image));
callbackContext.success();
} else {
callbackContext.error("[error]groundoverlay.setImage(" + url + ")");
}
}
@Override
public void onError(String errorMsg) {
}
});
}
/**
* Set bounds
* @param args
* @param callbackContext
* @throws JSONException
*/
public void setBounds(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
final GroundOverlay groundOverlay = (GroundOverlay)pluginMap.objects.get(id);
String propertyId = id.replace("groundoverlay_", "groundoverlay_initOpts_");
JSONObject opts = (JSONObject) pluginMap.objects.get(propertyId);
JSONArray points = args.getJSONArray(1);
opts.put("bounds", points);
pluginMap.objects.put(propertyId, opts);
final LatLngBounds bounds = PluginUtil.JSONArray2LatLngBounds(points);
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
groundOverlay.setPositionFromBounds(bounds);
}
});
String boundsId = id.replace("groundoverlay_", "groundoverlay_bounds_");
pluginMap.objects.put(boundsId, bounds);
callbackContext.success();
}
/**
* Set opacity
* @param args
* @param callbackContext
* @throws JSONException
*/
public void setOpacity(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
float opacity = (float)args.getDouble(1);
String id = args.getString(0);
String propertyId = id.replace("groundoverlay_", "groundoverlay_initOpts_");
JSONObject opts = (JSONObject) pluginMap.objects.get(propertyId);
opts.put("opacity", opacity);
pluginMap.objects.put(propertyId, opts);
this.setFloat("setTransparency", id, 1 - opacity, callbackContext);
}
/**
* Set bearing
* @param args
* @param callbackContext
* @throws JSONException
*/
public void setBearing(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
float bearing = (float)args.getDouble(1);
String id = args.getString(0);
String propertyId = id.replace("groundoverlay_", "groundoverlay_initOpts_");
JSONObject opts = (JSONObject) pluginMap.objects.get(propertyId);
opts.put("bearing", bearing);
pluginMap.objects.put(propertyId, opts);
this.setFloat("setBearing", id, bearing, callbackContext);
}
/**
* set z-index
* @param args
* @param callbackContext
* @throws JSONException
*/
public void setZIndex(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
float zIndex = (float) args.getDouble(1);
String propertyId = id.replace("groundoverlay_", "groundoverlay_initOpts_");
JSONObject opts = (JSONObject) pluginMap.objects.get(propertyId);
opts.put("zIndex", zIndex);
pluginMap.objects.put(propertyId, opts);
this.setFloat("setZIndex", id, zIndex, callbackContext);
}
/**
* Set visibility for the object
* @param args
* @param callbackContext
* @throws JSONException
*/
public void setVisible(JSONArray args, CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
final boolean isVisible = args.getBoolean(1);
final GroundOverlay groundOverlay = this.getGroundOverlay(id);
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
groundOverlay.setVisible(isVisible);
}
});
String propertyId = id.replace("groundoverlay_", "groundoverlay_property_");
JSONObject properties = (JSONObject)pluginMap.objects.get(propertyId);
properties.put("isVisible", isVisible);
pluginMap.objects.put(propertyId, properties);
propertyId = id.replace("groundoverlay_", "groundoverlay_initOpts_");
JSONObject opts = (JSONObject) pluginMap.objects.get(propertyId);
opts.put("visible", isVisible);
pluginMap.objects.put(propertyId, opts);
callbackContext.success();
}
/**
* Set clickable for the object
* @param args
* @param callbackContext
* @throws JSONException
*/
public void setClickable(JSONArray args, CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
final boolean clickable = args.getBoolean(1);
String propertyId = id.replace("groundoverlay_", "groundoverlay_property_");
JSONObject properties = (JSONObject)pluginMap.objects.get(propertyId);
properties.put("isClickable", clickable);
pluginMap.objects.put(propertyId, properties);
callbackContext.success();
}
private void setImage_(final String imgUrl, final PluginAsyncInterface callback) {
if (imgUrl == null) {
callback.onPostExecute(null);
return;
}
final AsyncLoadImage.AsyncLoadImageOptions imageOptions = new AsyncLoadImage.AsyncLoadImageOptions();
imageOptions.height = -1;
imageOptions.width = -1;
imageOptions.noCaching = true;
imageOptions.url = imgUrl;
final int taskId = imageOptions.hashCode();
final AsyncLoadImageInterface onComplete = new AsyncLoadImageInterface() {
@Override
public void onPostExecute(AsyncLoadImage.AsyncLoadImageResult result) {
if (result == null || result.image == null) {
callback.onError("Can not read image from " + imgUrl);
imageLoadingTasks.remove(taskId).cancel(true);
return;
}
callback.onPostExecute(result);
imageLoadingTasks.remove(taskId).cancel(true);
}
};
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
AsyncLoadImage task = new AsyncLoadImage(cordova, webView, imageOptions, onComplete);
//cordova.getActivity().runOnUiThread(new Runnable() {
// @Override
// public void run() {
// task.execute();
// }
//});
task.execute();
imageLoadingTasks.put(taskId, task);
}
});
/*
if (imgUrl == null) {
callback.onPostExecute(null);
return;
}
if (!imgUrl.contains("://") &&
!imgUrl.startsWith("/") &&
!imgUrl.startsWith("www/") &&
!imgUrl.startsWith("data:image") &&
!imgUrl.startsWith("./") &&
!imgUrl.startsWith("../")) {
imgUrl = "./" + imgUrl;
}
if (imgUrl.startsWith("./") || imgUrl.startsWith("../")) {
imgUrl = imgUrl.replace("././", "./");
String currentPage = CURRENT_PAGE_URL;
currentPage = currentPage.replaceAll("[^\\/]*$", "");
imgUrl = currentPage + "/" + imgUrl;
}
if (imgUrl == null) {
callback.onPostExecute(null);
return;
}
final String imageUrl = imgUrl;
if (imageUrl.indexOf("http") != 0) {
//----------------------------------
// Load img from local file
//----------------------------------
AsyncTask<Void, Void, Bitmap> task = new AsyncTask<Void, Void, Bitmap>() {
@Override
protected Bitmap doInBackground(Void... params) {
String imgUrl = imageUrl;
if (imgUrl == null) {
return null;
}
Bitmap image = null;
if (imgUrl.indexOf("cdvfile://") == 0) {
CordovaResourceApi resourceApi = webView.getResourceApi();
imgUrl = PluginUtil.getAbsolutePathFromCDVFilePath(resourceApi, imgUrl);
}
if (imgUrl == null) {
return null;
}
if (imgUrl.indexOf("data:image/") == 0 && imgUrl.contains(";base64,")) {
String[] tmp = imgUrl.split(",");
image = PluginUtil.getBitmapFromBase64encodedImage(tmp[1]);
} else if (imgUrl.indexOf("file://") == 0 &&
!imgUrl.contains("file:///android_asset/")) {
imgUrl = imgUrl.replace("file://", "");
File tmp = new File(imgUrl);
if (tmp.exists()) {
image = BitmapFactory.decodeFile(imgUrl);
} else {
Log.w(TAG, "image is not found (" + imgUrl + ")");
}
} else {
//Log.d(TAG, "imgUrl = " + imgUrl);
if (imgUrl.indexOf("file:///android_asset/") == 0) {
imgUrl = imgUrl.replace("file:///android_asset/", "");
}
//Log.d(TAG, "imgUrl = " + imgUrl);
if (imgUrl.contains("./")) {
try {
boolean isAbsolutePath = imgUrl.startsWith("/");
File relativePath = new File(imgUrl);
imgUrl = relativePath.getCanonicalPath();
//Log.d(TAG, "imgUrl = " + imgUrl);
if (!isAbsolutePath) {
imgUrl = imgUrl.substring(1);
}
//Log.d(TAG, "imgUrl = " + imgUrl);
} catch (Exception e) {
e.printStackTrace();
}
}
AssetManager assetManager = PluginGroundOverlay.this.cordova.getActivity().getAssets();
InputStream inputStream;
try {
inputStream = assetManager.open(imgUrl);
image = BitmapFactory.decodeStream(inputStream);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
if (image == null) {
return null;
}
return image;
}
@Override
protected void onPostExecute(Bitmap image) {
if (image == null) {
callback.onPostExecute(null);
return;
}
GroundOverlay groundOverlay = null;
try {
BitmapDescriptor bitmapDescriptor = BitmapDescriptorFactory.fromBitmap(image);
options.image(bitmapDescriptor);
groundOverlay = self.map.addGroundOverlay(options);
callback.onPostExecute(groundOverlay);
} catch (Exception e) {
Log.e(TAG,"PluginGroundOverlay: Warning - ground overlay method is called when ground overlay method has been disposed, wait for addGroundOverlay callback before calling more methods on the groundOverlay.");
//e.printStackTrace();
try {
if (groundOverlay != null) {
groundOverlay.remove();
}
} catch (Exception ignore) {
ignore = null;
}
callback.onError(e.getMessage() + "");
}
}
};
task.execute();
imageLoadingTasks.add(task);
return;
}
if (imageUrl.indexOf("http") == 0) {
//----------------------------------
// Load img from on the internet
//----------------------------------
int width = -1;
int height = -1;
AsyncLoadImage task = new AsyncLoadImage(userAgent, width, height, true, new AsyncLoadImageInterface() {
@Override
public void onPostExecute(Bitmap image) {
if (image == null) {
callback.onPostExecute(null);
return;
}
GroundOverlay groundOverlay = null;
try {
BitmapDescriptor bitmapDescriptor = BitmapDescriptorFactory.fromBitmap(image);
options.image(bitmapDescriptor);
groundOverlay = self.map.addGroundOverlay(options);
image.recycle();
callback.onPostExecute(groundOverlay);
} catch (Exception e) {
//e.printStackTrace();
try {
if (groundOverlay != null) {
groundOverlay.remove();
}
} catch (Exception ignore) {
ignore = null;
}
callback.onError(e.getMessage() + "");
}
}
});
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, imageUrl);
imageLoadingTasks.add(task);
}
*/
}
@Override
public void onDestroy() {
super.onDestroy();
//--------------------------------------
// Cancel tasks
//--------------------------------------
cordova.getThreadPool().submit(new Runnable() {
@Override
public void run() {
AsyncTask task;
int i, ilen = imageLoadingTasks.size();
for (i = 0; i < ilen; i++) {
task = imageLoadingTasks.remove(i);
task.cancel(true);
task = null;
}
imageLoadingTasks = null;
}
});
this.clear();
}
}
@@ -0,0 +1,434 @@
package plugin.google.maps;
import android.os.Bundle;
import org.apache.cordova.CallbackContext;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import za.co.twyst.tbxml.TBXML;
public class PluginKmlOverlay extends MyPlugin implements MyPluginInterface {
private HashMap<String, Bundle> styles = new HashMap<String, Bundle>();
private enum KML_TAG {
NOT_SUPPORTED,
kml,
style,
styleurl,
stylemap,
schema,
coordinates
}
/**
* Create kml overlay
*
* @param args
* @param callbackContext
* @throws JSONException
*/
public void create(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
final JSONObject opts = args.getJSONObject(1);
self = this;
if (!opts.has("url")) {
callbackContext.error("No kml file is specified");
return;
}
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
String urlStr = null;
try {
urlStr = opts.getString("url");
} catch (JSONException e) {
e.printStackTrace();
}
if (urlStr == null || urlStr.length() == 0) {
callbackContext.error("No kml file is specified");
return;
}
String currentPageUrl = webView.getUrl();
if (!urlStr.contains("://") &&
!urlStr.startsWith("/") &&
!urlStr.startsWith("www/") &&
!urlStr.startsWith("data:image") &&
!urlStr.startsWith("./") &&
!urlStr.startsWith("../")) {
urlStr = "./" + urlStr;
}
if (currentPageUrl.startsWith("http://localhost") ||
currentPageUrl.startsWith("http://127.0.0.1")) {
if (urlStr.contains("://")) {
urlStr = urlStr.replaceAll("http://.+?/", "file:///android_asset/www/");
} else {
// Avoid WebViewLocalServer (because can not make a connection for some reason)
urlStr = "file:///android_asset/www/".concat(urlStr);
}
}
if (urlStr.startsWith("./") || urlStr.startsWith("../")) {
urlStr = urlStr.replace("././", "./");
currentPageUrl = currentPageUrl.replaceAll("[^\\/]*$", "");
urlStr = currentPageUrl + "/" + urlStr;
}
if (urlStr.startsWith("cdvfile://")) {
urlStr = PluginUtil.getAbsolutePathFromCDVFilePath(webView.getResourceApi(), urlStr);
}
// Avoid WebViewLocalServer (because can not make a connection for some reason)
if (urlStr.contains("http://localhost") || urlStr.contains("http://127.0.0.1")) {
urlStr = urlStr.replaceAll("^http://[^\\/]+\\//", "file:///android_asset/www/");
}
final String finalUrl = urlStr;
executorService.submit(new Runnable() {
@Override
public void run() {
Bundle result = loadKml(finalUrl);
callbackContext.success(PluginUtil.Bundle2Json(result));
}
});
}
});
}
private Bundle loadKml(String urlStr) {
InputStream inputStream = getKmlContents(urlStr);
if (inputStream == null) {
return null;
}
try {
// KMZ is not ready yet.... sorry
// if (urlStr.contains(".kmz")) {
// String cacheDirPath = cordova.getActivity().getCacheDir() + "/" + Integer.toString(urlStr.hashCode(), 16);
// File cacheDir = new File(cacheDirPath);
// if (!cacheDir.exists()) {
// cacheDir.mkdirs();
// }
// ArrayList<File> files = (PluginUtil.unpackZipFromBytes(inputStream, cacheDirPath));
// inputStream.close();
// for (File file : files) {
// if (file.getName().contains(".kml")) {
// inputStream = new FileInputStream(file);
// break;
// }
// }
// }
String line;
StringBuilder stringBuilder = new StringBuilder();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line);
stringBuilder.append("\n");
}
bufferedReader.close();
TBXML tbxml = new TBXML();
tbxml.parse(stringBuilder.toString());
KmlParserClass parser = new KmlParserClass();
Bundle root = parser.parseXml(tbxml, tbxml.rootXMLElement());
Bundle result = new Bundle();
result.putBundle("schemas", parser.schemaHolder);
result.putBundle("styles", parser.styleHolder);
result.putBundle("root", root);
tbxml.release();
inputStreamReader.close();
inputStream.close();
inputStream = null;
return result;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
class KmlParserClass {
public Bundle styleHolder = new Bundle();
public Bundle schemaHolder = new Bundle();
private Bundle parseXml(TBXML tbxml, long rootElement) {
Bundle result = new Bundle();
String styleId, schemaId, txt, attrName;
int i;
String tagName = tbxml.elementName(rootElement).toLowerCase();
long childNode;
Bundle styles, schema, extendedData;
ArrayList<Bundle> children;
ArrayList<String> styleIDs;
//Log.d(TAG, "--->tagName = " + tagName + "(" + rootElement + ")");
result.putString("tagName", tagName);
KML_TAG kmlTag = null;
try {
kmlTag = KML_TAG.valueOf(tagName);
} catch(Exception e) {
kmlTag = KML_TAG.NOT_SUPPORTED;
}
long[] attributes = tbxml.listAttributesOfElement(rootElement);
for (i = 0; i < attributes.length; i++) {
attrName = tbxml.attributeName(attributes[i]);
result.putString(attrName, tbxml.attributeValue(attributes[i]));
}
switch (kmlTag) {
case styleurl:
styleId = tbxml.textForElement(rootElement);
result.putString("styleId", styleId);
break;
case stylemap:
case style:
// Generate a style id for the tag
styleId = tbxml.valueOfAttributeNamed("id", rootElement);
if (styleId == null || styleId.isEmpty()) {
styleId = "__" + rootElement + "__";
}
result.putString("styleId", styleId);
// Store style information into the styleHolder.
styles = new Bundle();
children = new ArrayList<Bundle>();
childNode = tbxml.firstChild(rootElement);
while (childNode > 0) {
Bundle node = this.parseXml(tbxml, childNode);
if (node != null) {
if (node.containsKey("value")) {
styles.putString(node.getString("tagName"), node.getString("value"));
} else {
children.add(node);
}
}
childNode = tbxml.nextSibling(childNode);
}
if (children.size() > 0) {
styles.putParcelableArrayList("children", children);
}
styleHolder.putBundle(styleId, styles);
break;
case schema:
// Generate a schema id for the tag
schemaId = tbxml.valueOfAttributeNamed("id", rootElement);
if (schemaId == null || schemaId.isEmpty()) {
schemaId = "__" + rootElement + "__";
}
// Store schema information into the schemaHolder.
schema = new Bundle();
schema.putString("name", tbxml.valueOfAttributeNamed("name", rootElement));
children = new ArrayList<Bundle>();
childNode = tbxml.firstChild(rootElement);
while (childNode > 0) {
Bundle node = this.parseXml(tbxml, childNode);
if (node != null) {
children.add(node);
}
childNode = tbxml.nextSibling(childNode);
}
if (children.size() > 0) {
schema.putParcelableArrayList("children", children);
}
schemaHolder.putBundle(schemaId, schema);
break;
case coordinates:
ArrayList<Bundle> latLngList = new ArrayList<Bundle>();
txt = tbxml.textForElement(rootElement);
txt = txt.replaceAll("\\s+", "\n");
txt = txt.replaceAll("\\n+", "\n");
String lines[] = txt.split("\n");
String tmpArry[];
Bundle latLng;
for (i = 0; i < lines.length; i++) {
lines[i] = lines[i].replaceAll("[^0-9,.\\-Ee]", "");
if (!"".equals(lines[i])) {
tmpArry = lines[i].split(",");
latLng = new Bundle();
latLng.putDouble("lat", Double.parseDouble(tmpArry[1]));
latLng.putDouble("lng", Double.parseDouble(tmpArry[0]));
latLngList.add(latLng);
}
}
result.putParcelableArrayList(tagName, latLngList);
break;
default:
childNode = tbxml.firstChild(rootElement);
if (childNode != 0) {
children = new ArrayList<Bundle>();
while (childNode != 0) {
Bundle node = this.parseXml(tbxml, childNode);
if (node != null) {
if (node.containsKey("styleId")) {
//--------------------------------------------
// writtin in JavaScript
// result.styleIDs = result.styleIDs || [];
// result.styleIDs.push(node.styleId);
//--------------------------------------------
styleIDs = result.getStringArrayList("styleIDs");
if (styleIDs == null) {
styleIDs = new ArrayList<String>();
}
styleIDs.add(node.getString("styleId"));
result.putStringArrayList("styleIDs", styleIDs);
} else if (!("schema".equals(node.getString("tagName")))) {
children.add(node);
}
}
childNode = tbxml.nextSibling(childNode);
}
result.putParcelableArrayList("children", children);
} else {
result.putString("value", tbxml.textForElement(rootElement));
}
break;
}
return result;
}
}
private InputStream getKmlContents(String urlStr) {
InputStream inputStream;
try {
//Log.d("PluginKmlOverlay", "---> url = " + urlStr);
if (urlStr.startsWith("http://") || urlStr.startsWith("https://")) {
URL url = new URL(urlStr);
boolean redirect = true;
HttpURLConnection http = null;
String cookies = null;
int redirectCnt = 0;
while(redirect && redirectCnt < 10) {
redirect = false;
http = (HttpURLConnection)url.openConnection();
http.setRequestMethod("GET");
if (cookies != null) {
http.setRequestProperty("Cookie", cookies);
}
http.addRequestProperty("Accept-Language", "en-US,en;q=0.8");
http.addRequestProperty("User-Agent", "Mozilla");
http.setInstanceFollowRedirects(true);
HttpURLConnection.setFollowRedirects(true);
// normally, 3xx is redirect
int status = http.getResponseCode();
if (status != HttpURLConnection.HTTP_OK) {
if (status == HttpURLConnection.HTTP_MOVED_TEMP
|| status == HttpURLConnection.HTTP_MOVED_PERM
|| status == HttpURLConnection.HTTP_SEE_OTHER)
redirect = true;
}
if (redirect) {
// get redirect url from "location" header field
url = new URL(http.getHeaderField("Location"));
// get the cookie if need, for login
cookies = http.getHeaderField("Set-Cookie");
// Disconnect the current connection
http.disconnect();
redirectCnt++;
}
}
inputStream = http.getInputStream();
} else if (urlStr.indexOf("file://") == 0 && !urlStr.contains("file:///android_asset/") ||
urlStr.indexOf("/") == 0) {
urlStr = urlStr.replace("file://", "");
try {
boolean isAbsolutePath = urlStr.startsWith("/");
File relativePath = new File(urlStr);
urlStr = relativePath.getCanonicalPath();
//Log.d(TAG, "imgUrl = " + imgUrl);
if (!isAbsolutePath) {
urlStr = urlStr.substring(1);
}
//Log.d(TAG, "imgUrl = " + imgUrl);
} catch (Exception e) {
e.printStackTrace();
}
//Log.d("PluginKmlOverlay", "---> url = " + urlStr);
inputStream = new FileInputStream(urlStr);
} else {
if (urlStr.indexOf("file:///android_asset/") == 0) {
urlStr = urlStr.replace("file:///android_asset/", "");
}
try {
boolean isAbsolutePath = urlStr.startsWith("/");
File relativePath = new File(urlStr);
urlStr = relativePath.getCanonicalPath();
//Log.d(TAG, "imgUrl = " + imgUrl);
if (!isAbsolutePath) {
urlStr = urlStr.substring(1);
}
//Log.d(TAG, "imgUrl = " + imgUrl);
} catch (Exception e) {
e.printStackTrace();
}
//Log.d("PluginKmlOverlay", "---> url = " + urlStr);
inputStream = cordova.getActivity().getResources().getAssets().open(urlStr);
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return inputStream;
}
}
@@ -0,0 +1,665 @@
package plugin.google.maps;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.v4.content.PermissionChecker;
import android.util.Log;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
public class PluginLocationService extends CordovaPlugin {
private Activity activity;
private final String TAG = "PluginLocationService";
private HashMap<String, Bundle> bufferForLocationDialog = new HashMap<String, Bundle>();
private final int ACTIVITY_LOCATION_DIALOG = 0x7f999900; // Invite the location dialog using Google Play Services
private final int ACTIVITY_LOCATION_PAGE = 0x7f999901; // Open the location settings page
private GoogleApiClient googleApiClient = null;
public void initialize(final CordovaInterface cordova, final CordovaWebView webView) {
super.initialize(cordova, webView);
activity = cordova.getActivity();
}
private static Location lastLocation = null;
private ArrayList<CallbackContext> regularAccuracyRequestList = new ArrayList<CallbackContext>();
private ArrayList<CallbackContext> highAccuracyRequestList = new ArrayList<CallbackContext>();
public static final Object semaphore = new Object();
public static void setLastLocation(Location location) {
// Sets the last location if the end user click on the mylocation (blue dot)
lastLocation = location;
}
@Override
public boolean execute(final String action, final JSONArray args, final CallbackContext callbackContext) throws JSONException {
cordova.getThreadPool().submit(new Runnable() {
@Override
public void run() {
try {
if ("getMyLocation".equals(action)) {
PluginLocationService.this.getMyLocation(args, callbackContext);
} else if ("hasPermission".equals(action)) {
PluginLocationService.this.hasPermission(args, callbackContext);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
});
return true;
}
@SuppressWarnings("unused")
public void hasPermission(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
synchronized (semaphore) {
// Check geolocation permission.
boolean locationPermission = PermissionChecker.checkSelfPermission(cordova.getActivity().getApplicationContext(), Manifest.permission.ACCESS_COARSE_LOCATION) == PermissionChecker.PERMISSION_GRANTED;
callbackContext.success(locationPermission ? 1 : 0);
}
}
@SuppressWarnings("unused")
public void getMyLocation(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
synchronized (semaphore) {
LocationManager locationManager = (LocationManager) this.activity.getSystemService(Context.LOCATION_SERVICE);
List<String> providers = locationManager.getAllProviders();
int availableProviders = 0;
//if (mPluginLayout != null && mPluginLayout.isDebug) {
Log.d(TAG, "---debug at getMyLocation(available providers)--");
//}
Iterator<String> iterator = providers.iterator();
String provider;
boolean isAvailable;
while(iterator.hasNext()) {
provider = iterator.next();
if ("passive".equals(provider)) {
continue;
}
isAvailable = locationManager.isProviderEnabled(provider);
if (isAvailable) {
availableProviders++;
}
//if (mPluginLayout != null && mPluginLayout.isDebug) {
Log.d(TAG, " " + provider + " = " + (isAvailable ? "" : "not ") + "available");
//}
}
if (availableProviders == 0) {
JSONObject result = new JSONObject();
try {
result.put("status", false);
result.put("error_code", "not_available");
result.put("error_message", PluginUtil.getPgmStrings(activity,"pgm_no_location_providers"));
} catch (JSONException e) {
e.printStackTrace();
}
callbackContext.error(result);
return;
}
JSONObject params = args.getJSONObject(0);
boolean requestHighAccuracy = false;
if (params.has("enableHighAccuracy")) {
requestHighAccuracy = params.getBoolean("enableHighAccuracy");
}
if (requestHighAccuracy && !locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
JSONObject result = new JSONObject();
try {
result.put("status", false);
result.put("error_code", "not_available");
result.put("error_message", PluginUtil.getPgmStrings(activity,"pgm_no_location_service_is_disabled"));
} catch (JSONException e) {
e.printStackTrace();
}
callbackContext.error(result);
return;
}
// enableHighAccuracy = true -> PRIORITY_HIGH_ACCURACY
// enableHighAccuracy = false -> PRIORITY_BALANCED_POWER_ACCURACY
if (requestHighAccuracy) {
highAccuracyRequestList.add(callbackContext);
} else {
regularAccuracyRequestList.add(callbackContext);
}
if (googleApiClient != null && googleApiClient.isConnecting()) {
return;
}
}
// Request geolocation permission.
boolean locationPermission = PermissionChecker.checkSelfPermission(cordova.getActivity().getApplicationContext(), Manifest.permission.ACCESS_COARSE_LOCATION) == PermissionChecker.PERMISSION_GRANTED;
if (!locationPermission) {
//_saveArgs = args;
//_saveCallbackContext = callbackContext;
synchronized (semaphore) {
cordova.requestPermissions(this, callbackContext.hashCode(), new String[]{
Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION
});
try {
semaphore.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
locationPermission = PermissionChecker.checkSelfPermission(cordova.getActivity().getApplicationContext(), Manifest.permission.ACCESS_COARSE_LOCATION) == PermissionChecker.PERMISSION_GRANTED;
if (!locationPermission) {
PluginResult errorResult = new PluginResult(PluginResult.Status.ERROR, PluginUtil.getPgmStrings(activity,"pgm_location_rejected_by_user"));
synchronized (semaphore) {
for (CallbackContext callback: regularAccuracyRequestList) {
callback.sendPluginResult(errorResult);
}
for (CallbackContext callback: highAccuracyRequestList) {
callback.sendPluginResult(errorResult);
}
regularAccuracyRequestList.clear();
highAccuracyRequestList.clear();
}
return;
}
}
if (lastLocation != null && Calendar.getInstance().getTimeInMillis() - lastLocation.getTime() <= 2000) {
//---------------------------------------------------------------------
// If the user requests the location in two seconds from the last time,
// return the last result in order to save battery usage.
// (Don't request the device location too much! Save battery usage!)
//---------------------------------------------------------------------
JSONObject result;
try {
result = PluginUtil.location2Json(lastLocation);
result.put("status", true);
PluginResult successResult = new PluginResult(PluginResult.Status.OK, result);
synchronized (semaphore) {
for (CallbackContext callback: regularAccuracyRequestList) {
callback.sendPluginResult(successResult);
}
for (CallbackContext callback: highAccuracyRequestList) {
callback.sendPluginResult(successResult);
}
regularAccuracyRequestList.clear();
highAccuracyRequestList.clear();
}
} catch (JSONException e) {
e.printStackTrace();
}
return;
}
if (googleApiClient == null) {
googleApiClient = new GoogleApiClient.Builder(activity)
.addApi(LocationServices.API)
.addConnectionCallbacks(new com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks() {
@Override
public void onConnected(Bundle connectionHint) {
Log.d(TAG, "===> onConnected " + callbackContext.getCallbackId());
requestLocation();
}
@Override
public void onConnectionSuspended(int cause) {
Log.e(TAG, "===> onConnectionSuspended");
}
})
.addOnConnectionFailedListener(new com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener() {
@Override
public void onConnectionFailed(@NonNull ConnectionResult result) {
Log.e(TAG, "===> onConnectionFailed");
PluginResult errorResult = new PluginResult(PluginResult.Status.ERROR, result.toString());
synchronized (semaphore) {
for (CallbackContext callback: regularAccuracyRequestList) {
callback.sendPluginResult(errorResult);
}
for (CallbackContext callback: highAccuracyRequestList) {
callback.sendPluginResult(errorResult);
}
regularAccuracyRequestList.clear();
highAccuracyRequestList.clear();
}
googleApiClient.disconnect();
}
})
.build();
googleApiClient.connect();
} else if (googleApiClient.isConnected()) {
requestLocation();
} else {
Log.e(TAG, "===> googleApiClient.isConnected() is not connected");
googleApiClient.connect();
}
}
private void requestLocation() {
Log.d(TAG, "--->regularAccuracyRequestList.size = " + regularAccuracyRequestList.size());
if (regularAccuracyRequestList.size() > 0) {
PluginLocationService.this._requestLocationUpdate(false, false, new CallbackContext("regular-callback", webView) {
@Override
public void sendPluginResult(PluginResult pluginResult) {
synchronized (semaphore) {
for (CallbackContext callback: regularAccuracyRequestList) {
callback.sendPluginResult(pluginResult);
}
regularAccuracyRequestList.clear();
if (regularAccuracyRequestList.size() == 0 && highAccuracyRequestList.size() == 0) {
googleApiClient.disconnect();
}
}
}
});
}
Log.d(TAG, "--->highAccuracyRequestList.size = " + highAccuracyRequestList.size());
if (highAccuracyRequestList.size() > 0) {
PluginLocationService.this._requestLocationUpdate(false, true, new CallbackContext("regular-callback", webView) {
@Override
public void sendPluginResult(PluginResult pluginResult) {
synchronized (semaphore) {
for (CallbackContext callback: highAccuracyRequestList) {
callback.sendPluginResult(pluginResult);
}
highAccuracyRequestList.clear();
if (regularAccuracyRequestList.size() == 0 && highAccuracyRequestList.size() == 0) {
googleApiClient.disconnect();
}
}
}
});
}
}
// private void _checkLocationSettings() {
//
// LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder().setAlwaysShow(true);
//
// LocationRequest locationRequest;
// locationRequest = LocationRequest.create()
// .setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
// builder.addLocationRequest(locationRequest);
//
// if (enableHighAccuracy) {
// locationRequest = LocationRequest.create()
// .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
// builder.addLocationRequest(locationRequest);
// }
//
// PendingResult<LocationSettingsResult> locationSettingsResult =
// LocationServices.SettingsApi.checkLocationSettings(googleApiClient, builder.build());
//
// locationSettingsResult.setResultCallback(new ResultCallback<LocationSettingsResult>() {
//
// @Override
// public void onResult(@NonNull LocationSettingsResult result) {
// final Status status = result.getStatus();
// switch (status.getStatusCode()) {
// case LocationSettingsStatusCodes.SUCCESS:
// _requestLocationUpdate(false, enableHighAccuracy, callbackContext);
// break;
//
// case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
// // Location settings are not satisfied. But could be fixed by showing the user
// // a dialog.
// try {
// //Keep the callback id
// Bundle bundle = new Bundle();
// bundle.putInt("type", ACTIVITY_LOCATION_DIALOG);
// bundle.putString("callbackId", callbackContext.getCallbackId());
// bundle.putBoolean("enableHighAccuracy", enableHighAccuracy);
// int hashCode = bundle.hashCode();
//
// bufferForLocationDialog.put("bundle_" + hashCode, bundle);
// //PluginLocationService.this.sendNoResult(callbackContext);
//
// // Show the dialog by calling startResolutionForResult(),
// // and check the result in onActivityResult().
// cordova.setActivityResultCallback(PluginLocationService.this);
// status.startResolutionForResult(cordova.getActivity(), hashCode);
// } catch (IntentSender.SendIntentException e) {
// // Show the dialog that is original version of this plugin.
// _showLocationSettingsPage(enableHighAccuracy, callbackContext);
// }
// break;
//
// case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
// // Location settings are not satisfied. However, we have no way to fix the
// // settings so we won't show the dialog.
//
// JSONObject jsResult = new JSONObject();
// try {
// jsResult.put("status", false);
// jsResult.put("error_code", "service_not_available");
// jsResult.put("error_message", "This app has been rejected to use Location Services.");
// } catch (JSONException e) {
// e.printStackTrace();
// }
// callbackContext.error(jsResult);
// break;
// }
// }
//
// });
// }
//
// private void _showLocationSettingsPage(final boolean enableHighAccuracy, final CallbackContext callbackContext) {
// //Ask the user to turn on the location services.
// AlertDialog.Builder builder = new AlertDialog.Builder(this.activity);
// builder.setTitle("Improve location accuracy");
// builder.setMessage("To enhance your Maps experience:\n\n" +
// " - Enable Google apps location access\n\n" +
// " - Turn on GPS and mobile network location");
// builder.setPositiveButton("Settings", new DialogInterface.OnClickListener() {
// @Override
// public void onClick(DialogInterface dialog, int which) {
// //Keep the callback id
// Bundle bundle = new Bundle();
// bundle.putInt("type", ACTIVITY_LOCATION_PAGE);
// bundle.putString("callbackId", callbackContext.getCallbackId());
// bundle.putBoolean("enableHighAccuracy", enableHighAccuracy);
// int hashCode = bundle.hashCode();
//
// bufferForLocationDialog.put("bundle_" + hashCode, bundle);
// //PluginLocationService.this.sendNoResult(callbackContext);
//
// //Launch settings, allowing user to make a change
// cordova.setActivityResultCallback(PluginLocationService.this);
// Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
// activity.startActivityForResult(intent, hashCode);
// }
// });
// builder.setNegativeButton("Skip", new DialogInterface.OnClickListener() {
// @Override
// public void onClick(DialogInterface dialog, int which) {
// //No location service, no Activity
// dialog.dismiss();
//
// JSONObject result = new JSONObject();
// try {
// result.put("status", false);
// result.put("error_code", "service_denied");
// result.put("error_message", "This app has been rejected to use Location Services.");
// } catch (JSONException e) {
// e.printStackTrace();
// }
// callbackContext.error(result);
// }
// });
// builder.create().show();
// }
@SuppressWarnings("MissingPermission")
private void _requestLocationUpdate(final boolean isRetry, final boolean enableHighAccuracy, final CallbackContext callbackContext) {
Log.d(TAG, "---->_requestLocationUpdate (isRetry = " + isRetry + ")");
int priority = LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY;
if (enableHighAccuracy || "Genymotion".equals(Build.MANUFACTURER)) {
priority = LocationRequest.PRIORITY_HIGH_ACCURACY;
}
if (!isRetry) {
LocationServices.getFusedLocationProviderClient(cordova.getActivity())
.getLastLocation()
.addOnSuccessListener(new OnSuccessListener<Location>() {
@Override
public void onSuccess(Location location) {
lastLocation = location;
if (lastLocation != null && Calendar.getInstance().getTimeInMillis() - lastLocation.getTime() <= 2000) {
Log.d(TAG, "---->The last location is obtained in 2 sec.");
//---------------------------------------------------------------------
// If the user requests the location in two seconds from the last time,
// return the last result in order to save battery usage.
// (Don't request the device location too much! Save battery usage!)
//---------------------------------------------------------------------
JSONObject result;
try {
result = PluginUtil.location2Json(lastLocation);
result.put("status", true);
PluginResult successResult = new PluginResult(PluginResult.Status.OK, result);
synchronized (semaphore) {
for (CallbackContext callback : regularAccuracyRequestList) {
callback.sendPluginResult(successResult);
}
for (CallbackContext callback: highAccuracyRequestList) {
callback.sendPluginResult(successResult);
}
regularAccuracyRequestList.clear();
highAccuracyRequestList.clear();
}
} catch (JSONException e) {
e.printStackTrace();
}
googleApiClient.disconnect();
} else {
Log.d(TAG, "---->The last location is expired. Let's get the latest location...");
_requestLocationUpdate(true, enableHighAccuracy, callbackContext);
}
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
_requestLocationUpdate(true, enableHighAccuracy, callbackContext);
}
});
return;
}
// It seems setting the values to both setFastestInterval() and setInterval() is important.
// https://akira-watson.com/android/fusedlocationproviderapi.html
LocationRequest locationRequest= LocationRequest.create()
//.setNumUpdates(2)
.setFastestInterval(5000)
.setInterval(10000)
//.setSmallestDisplacement(0)
.setPriority(priority)
.setExpirationDuration(12000)
.setMaxWaitTime(5000);
LocationServices.getFusedLocationProviderClient(cordova.getActivity()).requestLocationUpdates(locationRequest, new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
Log.d(TAG, "---->onLocationResult");
Location location = null;
if (locationResult.getLocations().size() > 0) {
location = locationResult.getLocations().get(0);
lastLocation = location;
} else if (locationResult.getLastLocation() != null) {
location = locationResult.getLastLocation();
}
Log.d(TAG, "===> location =" + location);
if (location != null) {
JSONObject result;
Log.d(TAG, "===> location =" + callbackContext.getCallbackId());
try {
result = PluginUtil.location2Json(location);
result.put("status", true);
callbackContext.success(result);
} catch (JSONException e) {
e.printStackTrace();
}
} else {
// Send back the error result
JSONObject result = new JSONObject();
try {
result.put("status", false);
result.put("error_code", "cannot_detect");
result.put("error_message", PluginUtil.getPgmStrings(activity,"pgm_can_not_get_location"));
} catch (JSONException e) {
e.printStackTrace();
}
callbackContext.error(result);
}
googleApiClient.disconnect();
}
}, Looper.myLooper());
}
private void _onActivityResultLocationPage(Bundle bundle) {
String callbackId = bundle.getString("callbackId");
CallbackContext callbackContext = new CallbackContext(callbackId, this.webView);
LocationManager locationManager = (LocationManager) this.activity.getSystemService(Context.LOCATION_SERVICE);
List<String> providers = locationManager.getAllProviders();
int availableProviders = 0;
//if (mPluginLayout != null && mPluginLayout.isDebug) {
Log.d(TAG, "---debug at getMyLocation(available providers)--");
//}
Iterator<String> iterator = providers.iterator();
String provider;
boolean isAvailable;
while(iterator.hasNext()) {
provider = iterator.next();
if ("passive".equals(provider)) {
continue;
}
isAvailable = locationManager.isProviderEnabled(provider);
if (isAvailable) {
availableProviders++;
}
//if (mPluginLayout != null && mPluginLayout.isDebug) {
Log.d(TAG, " " + provider + " = " + (isAvailable ? "" : "not ") + "available");
//}
}
if (availableProviders == 0) {
JSONObject result = new JSONObject();
try {
result.put("status", false);
result.put("error_code", "not_available");
result.put("error_message", PluginUtil.getPgmStrings(activity,"pgm_no_location_providers"));
} catch (JSONException e) {
e.printStackTrace();
}
callbackContext.error(result);
return;
}
_inviteLocationUpdateAfterActivityResult(bundle);
}
private void _inviteLocationUpdateAfterActivityResult(Bundle bundle) {
boolean enableHighAccuracy = bundle.getBoolean("enableHighAccuracy");
String callbackId = bundle.getString("callbackId");
CallbackContext callbackContext = new CallbackContext(callbackId, this.webView);
this._requestLocationUpdate(false, enableHighAccuracy, callbackContext);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (!bufferForLocationDialog.containsKey("bundle_" + requestCode)) {
Log.e(TAG, "no key");
return;
}
Bundle query = bufferForLocationDialog.get("bundle_" + requestCode);
Log.d(TAG, "====> onActivityResult (" + resultCode + ")");
switch (query.getInt("type")) {
case ACTIVITY_LOCATION_DIALOG:
// User was asked to enable the location setting.
switch (resultCode) {
case Activity.RESULT_OK:
// All required changes were successfully made
_inviteLocationUpdateAfterActivityResult(query);
break;
case Activity.RESULT_CANCELED:
// The user was asked to change settings, but chose not to
_userRefusedToUseLocationAfterActivityResult(query);
break;
default:
break;
}
break;
case ACTIVITY_LOCATION_PAGE:
_onActivityResultLocationPage(query);
break;
}
}
private void _userRefusedToUseLocationAfterActivityResult(Bundle bundle) {
String callbackId = bundle.getString("callbackId");
CallbackContext callbackContext = new CallbackContext(callbackId, this.webView);
JSONObject result = new JSONObject();
try {
result.put("status", false);
result.put("error_code", "service_denied");
result.put("error_message", PluginUtil.getPgmStrings(activity,"pgm_location_rejected_by_user"));
} catch (JSONException e) {
e.printStackTrace();
}
callbackContext.error(result);
}
public void onRequestPermissionResult(int requestCode, String[] permissions,
int[] grantResults) throws JSONException {
synchronized (semaphore) {
semaphore.notify();
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,779 @@
package plugin.google.maps;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.util.Log;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaWebView;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class PluginMarkerCluster extends PluginMarker {
private final static Map<String, STATUS> pluginMarkers = new ConcurrentHashMap<String, STATUS>();
private final static Map<String, Integer> waitCntManager = new ConcurrentHashMap<String, Integer>();
private final static Map<String, Boolean> debugFlags = new ConcurrentHashMap<String, Boolean>();
private final static ArrayList<String> deleteMarkers = new ArrayList<String>();
private final Object semaphore = new Object();
private boolean stopFlag = false;
private final Object deleteThreadLock = new Object();
enum STATUS {
WORKING,
CREATED,
DELETED
}
@Override
public void initialize(CordovaInterface cordova, final CordovaWebView webView) {
super.initialize(cordova, webView);
if (!deleteThread.isAlive()) {
deleteThread.start();
}
}
@Override
public void onDestroy() {
super.onDestroy();
stopFlag = true;
}
@Override
protected void clear() {
super.clear();
synchronized (semaphore) {
synchronized (pluginMarkers) {
synchronized (deleteMarkers) {
String clusterId_markerId;
if (pluginMarkers.size() > 0) {
String[] keys = pluginMarkers.keySet().toArray(new String[pluginMarkers.size()]);
for (int i = 0; i < keys.length; i++) {
clusterId_markerId = keys[i];
pluginMarkers.put(clusterId_markerId, STATUS.DELETED);
deleteMarkers.add(clusterId_markerId);
}
}
}
}
}
}
//---------------------
// Delete thread
//---------------------
private Thread deleteThread = new Thread(new Runnable() {
@Override
public void run() {
while(!stopFlag) {
synchronized (deleteThreadLock) {
try {
deleteThreadLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (deleteMarkers.size() == 0) {
continue;
}
cordova.getActivity().runOnUiThread(removeOverlaysOnUiThread);
}
}
});
private Runnable removeOverlaysOnUiThread = new Runnable() {
@Override
public void run() {
synchronized (deleteMarkers) {
String markerId;
Marker marker;
STATUS status;
String cacheKey;
if (deleteMarkers.size() > 0) {
String[] targetIDs = deleteMarkers.toArray(new String[deleteMarkers.size()]);
for (int i = targetIDs.length - 1; i > -1; i--) {
markerId = targetIDs[i];
marker = self.getMarker(markerId);
synchronized (pluginMarkers) {
status = pluginMarkers.get(markerId);
if (!STATUS.WORKING.equals(status)) {
synchronized (pluginMap.objects) {
_removeMarker(marker);
marker = null;
cacheKey = (String) pluginMap.objects.remove("marker_icon_" + markerId);
if (cacheKey != null && iconCacheKeys.containsKey(cacheKey)) {
int count = iconCacheKeys.get(cacheKey);
if (count < 1) {
iconCacheKeys.remove(cacheKey);
AsyncLoadImage.removeBitmapFromMemCahce(cacheKey);
} else {
iconCacheKeys.put(cacheKey, count - 1);
}
}
pluginMap.objects.remove(markerId);
pluginMap.objects.remove("marker_property_" + markerId);
pluginMap.objects.remove("marker_imageSize_" + markerId);
}
pluginMarkers.remove(markerId);
deleteMarkers.remove(i);
} else {
pluginMarkers.put(markerId, STATUS.DELETED);
}
}
}
}
}
System.gc();
}
};
public void remove(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String clusterId = args.getString(0);
//Log.d(TAG, "-->remove = " + clusterId);
synchronized (debugFlags) {
debugFlags.remove(clusterId);
waitCntManager.remove(clusterId);
}
synchronized (pluginMarkers) {
for(String key: pluginMarkers.keySet()) {
if (key.startsWith(clusterId)) {
//Log.d(TAG, "-->delete = " + key);
pluginMarkers.put(key, STATUS.CREATED);
deleteMarkers.add(key);
}
}
}
/*
String cacheKey;
Set<String> keySet = pluginMap.objects.keySet();
Marker marker;
String[] objectIdArray = keySet.toArray(new String[keySet.size()]);
for (String objectId : objectIdArray) {
if (objectId.contains(clusterId)) {
if (objectId.startsWith("marker_icon")) {
cacheKey = (String) pluginMap.objects.remove(objectId);
if (iconCacheKeys.containsKey(cacheKey)) {
int count = iconCacheKeys.get(cacheKey);
if (count < 1) {
iconCacheKeys.remove(cacheKey);
AsyncLoadImage.removeBitmapFromMemCahce(cacheKey);
} else {
iconCacheKeys.put(cacheKey, count - 1);
}
}
} else if (objectId.startsWith("marker_property_") ||
objectId.startsWith("marker_imageSize_")) {
pluginMap.objects.remove(objectId);
//} else if (objectId.startsWith("marker_")) {
// marker = (Marker) pluginMap.objects.remove(objectId);
// marker.remove();
} else {
pluginMap.objects.remove(objectId);
}
}
}
*/
synchronized (deleteThreadLock) {
deleteThreadLock.notify();
}
callbackContext.success();
}
/**
* Create a marker
*
* @param args
* @param callbackContext
* @throws JSONException
*/
@SuppressWarnings("unused")
public void create(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
JSONObject params = args.getJSONObject(1);
String hashCode = args.getString(2);
JSONArray positionList = params.getJSONArray("positionList");
JSONArray geocellList = new JSONArray();
JSONObject position;
for (int i = 0; i < positionList.length(); i++) {
position = positionList.getJSONObject(i);
geocellList.put(getGeocell(position.getDouble("lat"), position.getDouble("lng"), 12));
}
String id = "markercluster_" + hashCode;
debugFlags.put(id, params.getBoolean("debug"));
final JSONObject result = new JSONObject();
try {
result.put("geocellList", geocellList);
result.put("hashCode", hashCode);
result.put("__pgmId", id);
} catch (JSONException e) {
e.printStackTrace();
}
callbackContext.success(result);
}
public void redrawClusters(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
final HashSet<String> updateClusterIDs = new HashSet<String>();
final HashMap<String, Bundle> changeProperties = new HashMap<String, Bundle>();
final String clusterId = args.getString(0);
final boolean isDebug = debugFlags.get(clusterId);
final JSONObject params = args.getJSONObject(1);
String markerId, clusterId_markerId;
JSONArray new_or_update = null;
if (params.has("new_or_update")) {
new_or_update = params.getJSONArray("new_or_update");
}
//---------------------------
// Determine new or update
//---------------------------
int new_or_updateCnt = 0;
if (new_or_update != null) {
new_or_updateCnt = new_or_update.length();
}
JSONObject clusterData, positionJSON;
Bundle properties;
boolean isNew;
for (int i = 0; i < new_or_updateCnt; i++) {
clusterData = new_or_update.getJSONObject(i);
positionJSON = clusterData.getJSONObject("position");
markerId = clusterData.getString("__pgmId");
clusterId_markerId = clusterId + "-" + markerId;
// Save the marker properties
pluginMap.objects.put("marker_property_" + clusterId_markerId, clusterData);
// Set the WORKING status flag
synchronized (pluginMarkers) {
pluginMarkers.put(clusterId_markerId, STATUS.WORKING);
}
updateClusterIDs.add(clusterId_markerId);
// Prepare the marker properties for addMarker()
properties = new Bundle();
properties.putDouble("lat", positionJSON.getDouble("lat"));
properties.putDouble("lng", positionJSON.getDouble("lng"));
if (clusterData.has("title")) {
properties.putString("title", clusterData.getString("title"));
}
properties.putString("__pgmId", clusterId_markerId);
if (clusterData.has("icon")) {
Object iconObj = clusterData.get("icon");
if (iconObj instanceof String) {
Bundle iconProperties = new Bundle();
iconProperties.putString("url", (String) iconObj);
properties.putBundle("icon", iconProperties);
} else if (iconObj instanceof JSONObject) {
JSONObject icon = clusterData.getJSONObject("icon");
Bundle iconProperties = PluginUtil.Json2Bundle(icon);
if (clusterData.has("isClusterIcon") && clusterData.getBoolean("isClusterIcon")) {
if (icon.has("label")) {
JSONObject label = icon.getJSONObject("label");
if (isDebug) {
label.put("text", markerId);
} else {
label.put("text", clusterData.getInt("count") + "");
}
if (label.has("color")) {
label.put("color", PluginUtil.parsePluginColor(label.getJSONArray("color")));
}
iconProperties.putBundle("label", PluginUtil.Json2Bundle(label));
} else {
Bundle label = new Bundle();
if (isDebug) {
label.putString("text", markerId);
} else {
label.putInt("fontSize", 15);
label.putBoolean("bold", true);
label.putString("text", clusterData.getInt("count") + "");
}
iconProperties.putBundle("label", label);
}
}
if (icon.has("anchor")) {
double[] anchor = new double[2];
anchor[0] = icon.getJSONArray("anchor").getDouble(0);
anchor[1] = icon.getJSONArray("anchor").getDouble(1);
iconProperties.putDoubleArray("anchor", anchor);
}
if (icon.has("infoWindowAnchor")) {
double[] anchor = new double[2];
anchor[0] = icon.getJSONArray("infoWindowAnchor").getDouble(0);
anchor[1] = icon.getJSONArray("infoWindowAnchor").getDouble(1);
iconProperties.putDoubleArray("infoWindowAnchor", anchor);
}
properties.putBundle("icon", iconProperties);
}
}
changeProperties.put(clusterId_markerId, properties);
}
//Log.d(TAG, "---> deleteCnt : " + deleteCnt + ", newCnt : " + newCnt + ", updateCnt : " + updateCnt + ", reuseCnt : " + reuseCnt);
if (updateClusterIDs.size() == 0) {
deleteProcess(clusterId, params);
callbackContext.success();
return;
}
//---------------------------
// mapping markers on the map
//---------------------------
final JSONObject allResults = new JSONObject();
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
Iterator<String> iterator;
String clusterId_markerId;
Bundle markerProperties;
Marker marker;
Boolean isNew;
//---------
// new or update
//---------
int waitCnt = updateClusterIDs.size();
int currentCnt = 0;
waitCntManager.put(clusterId, updateClusterIDs.size());
iterator = updateClusterIDs.iterator();
while (iterator.hasNext()) {
currentCnt++;
clusterId_markerId = iterator.next();
//Log.d(TAG, "---> progress = " + currentCnt + "/ " + waitCnt + ", " + clusterId_markerId);
synchronized (pluginMarkers) {
pluginMarkers.put(clusterId_markerId, STATUS.WORKING);
}
isNew = !pluginMap.objects.containsKey(clusterId_markerId);
// Get the marker properties
markerProperties = changeProperties.get(clusterId_markerId);
if (clusterId_markerId.contains("-marker_")) {
//-------------------
// regular marker
//-------------------
if (isNew) {
final String fMarkerId = clusterId_markerId;
JSONObject properties = (JSONObject) pluginMap.objects.get("marker_property_" + clusterId_markerId);
try {
_create(clusterId_markerId, properties, new ICreateMarkerCallback() {
@Override
public void onSuccess(Marker marker) {
synchronized (pluginMarkers) {
if (pluginMarkers.get(fMarkerId) == STATUS.DELETED) {
_removeMarker(marker);
pluginMarkers.remove(fMarkerId);
} else {
pluginMarkers.put(fMarkerId, STATUS.CREATED);
JSONObject result = new JSONObject();
if (icons.containsKey(fMarkerId)) {
Bitmap icon = icons.get(fMarkerId);
try {
result.put("width", icon.getWidth() / density);
result.put("height", icon.getHeight() / density);
} catch (Exception e) {
e.printStackTrace();
}
} else {
try {
result.put("width", 24);
result.put("height", 42);
} catch (Exception e) {
e.printStackTrace();
}
}
try {
allResults.put(fMarkerId.split("-")[1], result);
} catch (JSONException e) {
e.printStackTrace();
}
}
}
decreaseWaitCnt(clusterId);
}
@Override
public void onError(String message) {
synchronized (pluginMarkers) {
pluginMarkers.put(fMarkerId, STATUS.DELETED);
}
Log.e(TAG, message);
decreaseWaitCnt(clusterId);
synchronized (deleteMarkers) {
deleteMarkers.add(fMarkerId);
}
}
});
} catch (JSONException e) {
e.printStackTrace();
decreaseWaitCnt(clusterId);
}
} else {
marker = getMarker(clusterId_markerId);
//----------------------------------------
// Set the title and snippet properties
//----------------------------------------
if (markerProperties.containsKey("title")) {
marker.setTitle(markerProperties.getString("title"));
}
if (markerProperties.containsKey("snippet")) {
marker.setSnippet(markerProperties.getString("snippet"));
}
synchronized (pluginMarkers) {
if (pluginMarkers.get(clusterId_markerId) == STATUS.DELETED) {
_removeMarker(marker);
pluginMarkers.remove(clusterId_markerId);
} else {
pluginMarkers.put(clusterId_markerId, STATUS.CREATED);
}
}
decreaseWaitCnt(clusterId);
}
continue;
}
//--------------------------
// cluster icon
//--------------------------
if (isNew) {
// If the requested id is new location, create a marker
marker = map.addMarker(new MarkerOptions()
.position(new LatLng(markerProperties.getDouble("lat"), markerProperties.getDouble("lng")))
.visible(false));
marker.setTag(clusterId_markerId);
// Store the marker instance with markerId
synchronized (pluginMap.objects) {
pluginMap.objects.put(clusterId_markerId, marker);
}
} else {
marker = getMarker(clusterId_markerId);
}
//----------------------------------------
// Set the title and snippet properties
//----------------------------------------
if (markerProperties.containsKey("title")) {
marker.setTitle(markerProperties.getString("title"));
}
if (markerProperties.containsKey("snippet")) {
marker.setSnippet(markerProperties.getString("snippet"));
}
/*
if (!isNew) {
synchronized (pluginMarkers) {
pluginMarkers.put(markerId, STATUS.CREATED);
}
decreaseWaitCnt(clusterId);
continue;
}
*/
if (markerProperties.containsKey("icon")) {
Bundle icon = markerProperties.getBundle("icon");
final String fMarkerId = clusterId_markerId;
//Log.d(TAG, "---> targetMarkerId = " + targetMarkerId + ", marker = " + marker);
setIconToClusterMarker(clusterId_markerId, marker, icon, new PluginAsyncInterface() {
@Override
public void onPostExecute(Object object) {
//--------------------------------------
// Marker was updated
//--------------------------------------
decreaseWaitCnt(clusterId);
synchronized (pluginMarkers) {
//Log.d(TAG, "create : " + fMarkerId + " = CREATED");
pluginMarkers.put(fMarkerId, STATUS.CREATED);
}
}
@Override
public void onError(String errorMsg) {
//--------------------------------------
// Could not read icon for some reason
//--------------------------------------
Log.e(TAG, errorMsg);
decreaseWaitCnt(clusterId);
synchronized (deleteMarkers) {
deleteMarkers.add(fMarkerId);
}
synchronized (pluginMarkers) {
pluginMarkers.put(fMarkerId, STATUS.DELETED);
}
}
});
} else {
//--------------------
// No icon for marker
//--------------------
synchronized (pluginMarkers) {
//Log.d(TAG, "create : " + clusterId_markerId + " = CREATED");
pluginMarkers.put(clusterId_markerId, STATUS.CREATED);
}
decreaseWaitCnt(clusterId);
}
}
updateClusterIDs.clear();
}
});
synchronized (semaphore) {
try {
semaphore.wait();
deleteProcess(clusterId, params);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
callbackContext.success(allResults);
}
private void deleteProcess(final String clusterId, final JSONObject params) {
if (!params.has("delete")) {
return;
}
cordova.getThreadPool().submit(new Runnable() {
@Override
public void run() {
try {
JSONArray deleteClusters = params.getJSONArray("delete");
if (deleteClusters != null) {
//-------------------------------------
// delete markers on the delete thread
//-------------------------------------
int deleteCnt = deleteClusters.length();
String clusterId_markerId;
for (int i = 0; i < deleteCnt; i++) {
clusterId_markerId = clusterId + "-" + deleteClusters.getString(i);
deleteMarkers.add(clusterId_markerId);
}
synchronized (deleteThreadLock) {
deleteThreadLock.notify();
}
}
} catch (Exception e) {e.printStackTrace();}
}
});
}
private void decreaseWaitCnt(String clusterId) {
//--------------------------------------
// Icon is set to marker
//--------------------------------------
synchronized (waitCntManager) {
int waitCnt = waitCntManager.get(clusterId);
waitCnt = waitCnt - 1;
//Log.d(TAG, "--->waitCnt = " + waitCnt);
if (waitCnt == 0) {
synchronized (semaphore) {
semaphore.notify();
}
}
waitCntManager.put(clusterId, waitCnt);
}
}
private void setIconToClusterMarker(final String markerId, final Marker marker, final Bundle iconProperty, final PluginAsyncInterface callback) {
synchronized (pluginMarkers) {
if (STATUS.DELETED.equals(pluginMarkers.get(markerId))) {
synchronized (pluginMap.objects) {
PluginMarkerCluster.this._removeMarker(marker);
pluginMap.objects.remove(markerId);
pluginMap.objects.remove("marker_property_" + markerId);
}
pluginMarkers.remove(markerId);
callback.onError("marker has been removed");
return;
}
}
PluginMarkerCluster.super.setIcon_(marker, iconProperty, new PluginAsyncInterface() {
@Override
public void onPostExecute(Object object) {
Marker marker = (Marker) object;
synchronized (pluginMarkers) {
if (STATUS.DELETED.equals(pluginMarkers.get(markerId))) {
synchronized (pluginMap.objects) {
PluginMarkerCluster.this._removeMarker(marker);
pluginMap.objects.remove(markerId);
pluginMap.objects.remove("marker_property_" + markerId);
}
pluginMarkers.remove(markerId);
callback.onPostExecute(null);
return;
}
marker.setVisible(true);
//Log.d(TAG, "create : " + markerId + " = CREATED");
pluginMarkers.put(markerId, STATUS.CREATED);
callback.onPostExecute(object);
}
}
@Override
public void onError(String errorMsg) {
synchronized (pluginMap.objects) {
if (marker != null && marker.getTag() != null) {
PluginMarkerCluster.this._removeMarker(marker);
}
pluginMap.objects.remove(markerId);
pluginMap.objects.remove("marker_property_" + markerId);
pluginMarkers.remove(markerId);
pluginMap.objects.remove(markerId);
pluginMarkers.put(markerId, STATUS.DELETED);
}
callback.onPostExecute(errorMsg);
}
});
}
// The maximum *practical* geocell resolution.
private static final int GEOCELL_GRID_SIZE = 4;
public static final String GEOCELL_ALPHABET = "0123456789abcdef";
private String getGeocell(double lat, double lng, int resolution) {
StringBuilder cell = new StringBuilder();
double north = 90.0;
double south = -90.0;
double east = 180.0;
double west = -180.0;
double subcell_lng_span, subcell_lat_span;
byte x, y;
while(cell.length() < resolution + 1) {
subcell_lng_span = (east - west) / GEOCELL_GRID_SIZE;
subcell_lat_span = (north - south) / GEOCELL_GRID_SIZE;
x = (byte)Math.min(Math.floor(GEOCELL_GRID_SIZE * (lng - west) / (east - west)), GEOCELL_GRID_SIZE - 1);
y = (byte)Math.min(Math.floor(GEOCELL_GRID_SIZE * (lat - south) / (north - south)), GEOCELL_GRID_SIZE - 1);
cell.append(_subdiv_char(x, y));
south += subcell_lat_span * y;
north = south + subcell_lat_span;
west += subcell_lng_span * x;
east = west + subcell_lng_span;
}
return cell.toString();
}
private char _subdiv_char(int posX, int posY) {
return GEOCELL_ALPHABET.charAt(
(posY & 2) << 2 |
(posX & 2) << 1 |
(posY & 1) << 1 |
(posX & 1) << 0);
}
private double[] _subdiv_xy(char cellChar) {
int charI = GEOCELL_ALPHABET.indexOf(cellChar);
return new double[]{
(double)((charI & 4) >> 1 | (charI & 1) >> 0) + 0.0f,
(double)((charI & 8) >> 2 | (charI & 2) >> 1) + 0.0f
};
}
private LatLngBounds computeBox(String geocell) {
//String tmp[] = geocell.split("-");
//geocell = tmp[1];
double subcell_lng_span, subcell_lat_span;
double x, y;
double xy[];
BoundBox bbox = new BoundBox(90.0, 180.0, -90.0, -180.0);
while (geocell.length() > 0) {
subcell_lng_span = (double)(bbox.getEast() - bbox.getWest()) / (double)GEOCELL_GRID_SIZE;
subcell_lat_span = (double)(bbox.getNorth() - bbox.getSouth()) / (double)GEOCELL_GRID_SIZE;
xy = _subdiv_xy(geocell.charAt(0));
x = xy[0];
y = xy[1];
bbox = new BoundBox(bbox.getSouth() + subcell_lat_span * (y + 1.0f),
bbox.getWest() + subcell_lng_span * (double)(x + 1.0f),
bbox.getSouth() + subcell_lat_span * y,
bbox.getWest() + subcell_lng_span * x);
geocell = geocell.substring(1);
}
LatLng sw = new LatLng(bbox.getSouth(), bbox.getWest());
LatLng ne = new LatLng(bbox.getNorth(), bbox.getEast());
return new LatLngBounds(sw, ne);
}
private class BoundBox {
double north_, south_, east_, west_;
BoundBox(double north, double east, double south, double west) {
if (south > north) {
south_ = north;
north_ = south;
} else {
south_ = south;
north_ = north;
}
west_ = west;
east_ = east;
}
public double getNorth() {
return north_;
}
public double getSouth() {
return south_;
}
public double getWest() {
return west_;
}
public double getEast() {
return east_;
}
}
}
@@ -0,0 +1,753 @@
package plugin.google.maps;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.Polygon;
import com.google.android.gms.maps.model.PolygonOptions;
import org.apache.cordova.CallbackContext;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Set;
//Future implement
//TODO: https://codepen.io/jhawes/pen/ujdgK
public class PluginPolygon extends MyPlugin implements MyPluginInterface {
private String polygonHashCode;
/**
* Create polygon
* @param args
* @param callbackContext
* @throws JSONException
*/
@Override
public void create(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
self = this;
final PolygonOptions polygonOptions = new PolygonOptions();
int color;
final LatLngBounds.Builder builder = new LatLngBounds.Builder();
final JSONObject properties = new JSONObject();
final ArrayList<LatLngBounds> holeBounds = new ArrayList<LatLngBounds>();
final ArrayList<LatLng> path = new ArrayList<LatLng>();
final ArrayList<ArrayList<LatLng>> holePaths = new ArrayList<ArrayList<LatLng>>();
JSONObject opts = args.getJSONObject(1);
final String hashCode = args.getString(2);
polygonHashCode = hashCode;
if (opts.has("points")) {
JSONArray points = opts.getJSONArray("points");
ArrayList<LatLng> path2 = PluginUtil.JSONArray2LatLngList(points);
for (int i = 0; i < path2.size(); i++) {
polygonOptions.add(path2.get(i));
path.add(path2.get(i));
builder.include(path2.get(i));
}
}
if (opts.has("holes")) {
JSONArray holes = opts.getJSONArray("holes");
int i;
JSONArray latLngArray;
ArrayList<LatLng> hole;
Iterator<LatLng> iterator;
LatLng latLng;
LatLngBounds.Builder builder2;
for (i = 0; i < holes.length(); i++) {
latLngArray = holes.getJSONArray(i);
hole = PluginUtil.JSONArray2LatLngList(latLngArray);
polygonOptions.addHole(hole);
holePaths.add(hole);
iterator = hole.iterator();
builder2 = new LatLngBounds.Builder();
while (iterator.hasNext()) {
latLng = iterator.next();
builder2.include(latLng);
}
holeBounds.add(builder2.build());
}
}
if (opts.has("strokeColor")) {
color = PluginUtil.parsePluginColor(opts.getJSONArray("strokeColor"));
polygonOptions.strokeColor(color);
}
if (opts.has("fillColor")) {
color = PluginUtil.parsePluginColor(opts.getJSONArray("fillColor"));
polygonOptions.fillColor(color);
}
if (opts.has("strokeWidth")) {
polygonOptions.strokeWidth((float)(opts.getDouble("strokeWidth") * density));
}
if (opts.has("visible")) {
polygonOptions.visible(opts.getBoolean("visible"));
}
if (opts.has("geodesic")) {
polygonOptions.geodesic(opts.getBoolean("geodesic"));
}
if (opts.has("zIndex")) {
polygonOptions.zIndex(opts.getInt("zIndex"));
}
if (opts.has("clickable")) {
properties.put("isClickable", opts.getBoolean("clickable"));
} else {
properties.put("isClickable", true);
}
properties.put("isVisible", polygonOptions.isVisible());
properties.put("zIndex", polygonOptions.getZIndex());
properties.put("isGeodesic", polygonOptions.isGeodesic());
// Since this plugin uses own detecting process,
// set false to the clickable property.
polygonOptions.clickable(false);
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
Polygon polygon = map.addPolygon(polygonOptions);
String id = hashCode;
polygon.setTag(hashCode);
pluginMap.objects.put("polygon_"+ id, polygon);
pluginMap.objects.put("polygon_bounds_" + id, builder.build());
pluginMap.objects.put("polygon_path_" + id, path);
pluginMap.objects.put("polygon_holePaths_" + id, holePaths);
pluginMap.objects.put("polygon_property_" + id, properties);
JSONObject result = new JSONObject();
try {
result.put("hashCode", hashCode);
result.put("__pgmId", "polygon_"+ id);
} catch (JSONException e) {
e.printStackTrace();
}
callbackContext.success(result);
}
});
}
@Override
public void onDestroy() {
super.onDestroy();
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
Set<String> keySet = pluginMap.objects.keys;
if (keySet.size() > 0) {
String[] objectIdArray = keySet.toArray(new String[keySet.size()]);
for (String objectId : objectIdArray) {
if (pluginMap.objects.containsKey(objectId)) {
if (objectId.contains("property")) {
Polygon polygon = (Polygon) pluginMap.objects.remove(objectId.replace("property_", ""));
if (polygon != null) {
polygon.remove();
}
}
Object object = pluginMap.objects.remove(objectId);
object = null;
}
}
}
}
});
}
/**
* set fill color
* @param args
* @param callbackContext
* @throws JSONException
*/
public void setFillColor(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
int color = PluginUtil.parsePluginColor(args.getJSONArray(1));
this.setInt("setFillColor", id, color, callbackContext);
}
/**
* set stroke color
* @param args
* @param callbackContext
* @throws JSONException
*/
public void setStrokeColor(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
int color = PluginUtil.parsePluginColor(args.getJSONArray(1));
this.setInt("setStrokeColor", id, color, callbackContext);
}
/**
* set stroke width
* @param args
* @param callbackContext
* @throws JSONException
*/
@SuppressWarnings("unused")
public void setStrokeWidth(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
float width = (float)(args.getDouble(1) * density);
this.setFloat("setStrokeWidth", id, width, callbackContext);
}
/**
* set z-index
* @param args
* @param callbackContext
* @throws JSONException
*/
@SuppressWarnings("unused")
public void setZIndex(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
final float zIndex = (float) args.getDouble(1);
this.setFloat("setZIndex", id, zIndex, callbackContext);
}
/**
* set geodesic
* @param args
* @param callbackContext
* @throws JSONException
*/
public void setGeodesic(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
boolean isGeodisic = args.getBoolean(1);
this.setBoolean("setGeodesic", id, isGeodisic, callbackContext);
}
/**
* Remove the polygon
* @param args
* @param callbackContext
* @throws JSONException
*/
public void remove(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
final Polygon polygon = this.getPolygon(id);
if (polygon == null) {
callbackContext.success();
return;
}
pluginMap.objects.remove(id);
pluginMap.objects.remove("polygon_bounds_" + polygonHashCode);
pluginMap.objects.remove("polygon_property_" + polygonHashCode);
pluginMap.objects.remove("polygon_path_" + polygonHashCode);
pluginMap.objects.remove("polygon_holePaths_" + polygonHashCode);
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
polygon.remove();
callbackContext.success();
}
});
}
/**
* Set points
* @param args
* @param callbackContext
* @throws JSONException
*/
public void removePointAt(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
final int index = args.getInt(1);
final Polygon polygon = this.getPolygon(id);
//------------------------
// Update the hole list
//------------------------
String propertyId = "polygon_path_" + polygonHashCode;
final ArrayList<LatLng> path = (ArrayList<LatLng>)pluginMap.objects.get(propertyId);
if (path.size() > 0) {
path.remove(index);
}
pluginMap.objects.put(propertyId, path);
//-----------------------------------
// Recalculate the polygon bounds
//-----------------------------------
propertyId = "polygon_bounds_" + polygonHashCode;
if (path.size() > 0) {
pluginMap.objects.put(propertyId, PluginUtil.getBoundsFromPath(path));
} else {
pluginMap.objects.remove(propertyId);
}
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
if (path.size() > 0) {
try {
polygon.setPoints(path);
} catch (Exception e) {
// Ignore this error
//e.printStackTrace();
}
} else {
polygon.setVisible(false);
}
callbackContext.success();
}
});
}
/**
* Set points
* @param args
* @param callbackContext
* @throws JSONException
*/
public void setPoints(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
final JSONArray positionList = args.getJSONArray(1);
final Polygon polygon = this.getPolygon(id);
//------------------------
// Update the hole list
//------------------------
String propertyId = "polygon_path_" + polygonHashCode;
final ArrayList<LatLng> path = (ArrayList<LatLng>)pluginMap.objects.get(propertyId);
path.clear();
JSONObject position;
for (int i = 0; i < positionList.length(); i++) {
position = positionList.getJSONObject(i);
path.add(new LatLng(position.getDouble("lat"), position.getDouble("lng")));
}
pluginMap.objects.put(propertyId, path);
//-----------------------------------
// Recalculate the polygon bounds
//-----------------------------------
propertyId = "polygon_bounds_" + polygonHashCode;
pluginMap.objects.put(propertyId, PluginUtil.getBoundsFromPath(path));
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
// Update the polygon
polygon.setPoints(path);
if (path.size() > 0) {
polygon.setVisible(polygon.isVisible());
} else {
polygon.setVisible(false);
}
callbackContext.success();
}
});
}
/**
* Insert a point
* @param args
* @param callbackContext
* @throws JSONException
*/
public void insertPointAt(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
final int index = args.getInt(1);
JSONObject position = args.getJSONObject(2);
final LatLng latLng = new LatLng(position.getDouble("lat"), position.getDouble("lng"));
final Polygon polygon = this.getPolygon(id);
//------------------------
// Update the hole list
//------------------------
boolean shouldBeVisible = false;
String propertyId = "polygon_path_" + polygonHashCode;
final ArrayList<LatLng> path = (ArrayList<LatLng>)pluginMap.objects.get(propertyId);
if (path.size() == 0) {
JSONObject properties = (JSONObject)pluginMap.objects.get("polygon_property_" + polygonHashCode);
if (properties.getBoolean("isVisible")) {
shouldBeVisible = true;
}
}
path.add(index, latLng);
pluginMap.objects.put(propertyId, path);
//-----------------------------------
// Recalculate the polygon bounds
//-----------------------------------
propertyId = "polygon_bounds_" + polygonHashCode;
pluginMap.objects.put(propertyId, PluginUtil.getBoundsFromPath(path));
final boolean changeToVisible = shouldBeVisible;
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
// Update the polygon
polygon.setPoints(path);
if (changeToVisible) {
polygon.setVisible(true);
}
callbackContext.success();
}
});
}
/**
* Set points
* @param args
* @param callbackContext
* @throws JSONException
*/
public void setPointAt(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
final int index = args.getInt(1);
JSONObject position = args.getJSONObject(2);
final LatLng latLng = new LatLng(position.getDouble("lat"), position.getDouble("lng"));
final Polygon polygon = this.getPolygon(id);
//------------------------
// Update the hole list
//------------------------
String propertyId = "polygon_path_" + polygonHashCode;
final ArrayList<LatLng> path = (ArrayList<LatLng>)pluginMap.objects.get(propertyId);
path.set(index, latLng);
pluginMap.objects.put(propertyId, path);
//-----------------------------------
// Recalculate the polygon bounds
//-----------------------------------
propertyId = "polygon_bounds_" + polygonHashCode;
pluginMap.objects.put(propertyId, PluginUtil.getBoundsFromPath(path));
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
// Update the polygon
polygon.setPoints(path);
callbackContext.success();
}
});
}
/**
* Set points
* @param args
* @param callbackContext
* @throws JSONException
*/
public void setHoles(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
final JSONArray holeList = args.getJSONArray(1);
final Polygon polygon = this.getPolygon(id);
//------------------------
// Update the hole list
//------------------------
String propertyId = "polygon_holePaths_" + polygonHashCode;
final ArrayList<ArrayList<LatLng>> holes = (ArrayList<ArrayList<LatLng>>) pluginMap.objects.get(propertyId);
for (int i = 0; i < holes.size(); i++) {
holes.get(i).clear();
}
holes.clear();
JSONObject position;
for (int i = 0; i < holeList.length(); i++) {
ArrayList<LatLng> hole = new ArrayList<LatLng>();
JSONArray holePositions = holeList.getJSONArray(i);
for (int j = 0; j < holePositions.length(); j++) {
position = holePositions.getJSONObject(j);
hole.add(new LatLng(position.getDouble("lat"), position.getDouble("lng")));
}
holes.add(hole);
}
pluginMap.objects.put(propertyId, holes);
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
// Update the polygon
polygon.setHoles(holes);
callbackContext.success();
}
});
}
/**
* Set points
* @param args
* @param callbackContext
* @throws JSONException
*/
public void insertPointOfHoleAt(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
final int holeIndex = args.getInt(1);
final int pointIndex = args.getInt(2);
JSONObject position = args.getJSONObject(3);
final LatLng latLng = new LatLng(position.getDouble("lat"), position.getDouble("lng"));
final Polygon polygon = this.getPolygon(id);
//------------------------
// Update the hole list
//------------------------
String propertyId = "polygon_holePaths_" + polygonHashCode;
final ArrayList<ArrayList<LatLng>> holes = (ArrayList<ArrayList<LatLng>>) pluginMap.objects.get(propertyId);
ArrayList<LatLng> hole = null;
if (holeIndex < holes.size()) {
hole = holes.get(holeIndex);
}
if (hole == null) {
hole = new ArrayList<LatLng>();
}
if (holes.size() == 0) {
holes.add(hole);
}
hole.add(pointIndex, latLng);
pluginMap.objects.put(propertyId, holes);
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
// Update the polygon
polygon.setHoles(holes);
callbackContext.success();
}
});
}
/**
* Set points
* @param args
* @param callbackContext
* @throws JSONException
*/
public void setPointOfHoleAt(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
final int holeIndex = args.getInt(1);
final int pointIndex = args.getInt(2);
JSONObject position = args.getJSONObject(3);
final LatLng latLng = new LatLng(position.getDouble("lat"), position.getDouble("lng"));
final Polygon polygon = this.getPolygon(id);
//------------------------
// Update the hole list
//------------------------
String propertyId = "polygon_holePaths_" + polygonHashCode;
final ArrayList<ArrayList<LatLng>> holes = (ArrayList<ArrayList<LatLng>>) pluginMap.objects.get(propertyId);
ArrayList<LatLng> hole = null;
if (holeIndex < holes.size()) {
hole = holes.get(holeIndex);
}
if (hole == null) {
hole = new ArrayList<LatLng>();
}
if (holes.size() == 0) {
holes.add(hole);
}
hole.set(pointIndex, latLng);
pluginMap.objects.put(propertyId, holes);
final ArrayList<LatLng> newHole = hole;
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
// Update the polygon
polygon.setHoles(holes);
callbackContext.success();
}
});
}
/**
* Set points
* @param args
* @param callbackContext
* @throws JSONException
*/
public void removePointOfHoleAt(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
final int holeIndex = args.getInt(1);
final int pointIndex = args.getInt(2);
final Polygon polygon = this.getPolygon(id);
//------------------------
// Update the hole list
//------------------------
String propertyId = "polygon_holePaths_" + polygonHashCode;
final ArrayList<ArrayList<LatLng>> holes = (ArrayList<ArrayList<LatLng>>) pluginMap.objects.get(propertyId);
ArrayList<LatLng> hole = null;
if (holeIndex < holes.size()) {
hole = holes.get(holeIndex);
}
if (hole == null) {
hole = new ArrayList<LatLng>();
}
if (holes.size() == 0) {
holes.add(hole);
}
hole.remove(pointIndex);
pluginMap.objects.put(propertyId, holes);
final ArrayList<LatLng> newHole = hole;
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
// Update the polygon
polygon.setHoles(holes);
callbackContext.success();
}
});
}
public void insertHoleAt(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
final int holeIndex = args.getInt(1);
JSONArray holeJson = args.getJSONArray(2);
final ArrayList<LatLng> newHole = PluginUtil.JSONArray2LatLngList(holeJson);
final Polygon polygon = this.getPolygon(id);
//------------------------
// Update the hole list
//------------------------
String propertyId = "polygon_holePaths_" + polygonHashCode;
final ArrayList<ArrayList<LatLng>> holes = (ArrayList<ArrayList<LatLng>>) pluginMap.objects.get(propertyId);
holes.add(holeIndex, newHole);
pluginMap.objects.put(propertyId, holes);
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
// Update the polygon
try {
polygon.setHoles(holes);
} catch (Exception e) {
// Ignore this error
//e.printStackTrace();
}
callbackContext.success();
}
});
}
/**
* Set points
* @param args
* @param callbackContext
* @throws JSONException
*/
public void setHoleAt(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
final int holeIndex = args.getInt(1);
JSONArray holeJson = args.getJSONArray(2);
final ArrayList<LatLng> newHole = PluginUtil.JSONArray2LatLngList(holeJson);
final Polygon polygon = this.getPolygon(id);
//------------------------
// Update the hole list
//------------------------
String propertyId = "polygon_holePaths_" + polygonHashCode;
final ArrayList<ArrayList<LatLng>> holes = (ArrayList<ArrayList<LatLng>>) pluginMap.objects.get(propertyId);
holes.set(holeIndex, newHole);
pluginMap.objects.put(propertyId, holes);
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
// Update the polygon
polygon.setHoles(holes);
callbackContext.success();
}
});
}
/**
* Set points
* @param args
* @param callbackContext
* @throws JSONException
*/
public void removeHoleAt(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
final int holeIndex = args.getInt(1);
final Polygon polygon = this.getPolygon(id);
//------------------------
// Update the hole list
//------------------------
String propertyId = "polygon_holePaths_" + polygonHashCode;
final ArrayList<ArrayList<LatLng>> holes = (ArrayList<ArrayList<LatLng>>) pluginMap.objects.get(propertyId);
holes.remove(holeIndex);
pluginMap.objects.put(propertyId, holes);
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
// Update the polygon
polygon.setHoles(holes);
callbackContext.success();
}
});
}
/**
* Set visibility for the object
* @param args
* @param callbackContext
* @throws JSONException
*/
public void setVisible(JSONArray args, CallbackContext callbackContext) throws JSONException {
final boolean isVisible = args.getBoolean(1);
String id = args.getString(0);
final Polygon polygon = this.getPolygon(id);
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
polygon.setVisible(isVisible);
}
});
String propertyId = "polygon_property_" + polygonHashCode;
JSONObject properties = (JSONObject)pluginMap.objects.get(propertyId);
properties.put("isVisible", isVisible);
pluginMap.objects.put(propertyId, properties);
callbackContext.success();
}
/**
* Set clickable for the object
* @param args
* @param callbackContext
* @throws JSONException
*/
public void setClickable(JSONArray args, CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
final boolean clickable = args.getBoolean(1);
String propertyId = id.replace("polygon_", "polygon_property_");
JSONObject properties = (JSONObject)pluginMap.objects.get(propertyId);
properties.put("isClickable", clickable);
pluginMap.objects.put(propertyId, properties);
callbackContext.success();
}
}
@@ -0,0 +1,334 @@
package plugin.google.maps;
import android.graphics.Color;
import android.util.Log;
import com.google.android.gms.maps.model.CircleOptions;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.Polyline;
import com.google.android.gms.maps.model.PolylineOptions;
import org.apache.cordova.CallbackContext;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
public class PluginPolyline extends MyPlugin implements MyPluginInterface {
private String polylineHashCode;
/**
* Create polyline
* @param args
* @param callbackContext
* @throws JSONException
*/
public void create(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
self = this;
final PolylineOptions polylineOptions = new PolylineOptions();
int color;
final LatLngBounds.Builder builder = new LatLngBounds.Builder();
final JSONObject properties = new JSONObject();
JSONObject opts = args.getJSONObject(1);
final String hashCode = args.getString(2);
polylineHashCode = hashCode;
if (opts.has("points")) {
JSONArray points = opts.getJSONArray("points");
List<LatLng> path = PluginUtil.JSONArray2LatLngList(points);
int i = 0;
for (i = 0; i < path.size(); i++) {
polylineOptions.add(path.get(i));
builder.include(path.get(i));
}
}
if (opts.has("color")) {
color = PluginUtil.parsePluginColor(opts.getJSONArray("color"));
polylineOptions.color(color);
}
if (opts.has("width")) {
polylineOptions.width((float)(opts.getDouble("width") * density));
}
if (opts.has("visible")) {
polylineOptions.visible(opts.getBoolean("visible"));
}
if (opts.has("geodesic")) {
polylineOptions.geodesic(opts.getBoolean("geodesic"));
}
if (opts.has("zIndex")) {
polylineOptions.zIndex(opts.getInt("zIndex"));
}
if (opts.has("clickable")) {
properties.put("isClickable", opts.getBoolean("clickable"));
} else {
properties.put("isClickable", true);
}
properties.put("isVisible", polylineOptions.isVisible());
// Since this plugin provide own click detection,
// disable default clickable feature.
polylineOptions.clickable(false);
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
Polyline polyline = map.addPolyline(polylineOptions);
polyline.setTag(hashCode);
String id = "polyline_" + hashCode;
pluginMap.objects.put(id, polyline);
String boundsId = "polyline_bounds_" + hashCode;
pluginMap.objects.put(boundsId, builder.build());
String propertyId = "polyline_property_" + hashCode;
pluginMap.objects.put(propertyId, properties);
try {
JSONObject result = new JSONObject();
result.put("hashCode", hashCode);
result.put("__pgmId", id);
callbackContext.success(result);
} catch (JSONException e) {
e.printStackTrace();
callbackContext.error("" + e.getMessage());
}
}
});
}
/**
* set color
* @param args
* @param callbackContext
* @throws JSONException
*/
public void setStrokeColor(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
int color = PluginUtil.parsePluginColor(args.getJSONArray(1));
this.setInt("setColor", id, color, callbackContext);
}
/**
* set width
* @param args
* @param callbackContext
* @throws JSONException
*/
public void setStrokeWidth(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
float width = (float)(args.getDouble(1) * density);
this.setFloat("setWidth", id, width, callbackContext);
}
/**
* set z-index
* @param args
* @param callbackContext
* @throws JSONException
*/
public void setZIndex(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
float zIndex = (float) args.getDouble(1);
this.setFloat("setZIndex", id, zIndex, callbackContext);
}
/**
* Remove the polyline
* @param args
* @param callbackContext
* @throws JSONException
*/
public void remove(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
final Polyline polyline = this.getPolyline(id);
if (polyline == null) {
callbackContext.success();
return;
}
pluginMap.objects.remove(id);
id = "polyline_bounds_" + polylineHashCode;
pluginMap.objects.remove(id);
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
polyline.remove();
callbackContext.success();
}
});
}
public void setPoints(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
final JSONArray positionList = args.getJSONArray(1);
final Polyline polyline = this.getPolyline(id);
// Recalculate the polygon bounds
final String propertyId = "polyline_bounds_" + polylineHashCode;
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
try {
List<LatLng> path = polyline.getPoints();
path.clear();
JSONObject position;
for (int i = 0; i < positionList.length(); i++) {
position = positionList.getJSONObject(i);
path.add(new LatLng(position.getDouble("lat"), position.getDouble("lng")));
}
polyline.setPoints(path);
pluginMap.objects.put(propertyId, PluginUtil.getBoundsFromPath(path));
} catch (JSONException e) {
e.printStackTrace();
}
callbackContext.success();
}
});
}
public void removePointAt(final JSONArray args, CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
final int index = args.getInt(1);
final Polyline polyline = this.getPolyline(id);
// Recalculate the polygon bounds
final String propertyId = "polyline_bounds_" + polylineHashCode;
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
List<LatLng> path = polyline.getPoints();
if (path.size() > index) {
path.remove(index);
if (path.size() > 0) {
pluginMap.objects.put(propertyId, PluginUtil.getBoundsFromPath(path));
} else {
pluginMap.objects.remove(propertyId);
}
polyline.setPoints(path);
}
}
});
callbackContext.success();
}
public void insertPointAt(final JSONArray args, CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
final int index = args.getInt(1);
JSONObject position = args.getJSONObject(2);
final LatLng latLng = new LatLng(position.getDouble("lat"), position.getDouble("lng"));
final Polyline polyline = this.getPolyline(id);
// Recalculate the polygon bounds
final String propertyId = "polyline_bounds_" + polylineHashCode;
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
List<LatLng> path = polyline.getPoints();
if (path.size() >= index) {
path.add(index, latLng);
polyline.setPoints(path);
pluginMap.objects.put(propertyId, PluginUtil.getBoundsFromPath(path));
}
}
});
callbackContext.success();
}
public void setPointAt(final JSONArray args, CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
final int index = args.getInt(1);
JSONObject position = args.getJSONObject(2);
final LatLng latLng = new LatLng(position.getDouble("lat"), position.getDouble("lng"));
final Polyline polyline = this.getPolyline(id);
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
List<LatLng> path = polyline.getPoints();
if (path.size() > index) {
path.set(index, latLng);
// Recalculate the polygon bounds
String propertyId = "polyline_bounds_" + polylineHashCode;
pluginMap.objects.put(propertyId, PluginUtil.getBoundsFromPath(path));
polyline.setPoints(path);
}
}
});
callbackContext.success();
}
/**
* set geodesic
* @param args
* @param callbackContext
* @throws JSONException
*/
public void setGeodesic(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
boolean isGeodisic = args.getBoolean(1);
this.setBoolean("setGeodesic", id, isGeodisic, callbackContext);
}
/**
* Set visibility for the object
* @param args
* @param callbackContext
* @throws JSONException
*/
public void setVisible(JSONArray args, CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
final boolean isVisible = args.getBoolean(1);
final Polyline polyline = this.getPolyline(id);
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
polyline.setVisible(isVisible);
}
});
String propertyId = "polyline_property_" + polylineHashCode;
JSONObject properties = (JSONObject)pluginMap.objects.get(propertyId);
properties.put("isVisible", isVisible);
pluginMap.objects.put(propertyId, properties);
callbackContext.success();
}
/**
* Set clickable for the object
* @param args
* @param callbackContext
* @throws JSONException
*/
public void setClickable(JSONArray args, CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
final boolean clickable = args.getBoolean(1);
String propertyId = id.replace("polyline_", "polyline_property_");
JSONObject properties = (JSONObject)pluginMap.objects.get(propertyId);
properties.put("isClickable", clickable);
pluginMap.objects.put(propertyId, properties);
callbackContext.success();
}
}
@@ -0,0 +1,459 @@
package plugin.google.maps;
import android.app.Activity;
import android.graphics.Point;
import android.view.View;
import android.view.ViewGroup;
import com.google.android.gms.maps.OnStreetViewPanoramaReadyCallback;
import com.google.android.gms.maps.StreetViewPanorama;
import com.google.android.gms.maps.StreetViewPanoramaOptions;
import com.google.android.gms.maps.StreetViewPanoramaView;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.StreetViewPanoramaCamera;
import com.google.android.gms.maps.model.StreetViewPanoramaLink;
import com.google.android.gms.maps.model.StreetViewPanoramaLocation;
import com.google.android.gms.maps.model.StreetViewPanoramaOrientation;
import com.google.android.gms.maps.model.StreetViewSource;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaWebView;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Locale;
public class PluginStreetViewPanorama extends MyPlugin implements
IPluginView, StreetViewPanorama.OnStreetViewPanoramaCameraChangeListener,
StreetViewPanorama.OnStreetViewPanoramaChangeListener,
StreetViewPanorama.OnStreetViewPanoramaClickListener {
private Activity mActivity;
private StreetViewPanoramaView panoramaView;
private StreetViewPanorama panorama;
private String panoramaId;
private boolean isVisible = true;
private boolean isClickable = true;
private final String TAG = "StreetView";
private String divId;
private int viewDepth = 0;
@Override
public void initialize(CordovaInterface cordova, final CordovaWebView webView) {
super.initialize(cordova, webView);
mActivity = cordova.getActivity();
}
public int getViewDepth() {
return viewDepth;
}
public String getDivId() {
return this.divId;
}
public String getOverlayId() {
return this.panoramaId;
}
public ViewGroup getView() {
return this.panoramaView;
}
public boolean getVisible() {
return isVisible;
}
public boolean getClickable() {
return isClickable;
}
public void getPanorama(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
JSONObject meta = args.getJSONObject(0);
panoramaId = meta.getString("__pgmId");
viewDepth = meta.getInt("depth");
JSONObject jsOptions = args.getJSONObject(1);
divId = args.getString(2);
StreetViewPanoramaOptions svOptions = new StreetViewPanoramaOptions();
if (jsOptions.has("camera")) {
JSONObject cameraOpts = jsOptions.getJSONObject("camera");
Object target = cameraOpts.get("target");
if (target instanceof JSONObject) {
JSONObject targetJson = cameraOpts.getJSONObject("target");
LatLng position = new LatLng(targetJson.getDouble("lat"), targetJson.getDouble("lng"));
if (cameraOpts.has("source")) {
StreetViewSource source = "OUTDOOR".equals(cameraOpts.getString("source")) ?
StreetViewSource.OUTDOOR : StreetViewSource.DEFAULT;
if (cameraOpts.has("radius")) {
svOptions.position(position, cameraOpts.getInt("radius"), source);
} else {
svOptions.position(position, source);
}
} else {
if (cameraOpts.has("radius")) {
svOptions.position(position, cameraOpts.getInt("radius"));
} else {
svOptions.position(position);
}
}
} else if (target instanceof String) {
svOptions.panoramaId(cameraOpts.getString("target"));
}
if (cameraOpts.has("bearing") ||
cameraOpts.has("tilt") ||
cameraOpts.has("zoom")) {
StreetViewPanoramaCamera.Builder builder = StreetViewPanoramaCamera.builder();
if (cameraOpts.has("bearing")) {
builder.bearing = (float) cameraOpts.getDouble("bearing");
}
if (cameraOpts.has("tilt")) {
builder.tilt = (float) cameraOpts.getDouble("tilt");
}
if (cameraOpts.has("zoom")) {
builder.zoom = (float) cameraOpts.getDouble("zoom");
}
svOptions.panoramaCamera(builder.build());
}
}
if (jsOptions.has("gestures")) {
JSONObject gestures = jsOptions.getJSONObject("gestures");
if (gestures.has("panning")) {
svOptions.panningGesturesEnabled(gestures.getBoolean("panning"));
}
if (gestures.has("zoom")) {
svOptions.zoomGesturesEnabled(gestures.getBoolean("zoom"));
}
}
if (jsOptions.has("controls")) {
JSONObject controls = jsOptions.getJSONObject("controls");
if (controls.has("navigation")) {
svOptions.userNavigationEnabled(controls.getBoolean("navigation"));
}
if (controls.has("streetNames")) {
svOptions.streetNamesEnabled(controls.getBoolean("streetNames"));
}
}
panoramaView = new StreetViewPanoramaView(mActivity, svOptions);
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
panoramaView.onCreate(null);
panoramaView.setTag(getViewDepth());
panoramaView.getStreetViewPanoramaAsync(new OnStreetViewPanoramaReadyCallback() {
@Override
public void onStreetViewPanoramaReady(StreetViewPanorama streetViewPanorama) {
panoramaView.onResume();
panorama = streetViewPanorama;
panorama.setOnStreetViewPanoramaCameraChangeListener(PluginStreetViewPanorama.this);
panorama.setOnStreetViewPanoramaChangeListener(PluginStreetViewPanorama.this);
panorama.setOnStreetViewPanoramaClickListener(PluginStreetViewPanorama.this);
// Don't support this because iOS does not support this feature.
//panorama.setOnStreetViewPanoramaLongClickListener(PluginStreetViewPanorama.this);
mapCtrl.mPluginLayout.addPluginOverlay(PluginStreetViewPanorama.this);
callbackContext.success();
}
});
}
});
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public void onPause(boolean multitasking) {
super.onPause(multitasking);
if (panoramaView != null && panoramaView.isActivated()) {
panoramaView.onPause();
}
//mapCtrl.mPluginLayout.removePluginOverlay(this.panoramaId);
}
@Override
public void onResume(boolean multitasking) {
super.onResume(multitasking);
if (panoramaView != null && panoramaView.isActivated()) {
panoramaView.onResume();
}
//mapCtrl.mPluginLayout.addPluginOverlay(PluginStreetViewPanorama.this);
}
public void attachToWebView(JSONArray args, final CallbackContext callbackContext) {
mapCtrl.mPluginLayout.addPluginOverlay(this);
callbackContext.success();
}
public void detachFromWebView(JSONArray args, final CallbackContext callbackContext) {
mapCtrl.mPluginLayout.removePluginOverlay(this.panoramaId);
callbackContext.success();
}
public void setPanningGesturesEnabled(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
final boolean boolValue = args.getBoolean(0);
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
panorama.setPanningGesturesEnabled(boolValue);
callbackContext.success();
}
});
}
public void setZoomGesturesEnabled(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
final boolean boolValue = args.getBoolean(0);
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
panorama.setZoomGesturesEnabled(boolValue);
callbackContext.success();
}
});
}
public void setNavigationEnabled(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
final boolean boolValue = args.getBoolean(0);
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
panorama.setUserNavigationEnabled(boolValue);
callbackContext.success();
}
});
}
public void setStreetNamesEnabled(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
final boolean boolValue = args.getBoolean(0);
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
panorama.setStreetNamesEnabled(boolValue);
callbackContext.success();
}
});
}
public void setVisible(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
final boolean boolValue = args.getBoolean(0);
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
panoramaView.setVisibility(boolValue ? View.VISIBLE : View.INVISIBLE);
callbackContext.success();
}
});
}
public void setPosition(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
try {
JSONObject cameraOpts = args.getJSONObject(0);
Object target = cameraOpts.get("target");
if (target instanceof JSONObject) {
JSONObject targetJson = (JSONObject)target;
LatLng position = new LatLng(targetJson.getDouble("lat"), targetJson.getDouble("lng"));
if (cameraOpts.has("source")) {
StreetViewSource source = "OUTDOOR".equals(cameraOpts.getString("source")) ?
StreetViewSource.OUTDOOR : StreetViewSource.DEFAULT;
if (cameraOpts.has("radius")) {
panorama.setPosition(position, cameraOpts.getInt("radius"), source);
} else {
panorama.setPosition(position, source);
}
} else {
if (cameraOpts.has("radius")) {
panorama.setPosition(position, cameraOpts.getInt("radius"));
} else {
panorama.setPosition(position);
}
}
} else if (target instanceof String) {
panorama.setPosition(cameraOpts.getString("target"));
}
callbackContext.success();
} catch (JSONException e) {
e.printStackTrace();
callbackContext.error("" + e.getMessage());
}
}
});
}
public void setPov(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
try {
final JSONObject cameraPosition = args.getJSONObject(0);
StreetViewPanoramaCamera currentCamera = panorama.getPanoramaCamera();
float bearing = cameraPosition.has("bearing") ? (float) cameraPosition.getDouble("bearing") : currentCamera.bearing;
float tilt = cameraPosition.has("tilt") ? (float) cameraPosition.getDouble("tilt") : currentCamera.tilt;
float zoom = cameraPosition.has("zoom") ? (float) cameraPosition.getDouble("zoom") : currentCamera.zoom;
long duration = cameraPosition.has("duration") ? (long) cameraPosition.getDouble("duration") : 1000;
StreetViewPanoramaCamera newCamera = new StreetViewPanoramaCamera.Builder()
.bearing(bearing)
.zoom(zoom)
.tilt(tilt)
.build();
panorama.animateTo(newCamera, duration);
callbackContext.success();
} catch (Exception e) {
e.printStackTrace();
callbackContext.error("" + e.getMessage());
}
}
});
}
public void remove(JSONArray args, final CallbackContext callbackContext) {
mapCtrl.mPluginLayout.removePluginOverlay(this.panoramaId);
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
panorama.setOnStreetViewPanoramaLongClickListener(null);
panorama.setOnStreetViewPanoramaClickListener(null);
panorama.setOnStreetViewPanoramaChangeListener(null);
panorama.setOnStreetViewPanoramaCameraChangeListener(null);
System.gc();
Runtime.getRuntime().gc();
if (callbackContext != null) {
callbackContext.success();
}
PluginStreetViewPanorama.this.onDestroy();
}
});
}
@Override
public void onStreetViewPanoramaCameraChange(StreetViewPanoramaCamera streetViewPanoramaCamera) {
try {
JSONObject camera = new JSONObject();
camera.put("bearing", streetViewPanoramaCamera.bearing);
camera.put("tilt", streetViewPanoramaCamera.tilt);
camera.put("zoom", streetViewPanoramaCamera.zoom);
// StreetViewPanoramaOrientation svOrientation = streetViewPanoramaCamera.getOrientation();
// JSONObject orientation = new JSONObject();
// orientation.put("bearing", svOrientation.bearing);
// orientation.put("tilt", svOrientation.tilt);
// camera.put("orientation", orientation);
String jsonStr = camera.toString(0);
jsCallback(
String.format(
Locale.ENGLISH,
"javascript:if('%s' in plugin.google.maps){plugin.google.maps['%s']({evtName:'%s', callback:'_onPanoramaCameraChange', args: [%s]});}",
panoramaId, panoramaId, "panorama_camera_change", jsonStr));
} catch (Exception e) {
// ignore
e.printStackTrace();
}
}
private void jsCallback(final String js) {
this.mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
webView.loadUrl(js);
}
});
}
@Override
public void onStreetViewPanoramaChange(StreetViewPanoramaLocation streetViewPanoramaLocation) {
try {
String jsonStr = "null";
if (streetViewPanoramaLocation != null) {
JSONObject location = new JSONObject();
location.put("panoId", streetViewPanoramaLocation.panoId);
JSONObject position = new JSONObject();
position.put("lat", streetViewPanoramaLocation.position.latitude);
position.put("lng", streetViewPanoramaLocation.position.longitude);
location.put("latLng", position);
JSONArray links = new JSONArray();
for (StreetViewPanoramaLink stLink : streetViewPanoramaLocation.links) {
JSONObject link = new JSONObject();
link.put("panoId", stLink.panoId);
link.put("bearing", stLink.bearing);
links.put(link);
}
location.put("links", links);
jsonStr = location.toString(0);
}
jsCallback(
String.format(
Locale.ENGLISH,
"javascript:if('%s' in plugin.google.maps){plugin.google.maps['%s']({evtName:'%s', callback:'_onPanoramaLocationChange', args: [%s]});}",
panoramaId, panoramaId, "panorama_location_change", jsonStr));
} catch (Exception e) {
// ignore
e.printStackTrace();
}
}
@Override
public void onStreetViewPanoramaClick(StreetViewPanoramaOrientation streetViewPanoramaOrientation) {
try {
JSONObject clickInfo = new JSONObject();
JSONObject orientation = new JSONObject();
orientation.put("bearing", streetViewPanoramaOrientation.bearing);
orientation.put("tilt", streetViewPanoramaOrientation.tilt);
clickInfo.put("orientation", orientation);
Point point = panorama.orientationToPoint(streetViewPanoramaOrientation);
JSONArray pointArray = new JSONArray();
pointArray.put((int)((double)point.x / (double)density));
pointArray.put((int)((double)point.y / (double)density));
clickInfo.put("point", pointArray);
String jsonStr = clickInfo.toString(0);
jsCallback(
String.format(
Locale.ENGLISH,
"javascript:if('%s' in plugin.google.maps){plugin.google.maps['%s']({evtName:'%s', callback:'_onPanoramaEvent', args: [%s]});}",
panoramaId, panoramaId, "panorama_click", jsonStr));
} catch (Exception e) {
// ignore
e.printStackTrace();
}
}
}
@@ -0,0 +1,224 @@
package plugin.google.maps;
import android.content.res.AssetManager;
import android.view.View;
import android.webkit.WebView;
import com.google.android.gms.maps.model.TileOverlay;
import com.google.android.gms.maps.model.TileOverlayOptions;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPreferences;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class PluginTileOverlay extends MyPlugin implements MyPluginInterface {
/**
* Create tile overlay
*
* @param args
* @param callbackContext
* @throws JSONException
*/
public void create(final JSONArray args,
final CallbackContext callbackContext) throws JSONException {
final JSONObject opts = args.getJSONObject(1);
final String id = args.getString(2);
final int tileSize = opts.getInt("tileSize");
final TileOverlayOptions options = new TileOverlayOptions();
if (opts.has("zIndex")) {
options.zIndex((float)opts.getDouble("zIndex"));
}
if (opts.has("visible")) {
options.visible(opts.getBoolean("visible"));
}
if (opts.has("opacity")) {
options.transparency((float)(1 - opts.getDouble("opacity")));
}
boolean isDebug = false;
if (opts.has("debug")) {
isDebug = opts.getBoolean("debug");
}
final boolean _isDebug = isDebug;
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
String userAgent = "";
if (opts.has("userAgent")) {
try {
userAgent = opts.getString("userAgent");
} catch (JSONException e) {
//e.printStackTrace();
}
}
if ("".equals(userAgent)) {
View browserView = webView.getView();
String browserViewName = browserView.getClass().getName();
if ("org.xwalk.core.XWalkView".equals(browserViewName) ||
"org.crosswalk.engine.XWalkCordovaView".equals(browserViewName)) {
CordovaPreferences preferences = webView.getPreferences();
// Set xwalk webview settings by Cordova preferences.
String xwalkUserAgent = preferences == null ? "" : preferences.getString("xwalkUserAgent", "");
if (!xwalkUserAgent.isEmpty()) {
userAgent = xwalkUserAgent;
}
String appendUserAgent = preferences.getString("AppendUserAgent", "");
if (!appendUserAgent.isEmpty()) {
userAgent = userAgent + " " + appendUserAgent;
}
} else {
userAgent = ((WebView) webView.getEngine().getView()).getSettings().getUserAgentString();
}
}
String currentPageUrl = webView.getUrl();
AssetManager assetManager = cordova.getActivity().getAssets();
final PluginTileProvider tileProvider = new PluginTileProvider(pluginMap.getOverlayId(), id, webView, assetManager, currentPageUrl, userAgent, tileSize, _isDebug);
tileProvider.setOnCacheClear(new PluginTileProvider.OnCacheClear() {
@Override
public void onCacheClear(int hashCode) {
TileOverlay tileOverlay = (TileOverlay)PluginTileOverlay.this.getTileOverlay(hashCode+"");
if (tileOverlay != null) {
tileOverlay.clearTileCache();
System.gc();
}
}
});
options.tileProvider(tileProvider);
TileOverlay tileOverlay = map.addTileOverlay(options);
//String id = tileOverlay.getId();
pluginMap.objects.put("tileoverlay_" + id, tileOverlay);
pluginMap.objects.put("tileprovider_" + id, tileProvider);
try {
JSONObject result = new JSONObject();
result.put("hashCode", id);
result.put("__pgmId", "tileoverlay_" + id);
callbackContext.success(result);
} catch (JSONException e) {
e.printStackTrace();
callbackContext.error("" + e.getMessage());
}
}
});
}
@SuppressWarnings("unused")
public void onGetTileUrlFromJS(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
String urlKey = args.getString(1);
String tileUrl = args.getString(2);
String pluginId = "tileprovider_" + id;
if (pluginMap.objects.containsKey(pluginId)) {
((PluginTileProvider)(this.pluginMap.objects.get(pluginId))).onGetTileUrlFromJS(urlKey, tileUrl);
}
callbackContext.success();
}
/**
* set z-index
* @param args
* @param callbackContext
* @throws JSONException
*/
@SuppressWarnings("unused")
public void setZIndex(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
float zIndex = (float) args.getDouble(1);
this.setFloat("setZIndex", id, zIndex, callbackContext);
}
/**
* Set visibility for the object
* @param args
* @param callbackContext
* @throws JSONException
*/
public void setVisible(JSONArray args, CallbackContext callbackContext) throws JSONException {
boolean visible = args.getBoolean(1);
String id = args.getString(0);
this.setBoolean("setVisible", id, visible, callbackContext);
}
/**
* Remove this tile layer
* @param args
* @param callbackContext
* @throws JSONException
*/
public void remove(final JSONArray args, final CallbackContext callbackContext) throws JSONException {
String id = args.getString(0);
TileOverlay tileOverlay = (TileOverlay)pluginMap.objects.get(id);
if (tileOverlay == null) {
callbackContext.success();
return;
}
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
try {
String id = args.getString(0);
TileOverlay tileOverlay = (TileOverlay)pluginMap.objects.remove(id);
if (tileOverlay != null) {
tileOverlay.clearTileCache();
tileOverlay.remove();
tileOverlay = null;
}
id = id.replace("tileoverlay_", "tileprovider_");
if (pluginMap.objects.containsKey(id)) {
((PluginTileProvider)(pluginMap.objects.get(id))).remove();
}
//pluginMap.objects.put(id, null);
PluginTileProvider provider = (PluginTileProvider) pluginMap.objects.remove(id);
provider = null;
callbackContext.success();
} catch (JSONException e) {
e.printStackTrace();
callbackContext.error("" + e.getMessage());
}
}
});
}
/**
* Set fadeIn for the object
* @param args
* @param callbackContext
* @throws JSONException
*/
public void setFadeIn(JSONArray args, CallbackContext callbackContext) throws JSONException {
boolean visible = args.getBoolean(1);
String id = args.getString(0);
this.setBoolean("setFadeIn", id, visible, callbackContext);
}
/**
* Set opacity for the tile layer
* @param args
* @param callbackContext
* @throws JSONException
*/
public void setOpacity(JSONArray args, CallbackContext callbackContext) throws JSONException {
double opacity = 1 - args.getDouble(1);
String id = args.getString(0);
this.setFloat("setTransparency", id, (float)opacity, callbackContext);
}
}
@@ -0,0 +1,440 @@
package plugin.google.maps;
import android.annotation.SuppressLint;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.os.Handler;
import android.os.Looper;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.Log;
import com.google.android.gms.maps.model.Tile;
import com.google.android.gms.maps.model.TileProvider;
import org.apache.cordova.CordovaWebView;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
public class PluginTileProvider implements TileProvider {
private final String TAG = "TileProvider";
private int tileSize = 512;
private Paint tilePaint = new Paint(Paint.FILTER_BITMAP_FLAG);
private Paint debugPaint = null;
private TextPaint debugTextPaint = null;
private String userAgent = null;
private static BitmapCache tileCache = null;
private OnCacheClear listener = null;
private String webPageUrl = null;
private AssetManager assetManager;
private CordovaWebView webView;
private String mapId, pluginId;
private final HashMap<String, String> tileUrlMap = new HashMap<String, String>();
private boolean isDebug = false;
private Handler handler;
private final Object semaphore = new Object();
private Bitmap emptyBitmap = null;
private final HashSet<String> cacheKeys = new HashSet<String>();
private boolean isRemoved = false;
@SuppressLint({"NewApi", "JavascriptInterface"})
public PluginTileProvider(String mapId, String pluginId, CordovaWebView webView, AssetManager assetManager, String webPageUrl, String userAgent, int tileSize, boolean isDebug) {
this.tileSize = tileSize;
//this.tilePaint.setAlpha((int) (opacity * 255));
this.userAgent = userAgent == null ? "Mozilla" : userAgent;
this.webPageUrl = webPageUrl;
this.assetManager = assetManager;
this.webView = webView;
this.mapId = mapId;
this.pluginId = pluginId;
// Get max available VM memory, exceeding this amount will throw an
// OutOfMemory exception. Stored in kilobytes as LruCache takes an
// int in its constructor.
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// Use 1/8th of the available memory for this memory cache.
int cacheSize = maxMemory / 8;
tileCache = new BitmapCache(cacheSize);
this.isDebug = isDebug;
if (isDebug) {
debugPaint = new Paint();
debugPaint.setTextSize(20);
debugPaint.setColor(Color.RED);
debugPaint.setStrokeWidth(1);
debugPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
debugTextPaint = new TextPaint();
debugTextPaint.setTextSize(20);
debugTextPaint.setColor(Color.RED);
debugTextPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
}
handler = new Handler(Looper.getMainLooper());
}
public interface OnCacheClear {
public void onCacheClear(int hashCode);
}
public void onGetTileUrlFromJS(String urlKey, String tileUrl) {
synchronized (tileUrlMap) {
tileUrlMap.put(urlKey, tileUrl);
}
synchronized (semaphore) {
semaphore.notify();
}
}
public void remove() {
isRemoved = true;
synchronized (cacheKeys) {
Iterator<String> iterator = cacheKeys.iterator();
String cacheKey;
Bitmap image;
while(iterator.hasNext()) {
cacheKey = iterator.next();
image = tileCache.remove(cacheKey);
if (image != null && !image.isRecycled()) {
image.recycle();
image = null;
}
}
}
tileCache.evictAll();
}
public void setOnCacheClear(OnCacheClear listener) {
this.listener = listener;
}
@Override
public Tile getTile(int x, int y, int zoom) {
if (isRemoved) {
return null;
}
String urlStr = null;
String originalUrlStr = null;
final String urlKey = String.format(Locale.US, "%s-%s-%d-%d-%d", mapId, pluginId, x, y, zoom);
synchronized (semaphore) {
final String js = String.format(Locale.ENGLISH, "javascript:if(window.cordova){cordova.fireDocumentEvent('%s-%s-tileoverlay', {key: \"%s\", x: %d, y: %d, zoom: %d});}",
mapId, pluginId, urlKey, x, y, zoom);
handler.post(new Runnable() {
@Override
public void run() {
webView.loadUrl(js);
}
});
try {
semaphore.wait(10000); // Maximum wait 10sec
} catch (InterruptedException e) {
e.printStackTrace();
return null;
}
}
synchronized (tileUrlMap) {
urlStr = tileUrlMap.remove(urlKey);
}
originalUrlStr = urlStr;
Tile tile = null;
if (urlStr == null || "(null)".equals(urlStr)) {
if (isDebug) {
if (emptyBitmap == null) {
emptyBitmap = Bitmap.createBitmap(tileSize, tileSize, Config.ARGB_8888);
}
Bitmap dummyBitmap = emptyBitmap.copy(Config.ARGB_8888, true);
drawDebugInfo(dummyBitmap, x, y, zoom, originalUrlStr);
tile = new Tile(tileSize, tileSize, bitmapToByteArray(dummyBitmap));
dummyBitmap.recycle();
return tile;
} else {
return null;
}
}
if (urlStr.indexOf("data:image/") == 0 && urlStr.contains(";base64,")) {
String[] tmp = urlStr.split(",");
Bitmap image = PluginUtil.getBitmapFromBase64encodedImage(tmp[1]);
tile = new Tile(tileSize, tileSize, bitmapToByteArray(image));
image.recycle();
image = null;
return tile;
}
if (urlStr.startsWith("http://localhost") ||
urlStr.startsWith("http://127.0.0.1")) {
if (urlStr.contains("://")) {
urlStr = urlStr.replaceAll("http://.+?/", "file:///android_asset/www/");
} else {
// Avoid WebViewLocalServer (because can not make a connection for some reason)
urlStr = "file:///android_asset/www/".concat(urlStr);
}
}
try {
InputStream inputStream = null;
if (urlStr.startsWith("http://") || urlStr.startsWith("https://")) {
//-------------------------------
// load image from the internet
//-------------------------------
boolean redirect = true;
URL url = new URL(urlStr);
String cacheKey = url.hashCode() + "";
Bitmap cachedImage = tileCache.get(cacheKey);
if (cachedImage != null && !cachedImage.isRecycled()) {
if (isDebug) {
Bitmap copyImage = cachedImage.copy(Config.ARGB_8888, true);
drawDebugInfo(copyImage, x, y, zoom, originalUrlStr);
tile = new Tile(tileSize, tileSize, bitmapToByteArray(copyImage));
copyImage.recycle();
} else {
tile = new Tile(tileSize, tileSize, bitmapToByteArray(cachedImage));
}
//cachedImage.recycle(); // Don't recycle it. Need to keep the bitmap instance
return tile;
}
HttpURLConnection http = null;
String cookies = null;
int redirectCnt = 0;
while(redirect && redirectCnt < 10) {
redirect = false;
http = (HttpURLConnection)url.openConnection();
http.setRequestMethod("GET");
http.setReadTimeout(3000);
http.setConnectTimeout(3000);
if (cookies != null) {
http.setRequestProperty("Cookie", cookies);
}
http.addRequestProperty("Accept-Language", "en-US,en;q=0.8");
http.addRequestProperty("User-Agent", userAgent);
http.setInstanceFollowRedirects(true);
HttpURLConnection.setFollowRedirects(true);
// normally, 3xx is redirect
int status = http.getResponseCode();
if (status != HttpURLConnection.HTTP_OK) {
if (status == HttpURLConnection.HTTP_MOVED_TEMP
|| status == HttpURLConnection.HTTP_MOVED_PERM
|| status == HttpURLConnection.HTTP_SEE_OTHER)
redirect = true;
}
if (redirect) {
// get redirect url from "location" header field
url = new URL(http.getHeaderField("Location"));
// get the cookie if need, for login
cookies = http.getHeaderField("Set-Cookie");
// Disconnect the current connection
http.disconnect();
redirectCnt++;
}
}
if (http != null) {
inputStream = http.getInputStream();
Bitmap image = BitmapFactory.decodeStream(inputStream);
if (image != null) {
if (image.getWidth() != tileSize || image.getHeight() != tileSize) {
Bitmap tileImage = this.resizeForTile(image);
tileCache.put(cacheKey, tileImage.copy(Config.ARGB_8888, true));
if (isDebug) {
drawDebugInfo(tileImage, x, y, zoom, originalUrlStr);
}
tile = new Tile(tileSize, tileSize, bitmapToByteArray(tileImage));
tileImage.recycle();
} else {
tileCache.put(cacheKey, image.copy(Config.ARGB_8888, true));
if (isDebug) {
drawDebugInfo(image, x, y, zoom, originalUrlStr);
}
tile = new Tile(tileSize, tileSize, bitmapToByteArray(image));
}
cacheKeys.add(cacheKey);
image.recycle();
}
http.disconnect();
}
inputStream.close();
} else {
//---------------------------------
// load image from the local path
//---------------------------------
if (!urlStr.contains("://") &&
!urlStr.startsWith("/") &&
!urlStr.startsWith("www/") &&
!urlStr.startsWith("./") &&
!urlStr.startsWith("../")) {
urlStr = "./" + urlStr;
}
if (urlStr.startsWith("./") || urlStr.startsWith("../")) {
urlStr = urlStr.replace("././", "./");
String currentPage = webPageUrl;
currentPage = currentPage.replaceAll("#.*$", "");
currentPage = currentPage.replaceAll("\\?.*$", "");
currentPage = currentPage.replaceAll("[^\\/]*$", "");
urlStr = currentPage + "/" + urlStr;
}
String cacheKey = new File(urlStr).hashCode() + "";
Bitmap cachedImage = tileCache.get(cacheKey);
if (cachedImage != null && !cachedImage.isRecycled()) {
if (isDebug) {
Bitmap copyImage = cachedImage.copy(Config.ARGB_8888, true);
drawDebugInfo(copyImage, x, y, zoom, originalUrlStr);
cachedImage.recycle();
tile = new Tile(tileSize, tileSize, bitmapToByteArray(copyImage));
copyImage.recycle();
} else {
tile = new Tile(tileSize, tileSize, bitmapToByteArray(cachedImage));
}
//cachedImage.recycle(); // Don't recycle it. Need to keep the bitmap instance
return tile;
}
Bitmap image = null;
if (urlStr.indexOf("file://") == 0 &&
!urlStr.contains("file:///android_asset/")) {
urlStr = urlStr.replace("file://", "");
File tmp = new File(urlStr);
if (tmp.exists()) {
image = BitmapFactory.decodeFile(urlStr);
} else {
//Log.w("PluginTileProvider", "image is not found (" + urlStr + ")");
return null;
}
} else {
//Log.d(TAG, "imgUrl = " + imgUrl);
if (urlStr.indexOf("file:///android_asset/") == 0) {
urlStr = urlStr.replace("file:///android_asset/", "");
}
if (urlStr.contains("./")) {
try {
boolean isAbsolutePath = urlStr.startsWith("/");
File relativePath = new File(urlStr);
urlStr = relativePath.getCanonicalPath();
//Log.d(TAG, "imgUrl = " + imgUrl);
if (!isAbsolutePath) {
urlStr = urlStr.substring(1);
}
//Log.d(TAG, "imgUrl = " + imgUrl);
} catch (Exception e) {
//e.printStackTrace();
}
}
//Log.d("PluginTileProvider", "urlStr = " + urlStr);
try {
inputStream = assetManager.open(urlStr);
image = BitmapFactory.decodeStream(inputStream);
Bitmap tmpImage = image.copy(Config.ARGB_8888, true);
image.recycle();
image = tmpImage;
} catch (IOException e) {
//e.printStackTrace();
return null;
}
}
if (image != null) {
if (image.getWidth() != tileSize || image.getHeight() != tileSize) {
Bitmap tileImage = this.resizeForTile(image);
tileCache.put(cacheKey, tileImage.copy(Config.ARGB_8888, true));
if (isDebug) {
drawDebugInfo(tileImage, x, y, zoom, originalUrlStr);
}
tile = new Tile(tileSize, tileSize, bitmapToByteArray(tileImage));
tileImage.recycle();
} else {
tileCache.put(cacheKey, image.copy(Config.ARGB_8888, true));
if (isDebug) {
drawDebugInfo(image, x, y, zoom, originalUrlStr);
}
tile = new Tile(tileSize, tileSize, bitmapToByteArray(image));
}
image.recycle();
cacheKeys.add(cacheKey);
}
}
return tile;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private byte[] bitmapToByteArray(Bitmap bitmap) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.WEBP, 99, outputStream);
return outputStream.toByteArray();
}
private void drawDebugInfo(Bitmap bitmap, int x, int y, int zoom, String url) {
Canvas canvas = new Canvas(bitmap);
canvas.drawLine(0, 0, tileSize, 0, debugPaint);
canvas.drawLine(0, 0, 0, tileSize, debugPaint);
canvas.drawText(String.format(Locale.US, "x = %d, y = %d, zoom = %d", x, y, zoom), 30, 30, debugPaint);
if (url != null) {
StaticLayout mTextLayout = new StaticLayout(url, debugTextPaint,
tileSize * 4 / 5, Layout.Alignment.ALIGN_NORMAL,
1.0f, 0.0f, false);
canvas.save();
canvas.translate(30, 60);
mTextLayout.draw(canvas);
canvas.restore();
}
}
private Bitmap resizeForTile(Bitmap bitmap) {
if (bitmap == null) {
return null;
}
/**
* http://stackoverflow.com/questions/4821488/bad-image-quality-after-resizing-scaling-bitmap#7468636
*/
Bitmap scaledBitmap = Bitmap.createBitmap(tileSize, tileSize, Config.ARGB_8888);
float ratioX = tileSize / (float) bitmap.getWidth();
float ratioY = tileSize / (float) bitmap.getHeight();
float middleX = tileSize / 2.0f;
float middleY = tileSize / 2.0f;
Matrix scaleMatrix = new Matrix();
scaleMatrix.setScale(ratioX, ratioY, middleX, middleY);
Canvas canvas = new Canvas(scaledBitmap);
canvas.setMatrix(scaleMatrix);
canvas.drawBitmap(bitmap, middleX - bitmap.getWidth() / 2, middleY - bitmap.getHeight() / 2, tilePaint);
bitmap.recycle();
return scaledBitmap;
}
}
@@ -0,0 +1,435 @@
package plugin.google.maps;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.location.Location;
import android.net.Uri;
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Bundle;
import android.util.Base64;
import com.google.android.gms.maps.model.IndoorBuilding;
import com.google.android.gms.maps.model.IndoorLevel;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.LatLngBounds.Builder;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaResourceApi;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class PluginUtil {
// Get resource id
// http://stackoverflow.com/a/37840674
public static int getAppResource(Activity activity, String name, String type) {
return activity.getResources().getIdentifier(name, type, activity.getPackageName());
}
public static abstract class MyCallbackContext extends CallbackContext {
public MyCallbackContext(String callbackId, CordovaWebView webView) {
super(callbackId, webView);
}
@Override
public void sendPluginResult(PluginResult pluginResult) {
this.onResult(pluginResult);
}
abstract public void onResult(PluginResult pluginResult);
}
public static boolean isNumeric(String str)
{
for (char c : str.toCharArray()) {
if (!Character.isDigit(c)) {
return false;
}
}
return true;
}
public static LatLngBounds getBoundsFromCircle(LatLng center, double radius) {
double d2r = Math.PI / 180; // degrees to radians
double r2d = 180 / Math.PI; // radians to degrees
double earthsradius = 3963.189; // 3963 is the radius of the earth in miles
radius *= 0.000621371192; // convert to mile
// find the raidus in lat/lon
double rlat = (radius / earthsradius) * r2d;
double rlng = rlat / Math.cos(center.latitude * d2r);
LatLngBounds.Builder builder = new LatLngBounds.Builder();
double ex, ey;
for (int i = 0; i < 360; i+=90) {
ey = center.longitude + (rlng * Math.cos(i * d2r)); // center a + radius x * cos(theta)
ex = center.latitude + (rlat * Math.sin(i * d2r)); // center b + radius y * sin(theta)
builder.include(new LatLng(ex, ey));
}
return builder.build();
}
public static LatLngBounds getBoundsFromPath(List<LatLng> path) {
LatLngBounds.Builder builder = new LatLngBounds.Builder();
for (LatLng aPath : path) {
builder.include(aPath);
}
return builder.build();
}
public static String getAbsolutePathFromCDVFilePath(CordovaResourceApi resourceApi, String cdvFilePath) {
if (cdvFilePath.indexOf("cdvfile://") != 0) {
return null;
}
//CordovaResourceApi resourceApi = webView.getResourceApi();
Uri fileURL = resourceApi.remapUri(Uri.parse(cdvFilePath));
File file = resourceApi.mapUriToFile(fileURL);
if (file == null) {
return null;
}
try {
return file.getCanonicalPath();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public static JSONObject location2Json(Location location) throws JSONException {
JSONObject latLng = new JSONObject();
latLng.put("lat", location.getLatitude());
latLng.put("lng", location.getLongitude());
JSONObject params = new JSONObject();
params.put("latLng", latLng);
if (VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
params.put("elapsedRealtimeNanos", location.getElapsedRealtimeNanos());
} else {
params.put("elapsedRealtimeNanos", 0);
}
params.put("time", location.getTime());
/*
SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
Date date = new Date(location.getTime());
params.put("timeFormatted", format.format(date));
*/
if (location.hasAccuracy()) {
params.put("accuracy", location.getAccuracy());
}
if (location.hasBearing()) {
params.put("bearing", location.getBearing());
}
if (location.hasAltitude()) {
params.put("altitude", location.getAltitude());
}
if (location.hasSpeed()) {
params.put("speed", location.getSpeed());
}
params.put("provider", location.getProvider());
params.put("hashCode", location.hashCode());
return params;
}
/**
* return color integer value
* @param arrayRGBA
* @throws JSONException
*/
public static int parsePluginColor(JSONArray arrayRGBA) throws JSONException {
return Color.argb(arrayRGBA.getInt(3), arrayRGBA.getInt(0), arrayRGBA.getInt(1), arrayRGBA.getInt(2));
}
public static ArrayList<LatLng> JSONArray2LatLngList(JSONArray points) throws JSONException {
ArrayList<LatLng> path = new ArrayList<LatLng>();
JSONObject pointJSON;
int i = 0;
for (i = 0; i < points.length(); i++) {
pointJSON = points.getJSONObject(i);
path.add(new LatLng(pointJSON.getDouble("lat"), pointJSON.getDouble("lng")));
}
return path;
}
/*
public static Set<LatLng> JSONArray2LatLngHash(JSONArray points) throws JSONException {
Set<LatLng> path = new HashSet<LatLng>();
JSONObject pointJSON;
int i = 0;
for (i = 0; i < points.length(); i++) {
pointJSON = points.getJSONObject(i);
path.add(new LatLng(pointJSON.getDouble("lat"), pointJSON.getDouble("lng")));
}
return path;
}
*/
public static LatLngBounds JSONArray2LatLngBounds(JSONArray points) throws JSONException {
List<LatLng> path = JSONArray2LatLngList(points);
Builder builder = LatLngBounds.builder();
int i = 0;
for (i = 0; i < path.size(); i++) {
builder.include(path.get(i));
}
return builder.build();
}
public static Bundle Json2Bundle(JSONObject json) {
Bundle mBundle = new Bundle();
@SuppressWarnings("unchecked")
Iterator<String> iter = json.keys();
Object value;
while (iter.hasNext()) {
String key = iter.next();
try {
value = json.get(key);
if (Boolean.class.isInstance(value)) {
mBundle.putBoolean(key, (Boolean)value);
} else if (Integer.class.isInstance(value)) {
mBundle.putInt(key, (Integer)value);
} else if (Long.class.isInstance(value)) {
mBundle.putLong(key, (Long)value);
} else if (Double.class.isInstance(value)) {
mBundle.putDouble(key, (Double)value);
} else if (JSONObject.class.isInstance(value)) {
mBundle.putBundle(key, Json2Bundle((JSONObject)value));
} else if (JSONArray.class.isInstance(value)) {
JSONArray values = (JSONArray)value;
ArrayList<String> strings = new ArrayList<String>();
for (int i = 0; i < values.length(); i++) {
strings.add(values.get(i) + "");
}
mBundle.putStringArrayList(key, strings);
} else {
mBundle.putString(key, json.getString(key));
}
} catch (JSONException e) {
e.printStackTrace();
}
}
return mBundle;
}
public static Bitmap resizeBitmap(Bitmap bitmap, int newWidth, int newHeight) {
if (bitmap == null) {
return null;
}
/**
* http://stackoverflow.com/questions/4821488/bad-image-quality-after-resizing-scaling-bitmap#7468636
*/
Bitmap scaledBitmap = Bitmap.createBitmap(newWidth, newHeight, Config.ARGB_8888);
float ratioX = newWidth / (float) bitmap.getWidth();
float ratioY = newHeight / (float) bitmap.getHeight();
float middleX = newWidth / 2.0f;
float middleY = newHeight / 2.0f;
Matrix scaleMatrix = new Matrix();
scaleMatrix.setScale(ratioX, ratioY, middleX, middleY);
Canvas canvas = new Canvas(scaledBitmap);
canvas.setMatrix(scaleMatrix);
canvas.drawBitmap(bitmap, middleX - bitmap.getWidth() / 2, middleY - bitmap.getHeight() / 2, new Paint(Paint.FILTER_BITMAP_FLAG));
bitmap.recycle();
return scaledBitmap;
}
public static Bitmap scaleBitmapForDevice(Bitmap bitmap) {
if (bitmap == null) {
return null;
}
float density = Resources.getSystem().getDisplayMetrics().density;
int newWidth = (int)(bitmap.getWidth() * density);
int newHeight = (int)(bitmap.getHeight() * density);
/*
Bitmap resizeBitmap = Bitmap.createScaledBitmap(bitmap, width, height, true);
*/
/**
* http://stackoverflow.com/questions/4821488/bad-image-quality-after-resizing-scaling-bitmap#7468636
*/
Bitmap scaledBitmap = Bitmap.createBitmap(newWidth, newHeight, Config.ARGB_8888);
float ratioX = newWidth / (float) bitmap.getWidth();
float ratioY = newHeight / (float) bitmap.getHeight();
float middleX = newWidth / 2.0f;
float middleY = newHeight / 2.0f;
Matrix scaleMatrix = new Matrix();
scaleMatrix.setScale(ratioX, ratioY, middleX, middleY);
Canvas canvas = new Canvas(scaledBitmap);
canvas.setMatrix(scaleMatrix);
canvas.drawBitmap(bitmap, middleX - bitmap.getWidth() / 2, middleY - bitmap.getHeight() / 2, new Paint(Paint.FILTER_BITMAP_FLAG));
bitmap.recycle();
return scaledBitmap;
}
public static Bitmap getBitmapFromBase64encodedImage(String base64EncodedImage) {
byte[] byteArray= Base64.decode(base64EncodedImage, Base64.DEFAULT);
Bitmap image= null;
try {
image = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length);
} catch (Exception e) {
e.printStackTrace();
}
return image;
}
public static JSONObject Bundle2Json(Bundle bundle) {
JSONObject json = new JSONObject();
Set<String> keys = bundle.keySet();
Iterator<String> iter = keys.iterator();
while (iter.hasNext()) {
String key = iter.next();
try {
Object value = bundle.get(key);
if (Bundle.class.isInstance(value)) {
value = Bundle2Json((Bundle)value);
}
if (value.getClass().isArray()) {
JSONArray values = new JSONArray();
Object[] objects = (Object[])value;
int i = 0;
for (i = 0; i < objects.length; i++) {
if (Bundle.class.isInstance(objects[i])) {
objects[i] = Bundle2Json((Bundle)objects[i]);
}
values.put(objects[i]);
}
json.put(key, values);
} else if (value.getClass() == ArrayList.class) {
JSONArray values = new JSONArray();
Iterator<?> listIterator = ((ArrayList<?>)value).iterator();
while(listIterator.hasNext()) {
value = listIterator.next();
if (Bundle.class.isInstance(value)) {
value = Bundle2Json((Bundle)value);
}
values.put(value);
}
json.put(key, values);
} else {
json.put(key, value);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
return json;
}
public static LatLngBounds convertToLatLngBounds(List<LatLng> points) {
LatLngBounds.Builder latLngBuilder = LatLngBounds.builder();
Iterator<LatLng> iterator = points.listIterator();
while (iterator.hasNext()) {
latLngBuilder.include(iterator.next());
}
return latLngBuilder.build();
}
public static JSONObject convertIndoorBuildingToJson(IndoorBuilding indoorBuilding) {
if (indoorBuilding == null) {
return null;
}
JSONObject result = new JSONObject();
try {
JSONArray levels = new JSONArray();
for(IndoorLevel level : indoorBuilding.getLevels()){
JSONObject levelInfo = new JSONObject();
levelInfo.put("name",level.getName());
// TODO Auto-generated catch block
levelInfo.put("shortName",level.getShortName());
levels.put(levelInfo);
}
result.put("activeLevelIndex",indoorBuilding.getActiveLevelIndex());
result.put("defaultLevelIndex",indoorBuilding.getDefaultLevelIndex());
result.put("levels",levels);
result.put("underground",indoorBuilding.isUnderground());
} catch (JSONException e) {
e.printStackTrace();
return null;
}
return result;
}
public static ArrayList<File> unpackZipFromBytes(InputStream zipped, String dstPath)
{
ArrayList<File> files = new ArrayList<File>();
try {
String filename;
ZipInputStream zipInputStream = new ZipInputStream(new BufferedInputStream(zipped));
ZipEntry zipEntry;
byte[] buffer = new byte[1024];
int count;
while ((zipEntry = zipInputStream.getNextEntry()) != null)
{
filename = zipEntry.getName();
if (zipEntry.isDirectory()) {
File directory = new File(dstPath + "/" + filename);
files.add(directory);
directory.mkdirs();
continue;
}
files.add(new File(dstPath + "/" + filename));
FileOutputStream fileOutputStream = new FileOutputStream(dstPath + "/" + filename);
while ((count = zipInputStream.read(buffer)) != -1) {
fileOutputStream.write(buffer, 0, count);
}
fileOutputStream.close();
zipInputStream.closeEntry();
}
zipInputStream.close();
zipped.close();
} catch(IOException e) {
e.printStackTrace();
}
return files;
}
public static String getPgmStrings(Activity activity, String key) {
int resId = PluginUtil.getAppResource(activity, key, "string");
Resources res = activity.getResources();
return res.getString(resId);
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<!-- Drop Shadow Stack -->
<item>
<shape>
<padding
android:bottom="1dp"
android:left="1dp"
android:right="1dp"
android:top="1dp" />
<solid android:color="#00CCCCCC" />
<corners android:radius="1dp" />
</shape>
</item>
<item>
<shape>
<padding
android:bottom="1dp"
android:left="1dp"
android:right="1dp"
android:top="1dp" />
<solid android:color="#10CCCCCC" />
<corners android:radius="1dp" />
</shape>
</item>
<item>
<shape>
<padding
android:bottom="1dp"
android:left="1dp"
android:right="1dp"
android:top="1dp" />
<solid android:color="#20CCCCCC" />
<corners android:radius="1dp" />
</shape>
</item>
<item>
<shape>
<padding
android:bottom="1dp"
android:left="1dp"
android:right="1dp"
android:top="1dp" />
<solid android:color="#30CCCCCC" />
<corners android:radius="1dp" />
</shape>
</item>
<item>
<shape>
<padding
android:bottom="1dp"
android:left="1dp"
android:right="1dp"
android:top="1dp" />
<solid android:color="#50CCCCCC" />
<corners android:radius="1dp" />
</shape>
</item>
<!-- Background -->
<item>
<shape>
<solid android:color="#FCFFFFFF" />
<corners android:radius="1dp" />
</shape>
</item>
</layer-list>
@@ -0,0 +1,11 @@
<?xml version='1.0' encoding='utf-8'?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<View
android:visibility="invisible"
android:layout_width="1dp"
android:layout_height="1dp" />
</LinearLayout>
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pgm_google_close_button">أغلق</string>
<string name="pgm_api_key_error">مع مفتاح API الخاص بك \"GOOGLE_MAPS_ANDROID_API_KEY\" يرجى استبدال config.xml في منصات </string>
<string name="pgm_google_play_error">غير متوفر لسبب ما على هذا الجهاز Google Maps Android API v2 </string>
<string name="pgm_google_play_developer_error">تم تكوين التطبيق بشكل خاطئ. هذا الخطأ غير قابل للاسترداد وسيتم التعامل معه على أنه مميت. يجب على مطور البرامج النظر في السجلات بعد ذلك لتحديد مزيد من المعلومات القابلة للتنفيذ</string>
<string name="pgm_google_play_internal_error">يرجى إعادة المحاولة ، ويجب أن تحل المشكلة ، Google Play حدث خطأ داخلي في خدمات </string>
<string name="pgm_google_play_invalid_account">لقد حاولت الاتصال بالخدمة باسم حساب غير صالح</string>
<string name="pgm_google_play_license_check_failed">.التطبيق غير مرخص للمستخدم. هذا الخطأ غير قابل للاسترداد وسيتم التعامل معه على أنه مميت</string>
<string name="pgm_google_play_network_error">.حدث خطأ في الشبكة. الرجاء المحاولة مرة أخرى ، ويجب أن تحل المشكلة</string>
<string name="pgm_google_play_service_disabled"> Google Play على هذا الجهاز. الرجاء تشغيل خدمات Google Play تم تعطيل الإصدار المثبت من خدمات</string>
<string name="pgm_google_play_service_invalid">Google play من متجر Google Play المثبتة على هذا الجهاز غير أصلي. يرجى تحديث خدمات Google Play إصدار خدمات</string>
<string name="pgm_google_play_service_missing">Google Play مفقودة على هذا الجهاز. الرجاء تثبيت خدمات Google Play خدمات </string>
<string name="pgm_google_play_service_update_required">Google Play من متجر Google Play قديم. يرجى تحديث خدمات Google Play الإصدار المثبت من خدمات </string>
<string name="pgm_google_play_sign_in_required"> Google Play لقد حاولت الاتصال بالخدمة ولكنك لم تسجل الدخول. يرجى التحقق من تكوين خدمات </string>
<string name="pgm_no_location_providers">. نظرًا لأن هذا الجهاز لا يحتوي على أي موفر للموقع ، لا يمكن لهذا التطبيق اكتشاف موقعك</string>
<string name="pgm_no_location_service_is_disabled">تم تعطيل خدمة الموقع</string>
<string name="pgm_location_rejected_by_user">خدمة الموقع مرفوضة من قبل المستخدم</string>
<string name="pgm_can_not_get_location"> .لا يمكن الكشف عن موقعك. حاول ثانية </string>
</resources>
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pgm_google_close_button">Luk</string>
<string name="pgm_api_key_error">Vær sød at ændre \'GOOGLE_MAPS_ANDROID_API_KEY\' i config.xml med din egen API-nøgle!</string>
<string name="pgm_google_play_error">Google Maps Android API v2 er, af en eller anden grund, ikke tilgængelig på denne enhed.</string>
<string name="pgm_google_play_developer_error">Applikationen er fejlkonfigureret. Denne fejl er ikke genoprettelig og vil blive betragtet som fatal. Udvikleren bør kigge i logs efter dette for at finde mere handlingsrettet information.</string>
<string name="pgm_google_play_internal_error">En intern fejl skete med Google Play Services. Vær sød at prøve igen, og så burde det løse problemet.</string>
<string name="pgm_google_play_invalid_account">Du prøvede at tilslutte til servicen med et invalidt kontonavn specificeret</string>
<string name="pgm_google_play_license_check_failed">Applikationen er ikke licenseret til brugeren. Denne fejl er ikke genoprettelig og vil blive betragtet som fatal.</string>
<string name="pgm_google_play_network_error">Der skete en netværksfejl. Vær sød at prøve igen, og så burde det løse problemet.</string>
<string name="pgm_google_play_service_disabled">Den installeret version af Google Play Services er blevet has been deaktiveret på denne enhed. Vær sød at tænde for Google Play Services.</string>
<string name="pgm_google_play_service_invalid">Versionen for Google Play Services installeret på denne enhed er ikke autentisk. Vær sød at opdatere Google Play Services fra Google Play Store.</string>
<string name="pgm_google_play_service_missing">Google Play services mangler på denne enhed. Vær sød at installere Google Play Services.</string>
<string name="pgm_google_play_service_update_required">Den installeret version af Google Play services er forældet. Vær sød at opdatere Google Play Services fra Google Play Store.</string>
<string name="pgm_google_play_sign_in_required">Du prøvede at tilslutte til servicen, men du er ikke logget ind. Vær sød at tjekke Google Play Services konfigurationen</string>
<string name="pgm_no_location_providers">Siden denne enhed ikke har en lokationsudbyder kan denne app ikke finde din lokation</string>
<string name="pgm_no_location_service_is_disabled">Lokationsservice er deaktiveret.</string>
<string name="pgm_location_rejected_by_user">Lokationsservice blev afvist af bruger.</string>
<string name="pgm_can_not_get_location">Kan ikke finde din lokation. Prøv igen.</string>
</resources>
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pgm_google_close_button">Schließen</string>
<string name="pgm_api_key_error">Bitte ersetzen Sie \'GOOGLE_MAPS_ANDROID_API_KEY\' unter config.xml durch Ihren API Key!</string>
<string name="pgm_google_play_error">Google Maps Android API v2 ist auf diesem Gerät aus einem unbekannten Grund nicht verfügbar.</string>
<string name="pgm_google_play_developer_error">Die Anwendung ist falsch konfiguriert und kann nicht fortgefahren werden. Der Entwickler muss die Protokolle inspezieren, um weitere Informationen zu erhalten.</string>
<string name="pgm_google_play_internal_error">Ein interner Fehler der Google Play-Dienste ist aufgetreten. Bitte versuchen Sie es erneut.</string>
<string name="pgm_google_play_invalid_account">Sie haben versucht, eine Verbindung zum Dienst mit einem ungültigen Kontonamen herzustellen.</string>
<string name="pgm_google_play_license_check_failed">Die Anwendung ist für den Benutzer nicht lizenziert. Dieser Fehler ist nicht wiederherstellbar und wird als fatal behandelt.</string>
<string name="pgm_google_play_network_error">Ein Netzwerkfehler ist aufgetreten. Bitte stellen Sie eine Internetverbindung her und versuchen Sie es erneut.</string>
<string name="pgm_google_play_service_disabled">Die installierte Version der Google Play-Dienste wurde auf diesem Gerät deaktiviert. Bitte schalten Sie die Google Play-Dienste ein.</string>
<string name="pgm_google_play_service_invalid">Die Version der auf diesem Gerät installierten Google Play-Dienste konnte nicht authentifiziert werden. Bitte Aktualisieren Sie die Google Play-Dienste im Google Play Store.</string>
<string name="pgm_google_play_service_missing">Google Play-Dienste fehlen auf diesem Gerät. Bitte installieren Sie die Google Play-Dienste über den Play Store.
</string>
<string name="pgm_google_play_service_update_required">Die installierte Version der Google Play-Dienste ist veraltet. Aktualisieren Sie die Google Play-Dienste im Google Play Store.</string>
<string name="pgm_google_play_sign_in_required">Sie haben versucht, eine Verbindung mit dem Dienst herzustellen, aber Sie sind nicht angemeldet. Überprüfen Sie die Konfiguration der Google Play-Dienste</string>
<string name="pgm_no_location_providers">Da dieses Gerät keinen Standortanbieter hat, kann diese App Ihren Standort nicht orten.</string>
<string name="pgm_no_location_service_is_disabled">Der Standortdienst ist deaktiviert.</string>
<string name="pgm_location_rejected_by_user">Der Standortdienst wird vom Benutzer abgelehnt.</string>
<string name="pgm_can_not_get_location">Sie konnten nicht lokalisiert werden. Versuchen Sie es bitte erneut.</string>
</resources>
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pgm_google_close_button">Cerrar</string>
<string name="pgm_api_key_error">Por favor reemplace \'GOOGLE_MAPS_ANDROID_API_KEY\' en el archivo config.xml por su API Key!</string>
<string name="pgm_google_play_error">Por alguna razón Google Maps Android API v2 no está disponible en este dispositivo.</string>
<string name="pgm_google_play_developer_error">La aplicación no está configurada correctamente. Ya que no es posible recuperarse de este error, será tratado como fatal. El desarrollador debe revisar los registros para determinar si existe alguna acción por realizar.</string>
<string name="pgm_google_play_internal_error">Ocurrió un error interno en Google Play Services. Por favor inténtalo de nuevo, y el problema debería resolverse.</string>
<string name="pgm_google_play_invalid_account">Intentaste conectarte al servicio especificando un nombre de cuenta invalido.</string>
<string name="pgm_google_play_license_check_failed">La aplicación no tiene licencia para el usuario. Ya que no es posible recuperarse de este error, será tratado como fatal.</string>
<string name="pgm_google_play_network_error">Ocurrió un error de red. Por favor inténtalo de nuevo, y el problema debería resolverse.</string>
<string name="pgm_google_play_service_disabled"> La versión instalada de Google Play Services ha sido deshabilitada en este dispositivo. Por favor encienda Google Play Services.</string>
<string name="pgm_google_play_service_invalid">La versión instalada de Google Play Services no es autentica. Por favor actualice Google Play Services desde Google Play Store.</string>
<string name="pgm_google_play_service_missing">No se encontró Google Play Services en el dispositivo. Por favor instale Google Play Services.</string>
<string name="pgm_google_play_service_update_required">La versión instalada de Google Play services está desactualizada. Por favor actualice Google Play Services desde Google Play Store.</string>
<string name="pgm_google_play_sign_in_required">Intentaste conectarte a los servicios pero no has iniciado sesión. Por favor revisa la configuración de Google Play Services</string>
<string name="pgm_no_location_providers">En vista de que este dispositivo no tiene ningún proveedor de localización, la aplicación no puede detectar tu ubicación.</string>
<string name="pgm_no_location_service_is_disabled">Servicio de localización desactivado.</string>
<string name="pgm_location_rejected_by_user">El servicio de localización fue rechazado por el usuario.</string>
<string name="pgm_can_not_get_location">No se pudo detectar tu ubicación. Inténtalo de nuevo.</string>
</resources>
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pgm_google_close_button">Fermer</string>
<string name="pgm_api_key_error">Veuillez remplacer \"GOOGLE_MAPS_ANDROID_API_KEY\" dans le fichier config.xml avec votre propre clé API!</string>
<string name="pgm_google_play_error">Google Maps Android API v2 n\'est pas disponible sur cet appareil pour une raison non identifiée.</string>
<string name="pgm_google_play_developer_error">L\'application a mal été configurée. Cette erreur n\'est pas récupérable et sera traitée comme étant fatale. Le développeur devrait inspecter le journal d\'erreur afin de trouver des informations plus précises.</string>
<string name="pgm_google_play_internal_error">Une erreur interne est intervenue au niveau du Google Play Services. Veuillez réessayer, cela devrait résoudre le problème.</string>
<string name="pgm_google_play_invalid_account">Vous avez tenté de vous connecter au service avec un nom de compte invalide.</string>
<string name="pgm_google_play_license_check_failed">L\'application ne contient pas de licence pour cet utilisateur. Cette erreur n\'est pas récupérable et sera traitée comme étant fatale.</string>
<string name="pgm_google_play_network_error">Un problème de réseau est intervenu. Veuillez réessayer, cela devrait résoudre le problème.</string>
<string name="pgm_google_play_service_disabled">La version du Google Play Services installée sur cet appareil a été désactivée. Veuillez activer le Google Play Services.</string>
<string name="pgm_google_play_service_invalid">La version du Google Play Services installée sur cet appareil n\'est pas authentique. Merci de mettre à jour le Google Play Services à partir du Google Play Store.</string>
<string name="pgm_google_play_service_missing">Google Play Services n\'est pas installé sur cet appareil. Merci d\'installer le Google Play Services.</string>
<string name="pgm_google_play_service_update_required">La version du Google Play Services installée sur cet appareil a expirée. Veuillez mettre à jour le Google Play Services à partir du Google Play Store.</string>
<string name="pgm_google_play_sign_in_required">Vous avez tenté de vous connecter au service, mais vous n\'est pas authentifié. Merci de vérifier la configuration du Google Play Services.</string>
<string name="pgm_no_location_providers">Cet appareil n\'est pas équipé d\'un service de localisation, cette application ne peut donc pas récupérer votre position.</string>
<string name="pgm_no_location_service_is_disabled">Le service de localisation est désactivé.</string>
<string name="pgm_location_rejected_by_user">Le service de localisation est rejeté par l\'utilisateur.</string>
<string name="pgm_can_not_get_location">Localisation introuvable. Veuillez réessayer.</string>
</resources>
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pgm_google_close_button">बंद</string>
<string name="pgm_api_key_error">कृपया config.xml मे \'GOOGLE_MAPS_ANDROID_API_KEY\' को अपनी एपीआई कुंजी से बदलें</string>
<string name="pgm_google_play_error">इस डिवाइस पर Google Maps Android API v2 उपलब्ध नहीं है</string>
<string name="pgm_google_play_developer_error">यह एप्लिकेशन गलत तरीके से कॉन्फ़िगर किया गया है. इस त्रुटि को ठीक किया नहीं जासकता इसे घातक माना जायेगा. डेवलपर को चाहिए वे लोग में देखकर कार्रवाई योग्य प्राप्त करे.</string>
<string name="pgm_google_play_internal_error">Google Play सेवाओं की एक आंतरिक त्रुटि हुई. कृप्या फिरसे प्रयास करें, इस से समस्या हल होनी चाहिए.</string>
<string name="pgm_google_play_invalid_account">आप ने एक अमान्य खाता नाम के साथ सेवा से जुड़ने का प्रयास है.</string>
<string name="pgm_google_play_license_check_failed">इस उपयोगकर्ता के पास इस एप्लीकेशन लाइसेंस नहीं है. इस त्रुटि को ठीक किया नहीं जासकता इसे घातक माना जायेग.</string>
<string name="pgm_google_play_network_error">एक नेटवर्क त्रुटि हुई. कृप्या फिरसे प्रयास करें, इस से समस्या हल होनी चाहिए.</string>
<string name="pgm_google_play_service_disabled">इस डिवाइस पर Google Play सेवाओं का स्थापित संस्करण अक्षम है. कृपया Google Play सेवाएं चालू करें.</string>
<string name="pgm_google_play_service_invalid">इस डिवाइस पर स्थापित Google Play सेवाओं का संस्करण प्रामाणिक नहीं है. कृपया Google Play Store से Google Play सेवाएं अपडेट करें.</string>
<string name="pgm_google_play_service_missing">इस डिवाइस पर Google Play सेवाएं मौजूद नहीं हैं. कृपया Google Play सेवाएं स्थापित करें.</string>
<string name="pgm_google_play_service_update_required">Google Play सेवाओं का स्थापित संस्करण पुराना है. कृपया Google Play Store से Google Play सेवाएं अपडेट करें.</string>
<string name="pgm_google_play_sign_in_required">आप ने साइन किये बिना इस सेवा से जुड़ने का प्रयास किया है. कृपया Google Play सेवाएं कॉन्फ़िगरेशन जांचें</string>
<string name="pgm_no_location_providers">इस डिवाइस में कोई स्थान प्रदाता नहीं है, इसके कारण यह ऐप आपके स्थान का पता नहीं लगा सकता.</string>
<string name="pgm_no_location_service_is_disabled">स्थान सेवा अक्षम है.</string>
<string name="pgm_location_rejected_by_user">उपयोगकर्ता ने स्थान सेवा अस्वीकार कर दिया है.</string>
<string name="pgm_can_not_get_location">आप के स्थान का पता नही चल रहा है. कृप्या फिरसे प्रयास करें.</string>
</resources>
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pgm_google_close_button">Tutup</string>
<string name="pgm_api_key_error">Silahkan ganti \'GOOGLE_MAPS_ANDROID_API_KEY\' di config.xml dengan Key API anda!</string>
<string name="pgm_google_play_error">Google Maps Android API v2 tidak dapat digunakan pada perangkat ini.</string>
<string name="pgm_google_play_developer_error">Pengaturan applikasi ini salah. Kesalahan ini tidak dapat diperbaharui dan dianggap FATAL. Developer harus memeriksa informasi logs setelah ini untuk memastikan tidandakan.</string>
<string name="pgm_google_play_internal_error">terdapat kesalahan pada Google Play Services. Silahkan Coba lagi, untuk memperbaiki masalah.</string>
<string name="pgm_google_play_invalid_account">Anda mencoba menghubungi service dengan nama akun yang tidak sesuai.</string>
<string name="pgm_google_play_license_check_failed">Appilkasi ini tidak diberikan licensi kepada pengguna. Error ini tidak dapat diperbaharui dan dianggap FATAL.</string>
<string name="pgm_google_play_network_error">Kesalahan pada Jaringan. Silahkan Coba lagi,untuk memperbaiki masalah.</string>
<string name="pgm_google_play_service_disabled">versi Google Play yang di install telah di non aktifkan pada perangkat ini. Silahkan Aktifkan Google Play Services.</string>
<string name="pgm_google_play_service_invalid">Versi Google Play services yang di install pada perangkat ini tidak asli. Silahkan perbaharui Google Play Services melalui Google Play Store.</string>
<string name="pgm_google_play_service_missing">Google Play services tidak terdapat pada perangkat ini. Silahkan install Google Play Services.</string>
<string name="pgm_google_play_service_update_required">Versi Google Play services sudha ketinggalan. Silahkan perbaharui Google Play Services melalui Google Play Store.</string>
<string name="pgm_google_play_sign_in_required">Anda mencoba terhubung pada service tetapi belum signed in. Silahkan periksa pengaturan Google Play Services </string>
<string name="pgm_no_location_providers">Karena perangkat ini tidak tersedia provider lokasi, Applikasi tidak dapat mengetahui lokasi anda.</string>
<string name="pgm_no_location_service_is_disabled">Service Lokasi tidak aktif.</string>
<string name="pgm_location_rejected_by_user">Service Lokasi Ditolak pengguna.</string>
<string name="pgm_can_not_get_location">Tidak dapat mengetahui lokasi anda. Silahkan Coba lagi.</string>
</resources>
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pgm_google_close_button">閉じる</string>
<string name="pgm_api_key_error">config.xmlファイル内の\'GOOGLE_MAPS_ANDROID_API_KEY\'をあなたのAPIキーに置き換えてください</string>
<string name="pgm_google_play_error">この端末ではGoogle Maps Android API v2が利用できません。</string>
<string name="pgm_google_play_developer_error">アプリケーションの設定が間違えています。このエラーは修復不可能かつ重大な失敗なものです。開発者は端末ログを確認してください。</string>
<string name="pgm_google_play_internal_error">Google Play Servicesの内部エラーが発生しました。おそらくもう一度試みていただければ解決します。</string>
<string name="pgm_google_play_invalid_account">ログインしているGoogleアカウントが不正です。再度ログインして試してみてください。</string>
<string name="pgm_google_play_license_check_failed">このアプリケーションの使用がユーザーに認められていません。このエラーは修復不可能かつ重大な失敗なものです。</string>
<string name="pgm_google_play_network_error">ネットワークエラーが発生しました。おそらくもう一度試みていただければ解決します。</string>
<string name="pgm_google_play_service_disabled">この端末にインストールされているGoogle Play Servicesが無効になっています。Google Play Servicesを有効にしてください。</string>
<string name="pgm_google_play_service_invalid">この端末にインストールされているGoogle Play Servicesでは認証ができません。Google Play Storeから最新版のGoogle Play Servicesをインストールしてください。</string>
<string name="pgm_google_play_service_missing">この端末にGoogle Play Servicesがインストールされていません。Google Play Storeから最新版のGoogle Play Servicesをインストールしてください。</string>
<string name="pgm_google_play_service_update_required">この端末にインストールされているGoogle Play Servicesが古過ぎです。Google Play Storeから最新版のGoogle Play Servicesをインストールしてください。</string>
<string name="pgm_google_play_sign_in_required">Google Maps Android API v2を使用しようと試みましたが、あなたがGoogleアカウントにログインしていません。Googleアカウントにログインしてから再度試してみてください。</string>
<string name="pgm_no_location_providers">利用できる位置情報を取得できる方法(GPSなど)がないため、この端末の位置情報が取得できませんでした。</string>
<string name="pgm_no_location_service_is_disabled">位置情報サービス(GPSなど)が無効になっています。</string>
<string name="pgm_location_rejected_by_user">位置情報サービス(GPSなど)の利用がユーザーにより拒否されました。</string>
<string name="pgm_can_not_get_location">この端末の位置情報が取得できませんでした。もう一度試してみてください。</string>
</resources>
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pgm_google_close_button">Lukk</string>
<string name="pgm_api_key_error">Vennligst erstatt \'GOOGLE_MAPS_ANDROID_API_KEY\' i config.xml med din API-nøkkel!</string>
<string name="pgm_google_play_error">Google Maps Android API v2 er ikke tilgjengelig av ukjent grunn på enheten.</string>
<string name="pgm_google_play_developer_error">Applikasjon er feilkonfigurert. Denne feilen er ikke gjenvinnbart og skal behandles som fatal. Utvikleren bør se på loggen etter dette for å fastslå mer handlingsbar informasjon.</string>
<string name="pgm_google_play_internal_error">Det har skjedd en intern feil av Google Play tjeneste. Vennligst prøv igjen og problemme skal løses ut.</string>
<string name="pgm_google_play_invalid_account">Du prøver å koble til tjeneste med ugyldig brukkernavn.</string>
<string name="pgm_google_play_license_check_failed">Applikasjon har ikke licensing for dette brukkernavnet. Denne feilen er ikke gjenvinnbart og skal behandles som fatal.</string>
<string name="pgm_google_play_network_error">Det har skjedd koblingsfailen. Vennligst prøv igjen og problemme skal løses ut.</string>
<string name="pgm_google_play_service_disabled">Innstallert versjon av Google Play tjenesten har vært deaktivert på denne innhetten. Vennligst slå av Google Play tjenesten.</string>
<string name="pgm_google_play_service_invalid">Innstallert versjon av Google Play tjenesten har ikke vært autentisk. Vennligst oppdater Google Play tjenesten fra Google Play Store.</string>
<string name="pgm_google_play_service_missing">Google Play tjenesten er utilghengelig på denne enheten. Vennligst installer Google Play tjenesten.</string>
<string name="pgm_google_play_service_update_required">Innstallert verjson av Google Play tjenesten er ikke oppdatert. Vennligst oppdater Google Play tjenesten fra Google Play Store.</string>
<string name="pgm_google_play_sign_in_required">Du prøver å koble til tjeneste, men du har ikke logget inn. Vennligst sjekk Google Play tjenesten innstellinger.</string>
<string name="pgm_no_location_providers">Siden enheten har ingen stedleverandør, kan ikke din lokasjon oppdages av enheten.</string>
<string name="pgm_no_location_service_is_disabled">Stedtjenester er deaktivert.</string>
<string name="pgm_location_rejected_by_user">Stedtjenester er avvist av brukeren.</string>
<string name="pgm_can_not_get_location">Kan ikke oppdage din lokasjon. Vannligst prøv igjen.</string>
</resources>
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pgm_google_close_button">Sluiten</string>
<string name="pgm_api_key_error">Vervang \"GOOGLE_MAPS_ANDROID_API_KEY\" in config.xml met je API Key!</string>
<string name="pgm_google_play_error">Google Maps Android API v2 is niet beschikbaar op dit apparaat.</string>
<string name="pgm_google_play_developer_error">De applicatie is onjuist geconfigureerd. Deze foutmelding kan niet hesteld worden. De ontwikkelaar moet de logboek informatie bekijken om dit probleem op te lossen.</string>
<string name="pgm_google_play_internal_error">Er heeft een interne fout van Google Play Services plaatsgevonden. Probeer het opnieuw.</string>
<string name="pgm_google_play_invalid_account">Je hebt geprobeerd om te verbinden met de service met een ongeldig gebruikersnaam.</string>
<string name="pgm_google_play_license_check_failed">Er is geen licentie voor de gebruiker. Deze foutmelding kan niet hersteld worden.</string>
<string name="pgm_google_play_network_error">Er heeft een netwerkfout plaatsgevonden. Probeer het opnieuw.</string>
<string name="pgm_google_play_service_disabled">De geinstalleerde versie van Google Play services staat uit op dit apparaat. Zet Google Play Services aan.</string>
<string name="pgm_google_play_service_invalid">De geinstaleerde versie van Google Play services is niet autentiek. Update Google Play Services van de Google Play Store.</string>
<string name="pgm_google_play_service_missing">Google Play services ontbreekt op dit apparaat. Installeer Google Play Services.</string>
<string name="pgm_google_play_service_update_required">De geinstalleerde Google Play services is niet langer geldig. Update Google Play Services van de Google Play Store.</string>
<string name="pgm_google_play_sign_in_required">Je probeert te verbinden met de service maar je bent niet ingelogd. Controleer de Google Play Services configuratie</string>
<string name="pgm_no_location_providers">Omdat dit apparaat geen locatie provider heeft kan de app je locatie niet bepalen.</string>
<string name="pgm_no_location_service_is_disabled">Locatie service staat uit.</string>
<string name="pgm_location_rejected_by_user">Locatie service is afgewezen door de gebruiker.</string>
<string name="pgm_can_not_get_location">Kan je locatie niet bepalen. Probeer opnieuw.</string>
</resources>
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pgm_google_close_button">Zamknij</string>
<string name="pgm_api_key_error">Proszę zastąpić \'GOOGLE_MAPS_ANDROID_API_KEY\' w pliku config.xml własnym kluczem API!</string>
<string name="pgm_google_play_error">Google Maps Android API v2 .</string>
<string name="pgm_google_play_developer_error">Aplikacja posiada niepoprawną konfigurację. Tego błędu nie można odtworzyć i będzie on traktowany jako krytyczny. W celu uzyskania bardziej szczegółowych informacji programista powinien przejrzeć pliki z logami.</string>
<string name="pgm_google_play_internal_error">Wystąpił błąd wewnątrz usług Google Play. Ponowne uruchomienie powinno rozwiązać problem.</string>
<string name="pgm_google_play_invalid_account">Próbę nawiązania połączenia z usługą nie powiodła się, z powodu nieprawidłowej nazwy konta.</string>
<string name="pgm_google_play_license_check_failed">Aplikacja nie jest licencjonowana dla użytkownika. Tego błędu nie można odtworzyć i będzie on traktowany jako krytyczny.</string>
<string name="pgm_google_play_network_error">Wystąpił błąd połączenia internetowego. Proszę spróbować ponownie.</string>
<string name="pgm_google_play_service_disabled">Zainstalowana wersja usług Google Play została wyłączona na urządzeniu. Proszę włączyć usługi Google Play.</string>
<string name="pgm_google_play_service_invalid">Zainstalowana na urządzeniu wersja usług Google Play jest fałszywa. Proszę zaktualizować usługi Google Play ze sklepu Google Play.</string>
<string name="pgm_google_play_service_missing">Nie odnaleziono zainstalowanej wersji usług Google Play. Proszę zainstalować usługi Google Play.</string>
<string name="pgm_google_play_service_update_required">Zainstalowana wersja usług Google Play jest przestarzała. Proszę zaktualizować usługi Google Play Services w sklepie Google Play.</string>
<string name="pgm_google_play_sign_in_required">Próbowano połączyć się z usługami Google Play nie będąc zalogowanym. Proszę sprawdzić konfigurację usług Google Play.</string>
<string name="pgm_no_location_providers">Na urządzeniu nie odnaleziono usług dostarczających położenie. Aplikacja nie będzie w stanie określić położenia.</string>
<string name="pgm_no_location_service_is_disabled">Usługi lokalizacji są wyłączone.</string>
<string name="pgm_location_rejected_by_user">Zgoda na usługi lokalizacji została odrzucona przez użytkownika.</string>
<string name="pgm_can_not_get_location">Nie można określić położenia. Spróbuj ponownie.</string>
</resources>
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pgm_google_close_button">Fechar</string>
<string name="pgm_api_key_error">Por favor substitua \'GOOGLE_MAPS_ANDROID_API_KEY\' em config.xml com sua chave de API!</string>
<string name="pgm_google_play_error">Google Maps Android API v2 não está disponível nesse dispositivo por alguma razão.</string>
<string name="pgm_google_play_developer_error">O aplicativo está mal configurado. Este erro não é recuperável e será tratado como fatal. O desenvolvedor deve analisar os logs para determinar o que pode ser feito</string>
<string name="pgm_google_play_internal_error">Ocorreu um erro interno no Google Play Services. Por favor tente novamente e o erro deverá estar resolvido.</string>
<string name="pgm_google_play_invalid_account">Você tentou se conectar ao serviço com um nome de conta inválido.</string>
<string name="pgm_google_play_license_check_failed">O aplicativo não está licenciado para o usuário. Esse erro não é recuperável e será tratado como fatal.</string>
<string name="pgm_google_play_network_error">Ocorreu um erro de rede. Por favor tente novamente e o erro deverá estar resolvido.</string>
<string name="pgm_google_play_service_disabled">A versão instalada do Google Play Services foi desativada nesse dispositivo. Por favor ative o Google Play Services.</string>
<string name="pgm_google_play_service_invalid">A versão do Google Play Services instalada nesse dispositivo não é autêntica. Atualize o Google Play Services na Google Play Store.</string>
<string name="pgm_google_play_service_missing">O Google Play services não existe nesse dispositivo. Por favor instale o Google Play Services.</string>
<string name="pgm_google_play_service_update_required">A versão do Google Play Services instalada está desatualizada. Por favor atualize o Google Play Services na Google Play Store.</string>
<string name="pgm_google_play_sign_in_required">Você tentou se conectar ao serviço mas você não está logado. Por favor cheque a configuração do Google Play Services.</string>
<string name="pgm_no_location_providers">Esse dispositivo não tem nenhum provedor de localização, logo, este aplicativo não pode detectar sua localização.</string>
<string name="pgm_no_location_service_is_disabled">O serviço de Localização está desativado.</string>
<string name="pgm_location_rejected_by_user">O serviço de Localização foi rejeitado pelo usuário.</string>
<string name="pgm_can_not_get_location">Não foi possível detectar sua localização. Tente novamente.</string>
</resources>
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pgm_google_close_button">Закрыть</string>
<string name="pgm_api_key_error">Пожалуйста, замените \'GOOGLE_MAPS_ANDROID_API_KEY\' в config.xml на Ваш API ключ!</string>
<string name="pgm_google_play_error">По какой то причине Google Maps Android API v2 не доступен на этом устройстве.</string>
<string name="pgm_google_play_developer_error">Приложение неправильно настроено. Произошла фатальная ошибка. Разработчик приложения должен посмотреть в логи, чтобы исправить ошибку.</string>
<string name="pgm_google_play_internal_error">Произошла внутренняя ошибка Google Play Services. Пожалуйста, повторите попытку для решения проблемы.</string>
<string name="pgm_google_play_invalid_account">Вы попытались подключиться к сервису с недопустимым именем учетной записи.</string>
<string name="pgm_google_play_license_check_failed">Произошла фатальная ошибка. Приложение не лицензировано для этого пользователя.</string>
<string name="pgm_google_play_network_error">Произошла сетевая ошибка. Пожалуйста, повторите попытку для решения проблемы.</string>
<string name="pgm_google_play_service_disabled">Установленная версия Google Play Services отключена на этом устройстве. Пожалуйста, включите Google Play Services.</string>
<string name="pgm_google_play_service_invalid">Версия Google Play Services установлена на этом уствройстве не подлинная. Пожалуйста, обновите Google Play Services из Google Play Store.</string>
<string name="pgm_google_play_service_missing">Google Play Services отсутствует на этом устройстве. Пожалуйста, установите Google Play Services.</string>
<string name="pgm_google_play_service_update_required">Установленная версия Google Play Services устаревшая. Пожалуйста, обновите Google Play Services из Google Play Store.</string>
<string name="pgm_google_play_sign_in_required">Вы попытались подключиться к сервису, но вы не вошли в систему. Пожалуйста, проверьте конфигурацию Google Play Services.</string>
<string name="pgm_no_location_providers">Поскольку это устройство не поддерживает поиск местоположения, это приложение не может определить Ваше расположение.</string>
<string name="pgm_no_location_service_is_disabled">Сервис определения местоположения отключен.</string>
<string name="pgm_location_rejected_by_user">Определение местоположения отклонено пользователем.</string>
<string name="pgm_can_not_get_location">Не удается определить Ваше местоположение. Попробуй еще раз.</string>
</resources>
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pgm_google_close_button">Закрити</string>
<string name="pgm_api_key_error">Будь ласка, замініть \'GOOGLE_MAPS_ANDROID_API_KEY\' в config.xml на Ваш API ключ!</string>
<string name="pgm_google_play_error">По якісь причині Google Maps Android API v2 не доступний на цьому пристрої.</string>
<string name="pgm_google_play_developer_error">Програма невірно налаштована. Виникла фатальна помилка. Розробник додатку повинен подивитись в логи, щоб виправити помилку.</string>
<string name="pgm_google_play_internal_error">Відбулася внутрішня помилка Google Play Services. Будь ласка, спробуйте ще раз для вирішення проблеми.</string>
<string name="pgm_google_play_invalid_account">Ви спробували підключитися до сервісу з недійсним ім\'ям облікового запису.</string>
<string name="pgm_google_play_license_check_failed">Виникла фатальна помилка. Додаток не ліцензійований для користувача.</string>
<string name="pgm_google_play_network_error">Виникла помилка мережі. Будь ласка, спробуйте ще раз для вирішення проблеми.</string>
<string name="pgm_google_play_service_disabled">Встановлений Google Play Services на цьому пристрої - відключений. Будь ласка, увімкніть Google Play Services.</string>
<string name="pgm_google_play_service_invalid">Встановлена версія Google Play Services на цьому пристрої не є автентична. Будь ласка, обновіть Google Play Services із Google Play Store.</string>
<string name="pgm_google_play_service_missing">Google Play Services відсутній на цьому пристрої. Будь ласка, встановіть Google Play Services.</string>
<string name="pgm_google_play_service_update_required">Встановлена версія Google Play Services застаріла. Будь ласка, обновіть Google Play Services із Google Play Store.</string>
<string name="pgm_google_play_sign_in_required">Ви спробували підключитися до сервісу, але ви не ввійшли в систему. Будь ласка, перевірте конфігурацію Google Play Services.</string>
<string name="pgm_no_location_providers">Оскільки цей пристрій не підтримує пошук місцезнаходження, цей додаток не може визначити Ваше розташування.</string>
<string name="pgm_no_location_service_is_disabled">Сервіс місцезнаходження вимкнений.</string>
<string name="pgm_location_rejected_by_user">Сервіс пошуку місцезнаходження відхилений користувачем.</string>
<string name="pgm_can_not_get_location">Не вдається виявити Ваше місцезнаходження. Спробуйте знову.</string>
</resources>
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pgm_google_close_button">Đóng</string>
<string name="pgm_api_key_error">Vui lòng thay \'GOOGLE_MAPS_ANDROID_API_KEY\' trong config.xml với API Key của bạn!</string>
<string name="pgm_google_play_error">Google Maps Android API v2 vì một lý do nào đó không hoạt động trên thiết bị của bạn.</string>
<string name="pgm_google_play_developer_error">Ứng dụng chưa được cấu hình. Lỗi này là nghiêm trọng và làm cho hệ thống ngừng hoạt động. Nhà phát triển cần kiểm tra hệ thống Logs để có hướng khắc phục cụ thể..</string>
<string name="pgm_google_play_internal_error">Lỗi xảy ra với dịch vụ Google Play. Vui lòng khởi động lại để hệ thống hoạt động bình thường..</string>
<string name="pgm_google_play_invalid_account">Bạn sử dụng tài khoản không hợp lệ để kết nối với dịch vụ.</string>
<string name="pgm_google_play_license_check_failed">Người sử dụng không được cấp phép sử dụng ứng dụng. Lỗi này là nghiêm trọng và làm cho hệ thống ngừng hoạt động.</string>
<string name="pgm_google_play_network_error">Đường truyền mạng có lỗi. Vui lòng thử lại, và hệ thống sẽ hoạt động bình thường.</string>
<string name="pgm_google_play_service_disabled">Dịch vụ Google Play không hoạt động trên thiết bị của bạn. Vui lòng bật dịch vụ Google Play để hệ thống hoạt động.</string>
<string name="pgm_google_play_service_invalid">Dịch vụ Google Play cài đặt trên máy của bạn không hợp lệ. Vui lòng cập nhật dịch vụ Google Play trên Kho ứng dụng Google Play.</string>
<string name="pgm_google_play_service_missing">Dịch vụ Google PLay chưa được cài đặt trên thiết bị của bạn. Vui lòng cài đặt dịch vụ Google Play.</string>
<string name="pgm_google_play_service_update_required">Dịch vụ Google Play của bạn đã lỗi thời. Vui lòng cập nhật dịch vụ Google Play trên Kho ứng dụng Google Play.</string>
<string name="pgm_google_play_sign_in_required">Bạn chưa thực hiện đăng nhập để kết nối với dịch vụ. Vui lòng xem lại cấu hình dịch vụ Google Play.</string>
<string name="pgm_no_location_providers">Thiết bị không hỗ trợ xác định vị trí vì vậy ứng dụng không thể xác định vị trí của bạn.</string>
<string name="pgm_no_location_service_is_disabled">Dịch vụ xác định vị trí không được kích hoạt.</string>
<string name="pgm_location_rejected_by_user">Người dùng không cho phép sử dụng dịch vụ xác định vị trí.</string>
<string name="pgm_can_not_get_location">Không thể xác định vị trí của bạn. Vui lòng thử lại..</string>
</resources>
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pgm_google_close_button">Close</string>
<string name="pgm_api_key_error">Please replace \'GOOGLE_MAPS_ANDROID_API_KEY\' in the config.xml with your API Key!</string>
<string name="pgm_google_play_error">Google Maps Android API v2 is not available for some reason on this device.</string>
<string name="pgm_google_play_developer_error">The application is miss-configured. This error is not recoverable and will be treated as fatal. The developer should look at the logs after this to determine more actionable information.</string>
<string name="pgm_google_play_internal_error">An internal error of Google Play Services occurred. Please retry, and it should resolve the problem.</string>
<string name="pgm_google_play_invalid_account">You attempted to connect to the service with an invalid account name specified.</string>
<string name="pgm_google_play_license_check_failed">The application is not licensed to the user. This error is not recoverable and will be treated as fatal.</string>
<string name="pgm_google_play_network_error">A network error occurred. Please retry, and it should resolve the problem.</string>
<string name="pgm_google_play_service_disabled">The installed version of Google Play services has been disabled on this device. Please turn on Google Play Services.</string>
<string name="pgm_google_play_service_invalid">The version of the Google Play services installed on this device is not authentic. Please update the Google Play Services from Google Play Store.</string>
<string name="pgm_google_play_service_missing">Google Play services is missing on this device. Please install the Google Play Services.</string>
<string name="pgm_google_play_service_update_required">The installed version of Google Play services is out of date. Please update the Google Play Services from Google Play Store.</string>
<string name="pgm_google_play_sign_in_required">You attempted to connect to the service but you are not signed in. Please check the Google Play Services configuration</string>
<string name="pgm_no_location_providers">Since this device does not have any location provider, this app can not detect your location.</string>
<string name="pgm_no_location_service_is_disabled">Location service is disabled.</string>
<string name="pgm_location_rejected_by_user">Location service is rejected by user.</string>
<string name="pgm_can_not_get_location">Can not detect your location. Try again.</string>
</resources>
+181
View File
@@ -0,0 +1,181 @@
var PluginMap = require('cordova-plugin-googlemaps.PluginMap'),
PluginStreetViewPanorama = require('cordova-plugin-googlemaps.PluginStreetViewPanorama'),
event = require('cordova-plugin-googlemaps.event'),
Environment = require('cordova-plugin-googlemaps.PluginEnvironment');
var MAPS = {};
var API_LOADED_STATUS = 0; // 0: not loaded, 1: loading, 2: completed
document.addEventListener('load_googlemaps', function() {
var envOptions = Environment._getEnv();
var API_KEY_FOR_BROWSER;
if (envOptions) {
if (location.protocol === 'https:') {
API_KEY_FOR_BROWSER = envOptions.API_KEY_FOR_BROWSER_RELEASE;
} else {
API_KEY_FOR_BROWSER = envOptions.API_KEY_FOR_BROWSER_DEBUG;
}
}
API_LOADED_STATUS = 1;
var secureStripeScript = document.createElement('script');
if (API_KEY_FOR_BROWSER && API_KEY_FOR_BROWSER.length > 35) {
secureStripeScript.setAttribute('src','https://maps.googleapis.com/maps/api/js?key=' + API_KEY_FOR_BROWSER);
} else {
// for development only
secureStripeScript.setAttribute('src','https://maps.googleapis.com/maps/api/js');
}
secureStripeScript.addEventListener('load', function() {
API_LOADED_STATUS = 2;
var mKeys = Object.keys(MAPS);
mKeys.forEach(function(mkey) {
var map = MAPS[mkey];
if (!map.get('isGoogleReady')) {
map.trigger('googleready');
}
});
});
secureStripeScript.addEventListener('error', function(error) {
console.log('Can not load the Google Maps JavaScript API v3');
console.log(error);
var mKeys = Object.keys(MAPS);
mKeys.forEach(function(mkey) {
var map = MAPS[mkey];
if (map) {
map.trigger('load_error');
}
});
});
document.getElementsByTagName('head')[0].appendChild(secureStripeScript);
}, {
once: true
});
var stub = function(onSuccess) {
onSuccess();
};
var CordovaGoogleMaps = {
resume: stub,
pause: stub,
getMap: function(onSuccess, onError, args) {
// memory cleanup
var mapIDs = Object.keys(MAPS);
mapIDs.forEach(function(mapId) {
var mapDivId = document.querySelector('[__pluginmapid=\'' + mapId + '\']');
if (!mapDivId) {
if (MAPS[mapDivId]) {
MAPS[mapDivId].destroy();
}
MAPS[mapDivId] = undefined;
delete MAPS[mapDivId];
}
});
var meta = args[0],
mapId = meta.__pgmId;
args[0] = mapId;
args.unshift(this);
var pluginMap = new (PluginMap.bind.apply(PluginMap, args));
MAPS[mapId] = pluginMap;
var dummyObj = {};
var keys = Object.getOwnPropertyNames(PluginMap.prototype).filter(function (p) {
return typeof PluginMap.prototype[p] === 'function';
});
keys.forEach(function(key) {
dummyObj[key] = pluginMap[key].bind(pluginMap);
});
require('cordova/exec/proxy').add(mapId, dummyObj);
pluginMap.one(event.MAP_READY, onSuccess);
pluginMap.one('load_error', onError);
// Does this app already load the google maps library?
API_LOADED_STATUS = (window.google && window.google.maps) ? 2 : API_LOADED_STATUS;
switch(API_LOADED_STATUS) {
case 0:
cordova.fireDocumentEvent('load_googlemaps', []);
break;
case 2:
pluginMap.trigger('googleready');
break;
}
},
removeMap: function(onSuccess, onError, args) {
var mapId = args[0];
var pluginMap = MAPS[mapId];
if (pluginMap) {
var map = pluginMap.get('map');
google.maps.event.clearInstanceListeners(map);
var mapDiv = map.getDiv();
if (mapDiv) {
mapDiv.parentNode.removeChild(mapDiv);
pluginMap.set('map', undefined);
}
}
pluginMap.destroy();
pluginMap = null;
MAPS[mapId] = undefined;
delete MAPS[mapId];
onSuccess();
},
getPanorama: function(onSuccess, onError, args) {
// memory cleanup
var mapIDs = Object.keys(MAPS);
mapIDs.forEach(function(mapId) {
var mapDivId = document.querySelector('[__pluginmapid=\'' + mapId + '\']');
if (!mapDivId) {
if (MAPS[mapDivId]) {
MAPS[mapDivId].destroy();
}
MAPS[mapDivId] = undefined;
delete MAPS[mapDivId];
}
});
var meta = args[0],
mapId = meta.__pgmId;
args[0] = mapId;
args.unshift(this);
var pluginStreetView = new (PluginStreetViewPanorama.bind.apply(PluginStreetViewPanorama, args));
MAPS[mapId] = pluginStreetView;
var dummyObj = {};
var keys = Object.getOwnPropertyNames(PluginStreetViewPanorama.prototype).filter(function (p) {
return typeof PluginStreetViewPanorama.prototype[p] === 'function';
});
keys.forEach(function(key) {
dummyObj[key] = pluginStreetView[key].bind(pluginStreetView);
});
require('cordova/exec/proxy').add(mapId, dummyObj);
pluginStreetView.one(event.PANORAMA_READY, onSuccess);
pluginStreetView.one('load_error', onError);
// Does this app already load the google maps library?
API_LOADED_STATUS = (window.google && window.google.maps) ? 2 : API_LOADED_STATUS;
switch(API_LOADED_STATUS) {
case 0:
cordova.fireDocumentEvent('load_googlemaps', []);
break;
case 2:
pluginStreetView.trigger('googleready');
break;
}
},
};
require('cordova/exec/proxy').add('CordovaGoogleMaps', CordovaGoogleMaps);
+199
View File
@@ -0,0 +1,199 @@
var utils = require('cordova/utils'),
event = require('cordova-plugin-googlemaps.event'),
BaseClass = require('cordova-plugin-googlemaps.BaseClass');
function PluginCircle(pluginMap) {
var self = this;
BaseClass.apply(self);
Object.defineProperty(self, 'pluginMap', {
value: pluginMap,
writable: false
});
}
utils.extend(PluginCircle, BaseClass);
PluginCircle.prototype._create = function(onSuccess, onError, args) {
var self = this,
map = self.pluginMap.get('map'),
circleId = 'circle_' + args[2],
pluginOptions = args[1];
var circleOpts = {
'overlayId': circleId,
'map': map
};
circleOpts.center = pluginOptions.center;
circleOpts.radius = pluginOptions.radius;
if (Array.isArray(pluginOptions.strokeColor)) {
circleOpts.strokeColor = 'rgb(' + pluginOptions.strokeColor[0] + ',' + pluginOptions.strokeColor[1] + ',' + pluginOptions.strokeColor[2] + ')';
circleOpts.strokeOpacity = pluginOptions.strokeColor[3] / 256;
}
if (Array.isArray(pluginOptions.fillColor)) {
circleOpts.fillColor = 'rgb(' + pluginOptions.fillColor[0] + ',' + pluginOptions.fillColor[1] + ',' + pluginOptions.fillColor[2] + ')';
circleOpts.fillOpacity = pluginOptions.fillColor[3] / 256;
}
if ('width' in pluginOptions) {
circleOpts.strokeWeight = pluginOptions.width;
}
if ('zIndex' in pluginOptions) {
circleOpts.zIndex = pluginOptions.zIndex;
}
if ('visible' in pluginOptions) {
circleOpts.visible = pluginOptions.visible;
}
if ('clickable' in pluginOptions) {
circleOpts.clickable = pluginOptions.clickable;
}
var circle = new google.maps.Circle(circleOpts);
circle.addListener('click', function(polyMouseEvt) {
self._onCircleEvent.call(self, circle, polyMouseEvt);
});
self.pluginMap.objects[circleId] = circle;
onSuccess({
'__pgmId': circleId
});
};
PluginCircle.prototype.setCenter = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var lat = args[1];
var lng = args[2];
var circle = self.pluginMap.objects[overlayId];
if (circle) {
circle.setCenter(new google.maps.LatLng(lat, lng));
}
onSuccess();
};
PluginCircle.prototype.setFillColor = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var fillColor = args[1];
var circle = self.pluginMap.objects[overlayId];
if (circle) {
if (Array.isArray(fillColor)) {
circle.setOptions({
'fillColor': 'rgb(' + fillColor[0] + ',' + fillColor[1] + ',' + fillColor[2] + ')',
'fillOpacity': fillColor[3] / 256
});
}
}
onSuccess();
};
PluginCircle.prototype.setStrokeColor = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var strokeColor = args[1];
var circle = self.pluginMap.objects[overlayId];
if (circle) {
if (Array.isArray(strokeColor)) {
circle.setOptions({
'strokeColor': 'rgb(' + strokeColor[0] + ',' + strokeColor[1] + ',' + strokeColor[2] + ')',
'strokeOpacity': strokeColor[3] / 256
});
}
}
onSuccess();
};
PluginCircle.prototype.setStrokeWidth = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var width = args[1];
var circle = self.pluginMap.objects[overlayId];
if (circle) {
circle.setOptions({
'strokeWeight': width
});
}
onSuccess();
};
PluginCircle.prototype.setRadius = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var radius = args[1];
var circle = self.pluginMap.objects[overlayId];
if (circle) {
circle.setRadius(radius);
}
onSuccess();
};
PluginCircle.prototype.setZIndex = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var zIndex = args[1];
var circle = self.pluginMap.objects[overlayId];
if (circle) {
circle.setOptions({
'zIndex': zIndex
});
}
onSuccess();
};
PluginCircle.prototype.setVisible = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var visible = args[1];
var circle = self.pluginMap.objects[overlayId];
if (circle) {
circle.setVisible(visible === true);
}
onSuccess();
};
PluginCircle.prototype.setClickable = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var clickable = args[1];
var circle = self.pluginMap.objects[overlayId];
if (circle) {
circle.setOptions({
'clickable': clickable === true
});
}
onSuccess();
};
PluginCircle.prototype.remove = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var circle = self.pluginMap.objects[overlayId];
if (circle) {
google.maps.event.clearInstanceListeners(circle);
circle.setMap(null);
circle = undefined;
self.pluginMap.objects[overlayId] = undefined;
delete self.pluginMap.objects[overlayId];
}
onSuccess();
};
PluginCircle.prototype._onCircleEvent = function(circle, polyMouseEvt) {
var self = this,
mapId = self.pluginMap.__pgmId;
if (mapId in plugin.google.maps) {
plugin.google.maps[mapId]({
'evtName': event.CIRCLE_CLICK,
'callback': '_onOverlayEvent',
'args': [circle.overlayId, new plugin.google.maps.LatLng(polyMouseEvt.latLng.lat(), polyMouseEvt.latLng.lng())]
});
}
};
module.exports = PluginCircle;
@@ -0,0 +1,31 @@
var envOptions = {};
module.exports = {
'isAvailable': function(onSuccess) {
onSuccess();
},
'setBackGroundColor': function(onSuccess) {
// stub
onSuccess();
},
'getLicenseInfo': function(onSuccess) {
// stub
onSuccess('cordova-plugin-googlemaps for browser does not need to display any open source lincenses. But for iOS, you still need to display the lincense.');
},
'setEnv': function(onSuccess, onError, args) {
var options = args[0];
var keys = Object.keys(options);
keys.forEach(function(key) {
envOptions[key] = options[key];
});
onSuccess();
},
'_getEnv': function() {
// internal method
return envOptions;
}
};
require('cordova/exec/proxy').add('PluginEnvironment', module.exports);
+176
View File
@@ -0,0 +1,176 @@
var BaseArrayClass = require('cordova-plugin-googlemaps.BaseArrayClass');
var geocoder = null;
var lastRequestTime = 0;
var QUEUE = new BaseArrayClass();
QUEUE.on('insert_at', function() {
if (QUEUE.getLength() === 1) {
this.trigger('next');
}
});
QUEUE.one('insert_at', function() {
geocoder = new google.maps.Geocoder();
this.trigger('next');
});
QUEUE.on('next', function() {
var self = QUEUE;
if (!geocoder || self._executing || QUEUE.getLength() === 0) {
return;
}
if (Date.now() - lastRequestTime < 300) {
setTimeout(function() {
self.trigger('next');
}, 300 + Math.floor(Math.random() * 200));
return;
}
lastRequestTime = Date.now();
self._executing = true;
var cmd = QUEUE.removeAt(0, true);
geocoder.geocode(cmd.geocoderRequest, function(results, status) {
switch(status) {
case google.maps.GeocoderStatus.ERROR:
cmd.onError('[geocoding] Cannot connect to Google servers');
return;
case google.maps.GeocoderStatus.INVALID_REQUEST:
cmd.onError('[geocoding] Invalid request for geocoder');
return;
case google.maps.GeocoderStatus.OVER_QUERY_LIMIT:
QUEUE.insertAt(0, cmd);
console.warn('[geocoding] Due to the OVER_QUERY_LIMIT error, wait 3 sec, then try again.');
setTimeout(function() {
self._executing = false;
self.trigger('next');
}, 3000 + Math.floor(Math.random() * 200));
return;
case google.maps.GeocoderStatus.REQUEST_DENIED:
cmd.onError('[geocoding] Google denited your geocoding request.');
return;
case google.maps.GeocoderStatus.UNKNOWN_ERROR:
cmd.onError('[geocoding] There was an unknown error. Please try again.');
return;
}
var pluginResults = results.map(function(geocoderResult) {
var result = {
'position': {
'lat': geocoderResult.geometry.location.lat(),
'lng': geocoderResult.geometry.location.lng()
},
extra: {
lines: []
}
};
if (geocoderResult.place_id) {
result.extra.place_id = geocoderResult.place_id;
}
if (geocoderResult.plus_code) {
result.extra.plus_code = geocoderResult.plus_code;
}
if (geocoderResult.types) {
result.extra.types = geocoderResult.types;
}
var administrative_area = [];
var sublocality_area = [];
geocoderResult.address_components.forEach(function(addrComp) {
result.extra.lines.push(addrComp.long_name);
if (!result.locality && addrComp.types.indexOf('locality') > -1) {
result.locality = addrComp.short_name;
}
if (addrComp.types.indexOf('administrative_area_level_1') > -1 ||
addrComp.types.indexOf('administrative_area_level_2') > -1 ||
addrComp.types.indexOf('administrative_area_level_3') > -1 ||
addrComp.types.indexOf('administrative_area_level_4') > -1) {
addrComp.types.forEach(function(type) {
if (type.indexOf('administrative_area_level_') === 0) {
var idx = parseInt(type.replace('administrative_area_level_', ''), 10);
administrative_area[idx - 1] = addrComp.long_name;
}
});
}
if (addrComp.types.indexOf('sublocality_level_1') > -1 ||
addrComp.types.indexOf('sublocality_level_2') > -1 ||
addrComp.types.indexOf('sublocality_level_3') > -1 ||
addrComp.types.indexOf('sublocality_level_4') > -1 ||
addrComp.types.indexOf('sublocality_level_5') > -1) {
addrComp.types.forEach(function(type) {
if (type.indexOf('sublocality_level_') === 0) {
var idx = parseInt(type.replace('sublocality_level_', ''), 10);
sublocality_area[idx - 1] = addrComp.long_name;
}
});
}
if (!result.country && addrComp.types.indexOf('country') > -1) {
result.country = addrComp.long_name;
result.countryCode = addrComp.short_name;
}
if (!result.postalCode && addrComp.types.indexOf('postal_code') > -1) {
result.postalCode = addrComp.long_name;
}
if (!result.thoroughfare && addrComp.types.indexOf('street_address') > -1) {
result.thoroughfare = addrComp.long_name;
}
});
if (administrative_area.length > 0) {
result.adminArea = administrative_area.shift();
result.subAdminArea = administrative_area.join(',');
}
if (sublocality_area.length > 0) {
result.subLocality = sublocality_area.join(',');
}
//result.extra = geocoderResult.address_components; // for debug
return result;
});
self._executing = false;
cmd.onSuccess({
'idx': cmd.pluginRequest.idx,
'results': pluginResults
});
// Insert delay to prevent the OVER_QUERY_LIMIT error.
var delay = 300 + Math.floor(Math.random() * 200);
setTimeout(function() {
self._executing = false;
self.trigger('next');
}, delay);
});
});
module.exports = {
'geocode': function(onSuccess, onError, args) {
var request = args[0];
var geocoderRequest = {};
if (!request.position && request.address) {
//---------------------
// Geocoding
//---------------------
if (request.bounds && Array.isArray(request.bounds)) {
var bounds = new google.maps.LatLngBounds();
request.bounds.forEach(function(position) {
bounds.extend(position);
});
geocoderRequest.bounds = bounds;
}
geocoderRequest.address = request.address;
}
if (request.position && !request.address) {
geocoderRequest.location = request.position;
}
QUEUE.push({
'geocoderRequest': geocoderRequest,
'pluginRequest': request,
'onSuccess': onSuccess,
'onError': onError
});
}
};
require('cordova/exec/proxy').add('PluginGeocoder', module.exports);
@@ -0,0 +1,412 @@
var utils = require('cordova/utils'),
event = require('cordova-plugin-googlemaps.event'),
BaseClass = require('cordova-plugin-googlemaps.BaseClass'),
Spherical = require('cordova-plugin-googlemaps.spherical'),
LatLng = require('cordova-plugin-googlemaps.LatLng');
function PluginGroundOverlay(pluginMap) {
var self = this;
BaseClass.apply(self);
Object.defineProperty(self, 'pluginMap', {
value: pluginMap,
writable: false
});
}
utils.extend(PluginGroundOverlay, BaseClass);
PluginGroundOverlay.prototype._create = function(onSuccess, onError, args) {
var self = this,
map = self.pluginMap.get('map'),
groundoverlayId = 'groundoverlay_' + args[2],
pluginOptions = args[1];
var groundoverlayOpts = {
'overlayId': groundoverlayId,
'map': map
};
var bounds = new google.maps.LatLngBounds();
pluginOptions.bounds.forEach(function(latLng) {
bounds.extend(latLng);
});
if ('bearing' in pluginOptions) {
groundoverlayOpts.bearing = pluginOptions.bearing;
}
if ('zIndex' in pluginOptions) {
groundoverlayOpts.zIndex = pluginOptions.zIndex;
}
if ('opacity' in pluginOptions) {
groundoverlayOpts.opacity = pluginOptions.opacity;
}
if ('visible' in pluginOptions) {
groundoverlayOpts.visible = pluginOptions.visible;
}
if ('clickable' in pluginOptions) {
groundoverlayOpts.clickable = pluginOptions.clickable;
}
var groundoverlay = newGroundOverlay(pluginOptions.url, bounds, groundoverlayOpts);
groundoverlay.addListener('click', function(polyMouseEvt) {
self._onGroundOverlayEvent.call(self, groundoverlay, polyMouseEvt);
});
self.pluginMap.objects[groundoverlayId] = groundoverlay;
self.pluginMap.objects['groundoverlay_property_' + groundoverlayId] = pluginOptions;
onSuccess({
'__pgmId': groundoverlayId
});
};
PluginGroundOverlay.prototype.setImage = function(onSuccess, onError, args) {
var self = this,
overlayId = args[0],
url = args[1],
groundoverlay = self.pluginMap.objects[overlayId];
if (groundoverlay) {
groundoverlay.setImage(url);
}
onSuccess();
};
PluginGroundOverlay.prototype.setBearing = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var groundOverlay = self.pluginMap.objects[overlayId];
if (groundOverlay) {
groundOverlay.setBearing(args[1]);
}
onSuccess();
};
PluginGroundOverlay.prototype.setOpacity = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var groundOverlay = self.pluginMap.objects[overlayId];
if (groundOverlay) {
groundOverlay.setOpacity(args[1]);
}
onSuccess();
};
PluginGroundOverlay.prototype.setBounds = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var groundOverlay = self.pluginMap.objects[overlayId];
if (groundOverlay) {
var points = args[1];
var bounds = new google.maps.LatLngBounds();
points.forEach(function(latLng) {
bounds.extend(latLng);
});
groundOverlay.setBounds(bounds);
}
onSuccess();
};
PluginGroundOverlay.prototype.setZIndex = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var zIndex = args[1];
var groundOverlay = self.pluginMap.objects[overlayId];
if (groundOverlay) {
groundOverlay.setZIndex(zIndex);
}
onSuccess();
};
PluginGroundOverlay.prototype.setClickable = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var groundOverlay = self.pluginMap.objects[overlayId];
if (groundOverlay) {
groundOverlay.setClickable(args[1]);
}
onSuccess();
};
PluginGroundOverlay.prototype.setVisible = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var groundOverlay = self.pluginMap.objects[overlayId];
if (groundOverlay) {
groundOverlay.setVisible(args[1]);
}
onSuccess();
};
PluginGroundOverlay.prototype.remove = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var groundOverlay = self.pluginMap.objects[overlayId];
if (groundOverlay) {
google.maps.event.clearInstanceListeners(groundOverlay);
groundOverlay.setMap(null);
groundOverlay.remove();
groundOverlay = undefined;
self.pluginMap.objects[overlayId] = undefined;
delete self.pluginMap.objects[overlayId];
delete self.pluginMap.objects['groundoverlay_property_' + overlayId];
}
onSuccess();
};
PluginGroundOverlay.prototype._onGroundOverlayEvent = function(groundoverlay, mouseEvt) {
var self = this,
mapId = self.pluginMap.__pgmId;
if (mapId in plugin.google.maps) {
plugin.google.maps[mapId]({
'evtName': event.GROUND_OVERLAY_CLICK,
'callback': '_onOverlayEvent',
'args': [groundoverlay.overlayId, new LatLng(mouseEvt.latLng.lat(), mouseEvt.latLng.lng())]
});
}
};
module.exports = PluginGroundOverlay;
function newGroundOverlay(url, bounds, options) {
// https://github.com/apache/cordova-js/blob/c75e8059114255d1dbae1ede398e6626708ee9f3/src/common/utils.js#L167
if (CustomGroundOverlay.__super__ !== google.maps.OverlayView.prototype) {
var key, defined = {};
for (key in CustomGroundOverlay.prototype) {
defined[key] = CustomGroundOverlay.prototype[key];
}
utils.extend(CustomGroundOverlay, google.maps.OverlayView);
for (key in defined) {
CustomGroundOverlay.prototype[key] = defined[key];
}
}
return new CustomGroundOverlay(url, bounds, options);
}
function CustomGroundOverlay(url, bounds, options) {
google.maps.OverlayView.apply(this);
var self = this;
//------------------------------------------------------------
// Create an img element, then put it on map as GroundOverlay
//------------------------------------------------------------
var img = new Image();
img.src = url;
img.style.position = 'absolute';
img.style.WebkitTransformOrigin = '50% 50%';
img.style.MozTransformOrigin = '50% 50%';
img.style.transformOrigin = '50% 50%';
self.set('img', img);
self.set('url', url);
self.addListener('url_changed', function() {
img.src = self.get('url');
});
var path = new google.maps.MVCArray([
new google.maps.LatLng(0, 0),
new google.maps.LatLng(0, 0),
new google.maps.LatLng(0, 0),
new google.maps.LatLng(0, 0)
]);
self.set('path', path);
self.addListener('bearing_changed', self.updateVertixes);
self.addListener('bounds_changed', self.updateVertixes);
self.addListener('opacity_changed', function() {
img.style.opacity = self.get('opacity');
});
//---------------------------------------------------------
// Create a transparent rectangle above the groundOverlay,
// then catch click event
//---------------------------------------------------------
var touchPolygon = new google.maps.Polygon({
'map': options.map,
'paths': path,
'clickable': true,
'fillColor': 'red',
'fillOpacity': 0,
'strokeOpacity': 0,
'zIndex': options.zIndex || 0
});
self.addListener('visible_changed', function() {
img.style.visibility = self.get('visible') ? 'visible' : 'hidden';
touchPolygon.setVisible(self.get('visible'));
});
self.addListener('zIndex_changed', function() {
touchPolygon.setOptions({
'zIndex': self.get('zIndex')
});
img.style.zIndex = self.get('zIndex');
});
touchPolygon.addListener('click', function(evt) {
if (self.get('clickable')) {
google.maps.event.trigger(self, 'click', {
'latLng': evt. latLng
});
}
});
self.set('touchPolygon', touchPolygon);
var markers = [];
for (var i = 0; i < 4; i++) {
markers.push(new google.maps.Marker({
'position': {'lat': 0, 'lng': 0},
'map': options.map
}));
}
self.set('markers', markers);
//---------------------------------------------------------
// Apply option values
//---------------------------------------------------------
for (var key in options) {
self.set(key, options[key]);
}
self.set('bounds', bounds);
}
CustomGroundOverlay.prototype.updateVertixes = function() {
var self = this;
var degree = self.get('bearing') || 0;
// Get center
var bounds = self.get('bounds');
if (!bounds) {
return;
}
var center = bounds.getCenter(),
centerPos = {
'lat': center.lat(),
'lng': center.lng()
};
// original NorthEast
var orgNE = bounds.getNorthEast(),
orgSW = bounds.getSouthWest(),
orgNEpos = {
'lat': orgNE.lat(),
'lng': orgNE.lng()
};
var orgDrgreeNE = Spherical.computeHeading(centerPos, orgNEpos);
var orgDrgreeSE = Spherical.computeHeading(centerPos, {
'lat': orgSW.lat(),
'lng': orgNE.lng()
});
var distanceCenter2NE = Spherical.computeDistanceBetween(orgNEpos, centerPos);
// new NorthEast
var newNE = Spherical.computeOffsetOrigin(centerPos, distanceCenter2NE, degree + orgDrgreeNE);
// new SourthEast
var newSE = Spherical.computeOffsetOrigin(centerPos, distanceCenter2NE, degree + orgDrgreeSE);
// new SourthWest
var newSW = Spherical.computeOffsetOrigin(centerPos, distanceCenter2NE, degree + orgDrgreeNE + 180);
// new SouthEast
var newNW = Spherical.computeOffsetOrigin(centerPos, distanceCenter2NE, degree + orgDrgreeSE + 180);
// Caclulate bounds
var path = self.get('path');
path.setAt(0, new google.maps.LatLng(newNE.lat, newNE.lng));
path.setAt(1, new google.maps.LatLng(newSE.lat, newSE.lng));
path.setAt(2, new google.maps.LatLng(newSW.lat, newSW.lng));
path.setAt(3, new google.maps.LatLng(newNW.lat, newNW.lng));
self.draw();
};
CustomGroundOverlay.prototype.remove = function() {
var self = this;
self.set('img', undefined);
google.maps.event.clearInstanceListeners(self);
google.maps.event.clearInstanceListeners(self.get('touchPolygon'));
};
CustomGroundOverlay.prototype.setClickable = function(clickable) {
var self = this;
self.set('clickable', clickable);
};
CustomGroundOverlay.prototype.setOpacity = function(opacity) {
var self = this;
self.get('img').style.opacity = opacity;
};
CustomGroundOverlay.prototype.setBearing = function(bearing) {
var self = this;
self.set('bearing', bearing);
};
CustomGroundOverlay.prototype.setBounds = function(bounds) {
var self = this;
self.set('bounds', bounds);
};
CustomGroundOverlay.prototype.setOpacity = function(opacity) {
var self = this;
self.set('opacity', opacity);
};
CustomGroundOverlay.prototype.setZIndex = function(zIndex) {
var self = this;
self.set('zIndex', zIndex);
};
CustomGroundOverlay.prototype.setVisible = function(visible) {
var self = this;
self.set('visible', visible);
};
CustomGroundOverlay.prototype.setImage = function(url) {
var self = this;
self.set('url', url);
};
CustomGroundOverlay.prototype.getBounds = function() {
var self = this;
return new google.maps.LatLngBounds(
self.get('sw'), self.get('ne')
);
};
CustomGroundOverlay.prototype.draw = function() {
var self = this,
projection = self.getProjection();
if (!projection) {
return;
}
var bounds = self.get('bounds'),
img = self.get('img');
// Calculate positions
var swPx = projection.fromLatLngToDivPixel(bounds.getSouthWest()),
nePx = projection.fromLatLngToDivPixel(bounds.getNorthEast());
img.style.left = swPx.x + 'px';
img.style.top = nePx.y + 'px';
img.style.width = (nePx.x - swPx.x) + 'px';
img.style.height = (swPx.y - nePx.y) + 'px';
img.style.transform = 'rotate(' + self.get('bearing') + 'deg)';
img.style.WebkitTransform = 'rotate(' + self.get('bearing') + 'deg)';
img.style.MozTransform = 'rotate(' + self.get('bearing') + 'deg)';
};
CustomGroundOverlay.prototype.onAdd = function() {
var self = this;
self.set('mapPane', self.getPanes().mapPane);
self.getPanes().mapPane.appendChild(self.get('img'));
};
CustomGroundOverlay.prototype.onRemove = function() {
var self = this;
self.get('mapPane').removeChild(self.get('img'));
google.maps.event.clearInstanceListeners(self.get('img'));
};
+545
View File
@@ -0,0 +1,545 @@
/* eslint no-useless-escape: off */
var InlineWorker = require('cordova-plugin-googlemaps.InlineWorker');
function PluginKmlOverlay() {
// stub
}
PluginKmlOverlay.prototype._create = function(onSuccess, onError, args) {
var pluginOptions = args[1];
if (!/^https?:/.test(location.protocol)) {
return onError('KmlOverlay is only available on http: or https: protocols.');
}
//-------------------------------------
// Parse the xml file using WebWorker
//-------------------------------------
var worker = new InlineWorker(loadKml);
worker.onmessage = function(evt) {
//console.log('host message', evt.data);
worker.terminate();
onSuccess(evt.data);
};
worker.onerror = onError;
var link = document.createElement('a');
link.href = pluginOptions.url;
var url = link.protocol+'//'+link.host+link.pathname+link.search;
worker.postMessage({
'url': url
});
};
module.exports = PluginKmlOverlay;
function loadKml(self) {
// code: https://stackoverflow.com/q/32912732/697856
var createCORSRequest = function(method, url, asynch) {
var xhr = new XMLHttpRequest();
if ('withCredentials' in xhr) {
// XHR for Chrome/Firefox/Opera/Safari.
xhr.open(method, url, asynch);
// xhr.setRequestHeader('MEDIBOX', 'login');
xhr.setRequestHeader('Content-Type', 'application/xml; charset=UTF-8');
} else if (typeof window.XDomainRequest != 'undefined') {
// XDomainRequest for IE.
xhr = new window.XDomainRequest();
xhr.open(method, url, asynch);
} else {
// CORS not supported.
xhr = null;
}
return xhr;
};
//---------------------------------------
// modified fromXML (xml parser)
//---------------------------------------
var fromXML = (function() {
// fromXML
// https://github.com/kawanet/from-xml
//
// The MIT License (MIT)
//
// Copyright (c) 2016 Yusuke Kawasaki
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
/**
* The fromXML() method parses an XML string, constructing the JavaScript
* value or object described by the string.
*
* @function fromXML
* @param text {String} The string to parse as XML
* @param [reviver] {Function} If a function, prescribes how the value
* originally produced by parsing is transformed, before being returned.
* @returns {Object}
*/
var UNESCAPE = {
'&amp;': '&',
'&lt;': '<',
'&gt;': '>',
'&apos;': '\'',
'&quot;': '"'
};
var CHILD_NODE_KEY = '#';
function parseXML(text) {
var list = String.prototype.split.call(text, /<([^!<>?](?:'[\S\s]*?'|"[\S\s]*?"|[^'"<>])*|!(?:--[\S\s]*?--|\[[^\[\]'"<>]+\[[\S\s]*?]]|DOCTYPE[^\[<>]*?\[[\S\s]*?]|(?:ENTITY[^"<>]*?"[\S\s]*?")?[\S\s]*?)|\?[\S\s]*?\?)>/);
var length = list.length;
// root element
var root = {f: []};
var elem = root;
// dom tree stack
var stack = [];
for (var i = 0; i < length;) {
// text node
var str = list[i++];
if (str) appendText(str);
// child node
var tag = list[i++];
if (tag) parseNode(tag);
}
return root;
function parseNode(tag) {
var tagLength = tag.length;
var firstChar = tag[0];
if (firstChar === '/') {
// close tag
var closed = tag.replace(/^\/|[\s\/].*$/g, '').toLowerCase();
while (stack.length) {
var tagName = elem.n && elem.n.toLowerCase();
elem = stack.pop();
if (tagName === closed) break;
}
// } else if (firstChar === "?") {
// // XML declaration
// appendChild({n: "?", r: tag.substr(1, tagLength - 2)});
} else if (firstChar === '!') {
if (tag.substr(1, 7) === '[CDATA[' && tag.substr(-2) === ']]') {
// CDATA section
appendText(tag.substr(8, tagLength - 10));
} else {
// comment
appendChild({n: '!', r: tag.substr(1)});
}
} else {
var child = openTag(tag);
appendChild(child);
if (tag[tagLength - 1] === '/') {
child.c = 1; // emptyTag
} else {
stack.push(elem); // openTag
elem = child;
}
}
}
function appendChild(child) {
elem.f.push(child);
}
function appendText(str) {
str = removeSpaces(str);
if (str) appendChild(unescapeXML(str));
}
}
function openTag(tag) {
var elem = {f: []};
tag = tag.replace(/\s*\/?$/, '');
var pos = tag.search(/[\s='"\/]/);
if (pos < 0) {
elem.n = tag;
} else {
elem.n = tag.substr(0, pos);
elem.t = tag.substr(pos);
}
return elem;
}
function parseAttribute(elem, reviver) {
if (!elem.t) return;
var list = elem.t.split(/([^\s='"]+(?:\s*=\s*(?:'[\S\s]*?'|"[\S\s]*?"|[^\s'"]*))?)/);
var attributes = {};
list.forEach(function(str) {
var val;
str = removeSpaces(str);
if (!str) return;
var pos = str.indexOf('=');
if (pos < 0) {
// bare attribute
val = null;
} else {
// attribute key/value pair
val = str.substr(pos + 1).replace(/^\s+/, '');
str = str.substr(0, pos).replace(/\s+$/, '');
// quote: foo="FOO" bar='BAR'
var firstChar = val[0];
var lastChar = val[val.length - 1];
if (firstChar === lastChar && (firstChar === '\'' || firstChar === '"')) {
val = val.substr(1, val.length - 2);
}
val = unescapeXML(val);
}
if (reviver) {
val = reviver(str, val);
}
addAttribute(attributes, str, val);
});
return attributes;
}
function removeSpaces(str) {
return str && str.replace(/^\s+|\s+$/g, '');
}
function unescapeXML(str) {
return str.replace(/(&(?:lt|gt|amp|apos|quot|#(?:\d{1,6}|x[0-9a-fA-F]{1,5}));)/g, function(str) {
if (str[1] === '#') {
var code = (str[2] === 'x') ? parseInt(str.substr(3), 16) : parseInt(str.substr(2), 10);
if (code > -1) return String.fromCharCode(code);
}
return UNESCAPE[str] || str;
});
}
function toObject(elem, reviver) {
//
// var raw = elem.r;
// if (raw) return raw;
var attributes = parseAttribute(elem, reviver);
var object;
var childList = elem.f;
var childLength = childList && childList.length || 0;
if (attributes || childLength > 1) {
// merge attributes and child nodes
if (typeof attributes === 'object') {
object = attributes;
object.line=198;
} else {
object = {};
}
object.tagName = elem.n;
childList.forEach(function(child) {
if ('string' === typeof child) {
addObject(object, CHILD_NODE_KEY, child);
} else {
addObject(object, child.n, toObject(child, reviver));
}
});
} else if (childLength) {
// the node has single child node but no attribute
var child = childList[0];
if ('string' === typeof child) {
object = {
'tagName': elem.n,
'value': child,
'line': 215
};
} else {
object = toObject(child, reviver);
if (child.n) {
object = {
'tagName': elem.n,
'value': {
'children': [object]
},
'line': 227
};
}
}
} else {
// the node has no attribute nor child node
object = {
'tagName': elem.n,
'value': '',
'line': 233
};
}
if (reviver) {
object = reviver(elem.n || '', object);
}
return object;
}
function addAttribute(object, key, val) {
if ('undefined' === typeof val) return;
object.attributes = object.attributes || {};
object.attributes[key] = val;
}
function addObject(object, key, val) {
if ('undefined' === typeof val) return;
object.value = object.value || {};
object.value.children = object.value.children || [];
if (typeof val === 'object' && val.tagName) {
object.value.children.push(val);
} else {
object.value.children.push({
'tagName': key,
'value': val,
'line': 258
});
}
}
return function(text, reviver) {
text = text.replace(/<\?xml[^>]+>/i, '');
var xmlTree = parseXML(text);
var result = toObject(xmlTree, reviver);
result.tagName = 'document';
return result;
};
})();
//---------------------------------------
// KmlParserClass
//---------------------------------------
function KmlParserClass() {
var _parser = this;
_parser.styleHolder = {};
_parser.schemaHolder = {};
_parser.tagSwitchTables = {
'styleurl': _parser._styleurl,
'stylemap': _parser._style,
'style': _parser._style,
'schema': _parser._schema,
'coordinates': _parser._coordinates
};
}
KmlParserClass.prototype.parseXml = function(rootElement) {
var _parser = this,
tagName = rootElement.tagName.toLowerCase();
var _proc = _parser.tagSwitchTables[tagName] || _parser._default;
//console.log("--->tagName = " + tagName, tagName in _parser.tagSwitchTables ? tagName : '_default');
var result = _proc.call(_parser, rootElement);
if (!result) {
return null;
}
result.tagName = tagName;
if (rootElement.attributes) {
var attrNames = Object.keys(rootElement.attributes);
attrNames.forEach(function(attrName) {
result[attrName] = rootElement.attributes[attrName];
});
}
return result;
};
KmlParserClass.prototype._styleurl = function(rootElement) {
return {
'styleId': rootElement.value
};
};
KmlParserClass.prototype._style = function(rootElement) {
var _parser = this;
// Generate a style id for the tag
var styleId = rootElement.attributes ? rootElement.attributes.id : null;
if (!styleId) {
styleId = '__' + Math.floor(Date.now() * Math.random()) + '__';
}
var result = {
'styleId': styleId
};
// Store style information into the styleHolder
var styles = {};
var children = [];
if (rootElement.value.children) {
rootElement.value.children.forEach(function(childNode) {
var node = _parser.parseXml(childNode);
if (node.value) {
styles[node.tagName] = node.value;
} else {
children.push(node);
}
});
if (children.length > 0) {
styles.children = children;
}
}
_parser.styleHolder[styleId] = styles;
return result;
};
KmlParserClass.prototype._schema = function(rootElement) {
var _parser = this;
var result = {};
// Generate a schema id for the tag
var schemaId = rootElement.attributes ? rootElement.attributes.id : null;
if (!schemaId) {
schemaId = '__' + Math.floor(Date.now() * Math.random()) + '__';
}
// Store schema information into the schemaHolder.
var schema = {};
schema.name = rootElement.attributes ? rootElement.attributes.id : '__' + Math.floor(Date.now() * Math.random()) + '__';
if (rootElement.value.children) {
var children = [];
rootElement.value.children.forEach(function(childNode) {
var node = _parser.parseXml(childNode);
if (node) {
children.push(node);
}
});
if (children.length > 0) {
schema.children = children;
}
}
_parser.schemaHolder[schemaId] = schema;
return result;
};
KmlParserClass.prototype._coordinates = function(rootElement) {
var result = {};
var latLngList = [];
var txt = rootElement.value;
txt = txt.replace(/\s+/g, '\n');
txt = txt.replace(/\n+/g, '\n');
var lines = txt.split(/\n/);
lines.forEach(function(line) {
line = line.replace(/[^0-9,.\\-]/g, '');
if (line !== '') {
var tmpArry = line.split(',');
latLngList.push({
'lat': parseFloat(tmpArry[1]),
'lng': parseFloat(tmpArry[0])
});
}
});
result.coordinates = latLngList;
return result;
};
KmlParserClass.prototype._default = function(rootElement) {
var _parser = this,
result = {};
if (!rootElement.value) {
return null;
}
if (rootElement.value.children) {
var children = [];
rootElement.value.children.forEach(function(childNode) {
var node = _parser.parseXml.call(_parser, childNode);
if (node) {
if ('styleId' in node) {
result.styleIDs = result.styleIDs || [];
result.styleIDs.push(node.styleId);
} else if (node.tagName !== 'schema') {
children.push(node);
}
}
});
result.children = children;
} else {
var value = rootElement.value;
if (/^-?[0-9]+$/.test(value)) {
result.value = parseInt(value, 10);
} else if (/^-?[0-9\.]+$/.test(value)) {
result.value = parseFloat(value, 10);
} else {
result.value = value;
}
}
return result;
};
self.onmessage = function(evt) {
var params = evt.data;
//------------------------------------------
// Load & parse kml file in WebWorker
//------------------------------------------
(new Promise(function(resolve, reject) {
//-----------------
// Read XML file
//-----------------
var xhr = createCORSRequest('GET', params.url, true);
if (xhr) {
xhr.onreadystatechange = function() {
try {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(xhr.responseText);
} else {
reject(xhr);
}
}
} catch (e) {
reject(e.description);
}
};
xhr.send();
}
}))
.then(function(xmlTxt) {
//-----------------
// Parse it
//-----------------
var doc = fromXML(xmlTxt);
var parser = new KmlParserClass();
var root = parser.parseXml(doc);
var result = {
'schemas': parser.schemaHolder,
'styles': parser.styleHolder,
'root': root
};
postMessage(result);
});
};
}
@@ -0,0 +1,59 @@
var LOCATION_ERROR = {
'1': 'service_denied',
'2': 'not_available',
'3': 'timeout'
};
module.exports = {
'hasPermission': function(onSuccess, onError) {
if (navigator.permissions) {
navigator.permissions.query({'name': 'geolocation'})
.then(function(permission) {
onSuccess(permission.state === 'granted' ? 1 : 0);
})
.catch(onError);
} else {
onError('Browser does not support this feature.');
}
},
'getMyLocation': function(onSuccess, onError) {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(position) {
onSuccess({
'latLng': {
'lat': position.coords.latitude,
'lng': position.coords.longitude
},
'elapsedRealtimeNanos': 0,
'time': position.timestamp,
'accuracy': position.coords.accuracy,
'altitude': position.coords.altitude,
'speed': position.coords.speed,
'bearing': position.coords.heading,
'provider': 'geolocationapi',
'hashCode': 'dummy',
'status': true
});
}, function(error) {
onError({
'status': false,
'error_code': LOCATION_ERROR[error.code],
'error_message': error.message
});
}, {
'enableHighAccuracy': true
});
} else {
onError({
'status': false,
'error_code': 'not_available',
'error_message': 'Since this device does not have any location provider, this app can not detect your location.'
});
}
}
};
require('cordova/exec/proxy').add('PluginLocationService', module.exports);
+726
View File
@@ -0,0 +1,726 @@
var utils = require('cordova/utils'),
event = require('cordova-plugin-googlemaps.event'),
BaseClass = require('cordova-plugin-googlemaps.BaseClass'),
LatLng = require('cordova-plugin-googlemaps.LatLng'),
MapTypeId = require('cordova-plugin-googlemaps.MapTypeId');
var MAP_TYPES = {};
MAP_TYPES[MapTypeId.NORMAL] = 'roadmap';
MAP_TYPES[MapTypeId.ROADMAP] = 'roadmap';
MAP_TYPES[MapTypeId.SATELLITE] = 'satellite';
MAP_TYPES[MapTypeId.HYBRID] = 'hybrid';
MAP_TYPES[MapTypeId.TERRAIN] = 'terrain';
MAP_TYPES[MapTypeId.NONE] = 'none';
var LOCATION_ERROR = {};
LOCATION_ERROR[1] = 'service_denied';
LOCATION_ERROR[2] = 'not_available';
LOCATION_ERROR[3] = 'cannot_detect';
var LOCATION_ERROR_MSG = {};
LOCATION_ERROR_MSG[1] = 'Location service is rejected by user.';
LOCATION_ERROR_MSG[2] = 'Since this device does not have any location provider, this app can not detect your location.';
LOCATION_ERROR_MSG[3] = 'Can not detect your location. Try again.';
function displayGrayMap(container) {
var gmErrorContent = document.querySelector('.gm-err-container');
var gmnoprint = document.querySelector('.gmnoprint');
if (!gmErrorContent && !gmnoprint) {
container.innerHTML = [
'<div style="position: absolute; left: 0; top: 0; width: 100%; height: 100%; background-color:rgb(229, 227, 223)">',
'<div style="text-align: center; position: absolute; left: 0; top: 0; bottom: 0; right: 0; width: 80%; height: 100px; margin: auto; color: #616161">',
'<img src="https://maps.gstatic.com/mapfiles/api-3/images/icon_error.png"><br>',
'<h3>Can not display map.<br>Check the developer console.</h3>',
'</div>',
'</div>'
].join('\n');
}
}
function PluginMap(mapId, options) {
var self = this;
BaseClass.apply(this);
var mapDiv = document.querySelector('[__pluginMapId=\'' + mapId + '\']');
mapDiv.style.backgroundColor = 'rgb(229, 227, 223)';
var container = document.createElement('div');
container.style.userSelect='none';
container.style['-webkit-user-select']='none';
container.style['-moz-user-select']='none';
container.style['-ms-user-select']='none';
mapDiv.style.position = 'relative';
container.style.position = 'absolute';
container.style.top = 0;
container.style.bottom = 0;
container.style.right = 0;
container.style.left = 0;
mapDiv.insertBefore(container, mapDiv.firstElementChild);
self.set('isGoogleReady', false);
self.set('container', container);
self.PLUGINS = {};
Object.defineProperty(self, '__pgmId', {
value: mapId,
writable: false
});
Object.defineProperty(self, 'objects', {
value: {},
writable: false
});
Object.defineProperty(self, 'activeMarker', {
value: null,
writable: true
});
self.set('clickable', true);
self.one('googleready', function() {
self.set('isGoogleReady', true);
var mapTypeReg = new google.maps.MapTypeRegistry();
mapTypeReg.set('none', new google.maps.ImageMapType({
'getTileUrl': function() { return null; },
'name': 'none_type',
'tileSize': new google.maps.Size(256, 256),
'minZoom': 0,
'maxZoom': 25
}));
var mapInitOptions = {
mapTypes: mapTypeReg,
mapTypeId: google.maps.MapTypeId.ROADMAP,
noClear: true,
zoom: 2,
minZoom: 2,
disableDefaultUI: true,
zoomControl: true,
center: {lat: 0, lng: 0}
};
if (options) {
if (options.mapType) {
mapInitOptions.mapTypeId = MAP_TYPES[options.mapType];
}
if (options.styles) {
mapInitOptions.styles = JSON.parse(options.styles);
}
if (options.controls) {
if (options.controls.zoom !== undefined) {
mapInitOptions.zoomControl = options.controls.zoom == true;
}
}
if (options.preferences) {
if (options.preferences.zoom) {
mapInitOptions.minZoom = options.preferences.zoom.minZoom;
if (options.preferences.zoom.maxZoom) {
mapInitOptions.maxZoom = options.preferences.zoom.maxZoom;
}
}
}
}
var map = new google.maps.Map(container, mapInitOptions);
map.mapTypes = mapTypeReg;
self.set('map', map);
var boundsLimit = null;
if (options.preferences && options.preferences.gestureBounds &&
options.preferences.gestureBounds.length > 0) {
boundsLimit = new google.maps.LatLngBounds();
options.preferences.gestureBounds.forEach(function(pos) {
boundsLimit.extend(pos);
});
}
map.set('boundsLimit', boundsLimit);
var timeoutError = setTimeout(function() {
self.trigger('load_error');
displayGrayMap(mapDiv);
}, 3000);
map.addListener('bounds_changed', function() {
var boundsLimit = map.get('boundsLimit');
if (!boundsLimit) {
return;
}
var visibleBounds = map.getBounds();
if (boundsLimit.intersects(visibleBounds) ||
visibleBounds.contains(boundsLimit.getNorthEast()) && visibleBounds.contains(boundsLimit.getSouthWest()) ||
boundsLimit.contains(visibleBounds.getNorthEast()) && boundsLimit.contains(visibleBounds.getSouthWest())) {
return;
}
var center = map.getCenter();
var dummyLat = center.lat(),
dummyLng = center.lng();
var ne = boundsLimit.getNorthEast(),
sw = boundsLimit.getSouthWest();
if (dummyLat < sw.lat() ) {
dummyLat = sw.lat();
} else if (dummyLat > ne.lat()) {
dummyLat = ne.lat();
}
if (dummyLng < 0) {
// the Western Hemisphere
if (dummyLng > ne.lng()) {
dummyLng = ne.lng();
} else if (dummyLng < sw.lng()) {
dummyLng = sw.lng();
}
} else {
// the Eastern Hemisphere
if (dummyLng > ne.lng()) {
dummyLng = ne.lng();
} else if (dummyLng < sw.lng()) {
dummyLng = sw.lng();
}
}
var dummyLatLng = new google.maps.LatLng(dummyLat, dummyLng);
map.panTo(dummyLatLng);
});
google.maps.event.addListenerOnce(map, 'projection_changed', function() {
clearTimeout(timeoutError);
self.trigger(event.MAP_READY);
map.addListener('idle', self._onCameraEvent.bind(self, 'camera_move_end'));
//map.addListener("bounce_changed", self._onCameraEvent.bind(self, 'camera_move'));
map.addListener('drag', self._onCameraEvent.bind(self, event.CAMERA_MOVE));
map.addListener('dragend', self._onCameraEvent.bind(self, event.CAMERA_MOVE_END));
map.addListener('dragstart', self._onCameraEvent.bind(self, event.CAMERA_MOVE_START));
map.addListener('click', function(evt) {
self._onMapEvent.call(self, event.MAP_CLICK, evt);
});
map.addListener('mousedown', function() {
map.set('mousedown_time', Date.now());
});
map.addListener('mouseup', function(evt) {
if (Date.now() - (map.get('mousedown_time') || Date.now()) > 500) {
self._onMapEvent.call(self, event.MAP_LONG_CLICK, evt);
}
});
map.addListener('drag', function(evt) {
self._onMapEvent.call(self, event.MAP_DRAG, evt);
});
map.addListener('dragend', function(evt) {
self._onMapEvent.call(self, event.MAP_DRAG_END, evt);
});
map.addListener('dragstart', function(evt) {
map.set('mousedown_time', undefined);
self._onMapEvent.call(self, event.MAP_DRAG_START, evt);
});
});
if (options) {
if (options.camera) {
if (options.camera.target) {
if (Array.isArray(options.camera.target)) {
var bounds = new google.maps.LatLngBounds();
options.camera.target.forEach(function(pos) {
bounds.extend(pos);
});
map.fitBounds(bounds, 5);
} else {
map.setCenter(options.camera.target);
}
if (typeof options.camera.tilt === 'number') {
map.setTilt(options.camera.tilt);
}
if (typeof options.camera.bearing === 'number') {
map.setHeading(options.camera.bearing);
}
if (typeof options.camera.zoom === 'number') {
map.setZoom(options.camera.zoom);
}
}
} else {
map.setCenter({lat: 0, lng: 0});
}
} else {
map.setCenter({lat: 0, lng: 0});
}
});
}
utils.extend(PluginMap, BaseClass);
PluginMap.prototype.setOptions = function(onSuccess, onError, args) {
var self = this;
var map = self.get('map'),
options = args[0];
var mapInitOptions = {};
if (options) {
if (options.mapType) {
mapInitOptions.mapTypeId = MAP_TYPES[options.mapType];
}
if (options.styles) {
mapInitOptions.styles = JSON.parse(options.styles);
}
if (options.controls) {
if (options.controls.zoom !== undefined) {
mapInitOptions.zoomControl = options.controls.zoom == true;
}
}
if (options.preferences) {
if (options.preferences.zoom) {
mapInitOptions.minZoom = Math.max(options.preferences.zoom || 2, 2);
if (options.preferences.zoom.maxZoom) {
mapInitOptions.maxZoom = options.preferences.zoom.maxZoom;
}
}
if ('gestureBounds' in options.preferences) {
var boundsLimit = null;
if (options.preferences.gestureBounds && options.preferences.gestureBounds.length > 0) {
boundsLimit = new google.maps.LatLngBounds();
options.preferences.gestureBounds.forEach(function(pos) {
boundsLimit.extend(pos);
});
}
map.set('boundsLimit', boundsLimit);
}
}
}
map.setOptions(mapInitOptions);
if (options) {
if (options.camera) {
if (options.camera.target) {
if (Array.isArray(options.camera.target)) {
var bounds = new google.maps.LatLngBounds();
options.camera.target.forEach(function(pos) {
bounds.extend(pos);
});
map.fitBounds(bounds, 5);
} else {
map.setCenter(options.camera.target);
}
if (typeof options.camera.tilt === 'number') {
map.setTilt(options.camera.tilt);
}
if (typeof options.camera.bearing === 'number') {
map.setHeading(options.camera.bearing);
}
if (typeof options.camera.zoom === 'number') {
map.setZoom(options.camera.zoom);
}
}
} else {
map.setCenter({lat: 0, lng: 0});
}
} else {
map.setCenter({lat: 0, lng: 0});
}
onSuccess();
};
PluginMap.prototype.setActiveMarkerId = function(onSuccess, onError, args) {
var self = this,
markerId = args[0];
self.activeMarker = self.objects[markerId];
onSuccess();
};
PluginMap.prototype.clear = function(onSuccess) {
this.activeMarker = null;
onSuccess();
};
PluginMap.prototype.getFocusedBuilding = function(onSuccess) {
// stub
onSuccess(-1);
};
PluginMap.prototype.setDiv = function(onSuccess, onError, args) {
var self = this,
map = self.get('map'),
container = self.get('container');
if (args.length === 0) {
if (container && container.parentNode) {
container.parentNode.removeAttribute('__pluginMapId');
container.parentNode.removeChild(container);
}
} else {
var domId = args[0];
var mapDiv = document.querySelector('[__pluginDomId=\'' + domId + '\']');
mapDiv.style.position = 'relative';
mapDiv.insertBefore(container, mapDiv.firstElementChild);
mapDiv.setAttribute('__pluginMapId', self.__pgmId);
}
google.maps.event.trigger(map, 'resize');
onSuccess();
};
PluginMap.prototype.resizeMap = function(onSuccess) {
var self = this;
var map = self.get('map');
google.maps.event.trigger(map, 'resize');
onSuccess();
};
PluginMap.prototype.panBy = function(onSuccess, onError, args) {
var self = this;
var map = self.get('map');
map.panBy.apply(map, args);
onSuccess();
};
PluginMap.prototype.setCameraBearing = function(onSuccess, onError, args) {
var self = this;
var map = self.get('map');
var heading = args[0];
map.setHeading(heading);
onSuccess();
};
PluginMap.prototype.setCameraZoom = function(onSuccess, onError, args) {
var self = this;
var map = self.get('map');
var zoom = args[0];
map.setZoom(zoom);
onSuccess();
};
PluginMap.prototype.setCameraTarget = function(onSuccess, onError, args) {
var self = this;
var map = self.get('map');
var lat = args[0],
lng = args[1];
map.setCenter(new google.maps.LatLng(lat, lng));
onSuccess();
};
PluginMap.prototype.setCameraTilt = function(onSuccess, onError, args) {
var self = this;
var map = self.get('map');
var tilt = args[0];
map.setTilt(tilt);
onSuccess();
};
PluginMap.prototype.setMyLocationEnabled = function(onSuccess) {
// stub
onSuccess();
};
PluginMap.prototype.stopAnimation = function(onSuccess) {
// stub
onSuccess();
};
PluginMap.prototype.animateCamera = function(onSuccess, onError, args) {
var self = this;
var map = self.get('map');
var options = args[0];
var padding = 'padding' in options ? options.padding : 5;
var bounds;
if (Array.isArray(options.target)) {
bounds = new google.maps.LatLngBounds();
options.target.forEach(function(pos) {
bounds.extend(pos);
});
map.fitBounds(bounds, padding);
} else {
var zoomFlag = typeof options.zoom === 'number';
var targetFlag = !!options.target;
if (zoomFlag && targetFlag) {
var projection = map.getProjection();
var centerLatLng = new google.maps.LatLng(options.target.lat, options.target.lng, true);
var centerPoint = projection.fromLatLngToPoint(centerLatLng);
var scale = Math.pow(2, options.zoom);
var div = map.getDiv();
var harfWidth = div.offsetWidth / 2;
var harfHeight = div.offsetHeight / 2;
var swPoint = new google.maps.Point((centerPoint.x * scale - harfWidth) / scale, (centerPoint.y * scale + harfHeight) / scale );
var nePoint = new google.maps.Point((centerPoint.x * scale + harfWidth) / scale, (centerPoint.y * scale - harfHeight) / scale);
var sw = projection.fromPointToLatLng(swPoint);
var ne = projection.fromPointToLatLng(nePoint);
bounds = new google.maps.LatLngBounds(sw, ne);
map.fitBounds(bounds, padding);
} else if (zoomFlag) {
map.setZoom(options.zoom);
} else if (targetFlag) {
map.panTo(options.target);
}
}
if (typeof options.tilt === 'number') {
map.setTilt(options.tilt);
}
if (typeof options.bearing === 'number') {
map.setHeading(options.bearing);
}
onSuccess();
};
PluginMap.prototype.moveCamera = function(onSuccess, onError, args) {
this.animateCamera.call(this, onSuccess, onError, args);
};
PluginMap.prototype.setMapTypeId = function(onSuccess, onError, args) {
var self = this;
var map = self.get('map');
var mapTypeId = args[0];
map.setMapTypeId(MAP_TYPES[mapTypeId]);
onSuccess();
};
PluginMap.prototype.setClickable = function(onSuccess, onError, args) {
var self = this;
var clickable = args[0];
self.set('clickable', clickable);
onSuccess();
};
PluginMap.prototype.setVisible = function(onSuccess, onError, args) {
var self = this;
var map = self.get('map');
var visibility = args[0];
var mapDiv = map.getDiv();
if (mapDiv) {
mapDiv.style.visibility = visibility === true ? 'visible' : 'hidden';
}
onSuccess();
};
PluginMap.prototype.setPadding = function(onSuccess) {
// stub
onSuccess();
};
PluginMap.prototype.setAllGesturesEnabled = function(onSuccess, onError, args) {
var self = this;
var map = self.get('map');
var enabled = args[0];
map.setOptions({
gestureHandling: enabled === true ? 'auto': 'none'
});
onSuccess();
};
PluginMap.prototype.setCompassEnabled = function(onSuccess, onError, args) {
var self = this;
var map = self.get('map');
var enabled = args[0];
map.setOptions({
rotateControl: enabled === true
});
var mapTypeId = map.getMapTypeId();
if (mapTypeId !== google.maps.MapTypeId.SATELLITE &&
mapTypeId !== google.maps.MapTypeId.HYBRID) {
console.warn('map.setCompassEnabled() works only HYBRID or SATELLITE for this platform.');
}
onSuccess();
};
PluginMap.prototype.setTrafficEnabled = function(onSuccess, onError, args) {
var self = this;
var map = self.get('map');
var enabled = args[0];
var trafficLayer = map.get('trafficLayer');
if (!trafficLayer) {
trafficLayer = new google.maps.TrafficLayer();
map.set('trafficLayer', trafficLayer);
}
if (enabled) {
trafficLayer.setMap(map);
} else {
trafficLayer.setMap(null);
}
onSuccess();
};
PluginMap.prototype.fromLatLngToPoint = function(onSuccess, onError, args) {
var self = this;
var map = self.get('map');
var lat = args[0],
lng = args[1];
var projection = map.getProjection(),
bounds = map.getBounds(),
ne = bounds.getNorthEast(),
sw = bounds.getSouthWest(),
zoom = map.getZoom(),
north = ne.lat(),
west = sw.lng();
var nowrapFlag = !bounds.contains(new google.maps.LatLng(north, 179));
var scale = Math.pow(2, zoom),
topLeft = projection.fromLatLngToPoint(new google.maps.LatLng(north, west + 360, nowrapFlag)),
worldPoint = projection.fromLatLngToPoint(new google.maps.LatLng(lat, lng + 360, true));
onSuccess([(worldPoint.x - topLeft.x) * scale, (worldPoint.y - topLeft.y) * scale]);
};
PluginMap.prototype.fromPointToLatLng = function(onSuccess, onError, args) {
var self = this;
var map = self.get('map');
var x = args[0],
y = args[1];
var projection = map.getProjection(),
bounds = map.getBounds(),
ne = bounds.getNorthEast(),
sw = bounds.getSouthWest(),
zoom = map.getZoom();
var topRight = projection.fromLatLngToPoint(ne);
var bottomLeft = projection.fromLatLngToPoint(sw);
var scale = Math.pow(2, zoom);
var worldPoint = new google.maps.Point(x / scale + bottomLeft.x, y / scale + topRight.y);
var latLng = map.getProjection().fromPointToLatLng(worldPoint);
onSuccess([latLng.lat(), latLng.lng()]);
};
PluginMap.prototype.setIndoorEnabled = function(onSuccess) {
// stub
onSuccess();
};
PluginMap.prototype.toDataURL = function(onSuccess) {
// stub
onSuccess();
};
PluginMap.prototype._syncInfoWndPosition = function() {
var self = this;
if (!self.activeMarker) {
return;
}
var latLng = self.activeMarker.getPosition();
self.fromLatLngToPoint(function(point) {
plugin.google.maps[self.__pgmId]({
'evtName': 'syncPosition',
'callback': '_onSyncInfoWndPosition',
'args': [{'x': point[0], 'y': point[1]}]
});
}, null, [latLng.lat(), latLng.lng()]);
};
PluginMap.prototype._onMapEvent = function(evtName, evt) {
var self = this;
if (self.get('clickable') === false &&
(evtName === event.MAP_CLICK || evtName === event.MAP_LONG_CLICK)) {
evt.stop();
return;
}
if (self.__pgmId in plugin.google.maps) {
if (evt) {
if (evtName === event.MAP_CLICK) {
if (evt.placeId) {
evt.stop();
plugin.google.maps[self.__pgmId]({
'evtName': event.POI_CLICK,
'callback': '_onMapEvent',
'args': [evt.placeId, undefined, new LatLng(evt.latLng.lat(), evt.latLng.lng())]
});
return;
}
}
plugin.google.maps[self.__pgmId]({
'evtName': evtName,
'callback': '_onMapEvent',
'args': [new LatLng(evt.latLng.lat(), evt.latLng.lng())]
});
} else {
plugin.google.maps[self.__pgmId]({
'evtName': evtName,
'callback': '_onMapEvent',
'args': []
});
}
}
};
PluginMap.prototype._onCameraEvent = function(evtName) {
var self = this,
map = self.get('map'),
center = map.getCenter(),
bounds = map.getBounds(),
ne = bounds.getNorthEast(),
sw = bounds.getSouthWest();
self._syncInfoWndPosition();
var cameraInfo = {
'target': {'lat': center.lat(), 'lng': center.lng()},
'zoom': map.getZoom(),
'tilt': map.getTilt() || 0,
'bearing': map.getHeading() || 0,
'northeast': {'lat': ne.lat(), 'lng': ne.lng()},
'southwest': {'lat': sw.lat(), 'lng': sw.lng()},
'farLeft': {'lat': ne.lat(), 'lng': sw.lng()},
'farRight': {'lat': ne.lat(), 'lng': ne.lng()}, // = northEast
'nearLeft': {'lat': sw.lat(), 'lng': sw.lng()}, // = southWest
'nearRight': {'lat': sw.lat(), 'lng': ne.lng()}
};
if (self.__pgmId in plugin.google.maps) {
plugin.google.maps[self.__pgmId]({
'evtName': evtName,
'callback': '_onCameraEvent',
'args': [cameraInfo]
});
}
};
PluginMap.prototype.loadPlugin = function(onSuccess, onError, args) {
var self = this;
var className = args[0];
var plugin;
if (className in self.PLUGINS) {
plugin = self.PLUGINS[className];
} else {
var OverlayClass = require('cordova-plugin-googlemaps.Plugin' + className);
plugin = new OverlayClass(this);
self.PLUGINS[className] = plugin;
// Since Cordova involes methods as Window,
// the `this` keyword of involved method is Window, not overlay itself.
// In order to keep indicate the `this` keyword as overlay itself,
// wrap the method.
var dummyObj = {};
for (var key in OverlayClass.prototype) {
if (typeof OverlayClass.prototype[key] === 'function') {
dummyObj[key] = plugin[key].bind(plugin);
} else {
dummyObj[key] = plugin[key];
}
}
require('cordova/exec/proxy').add(self.__pgmId + '-' + className.toLowerCase(), dummyObj);
}
plugin._create.call(plugin, onSuccess, onError, args);
};
module.exports = PluginMap;
+489
View File
@@ -0,0 +1,489 @@
var utils = require('cordova/utils'),
event = require('cordova-plugin-googlemaps.event'),
BaseClass = require('cordova-plugin-googlemaps.BaseClass'),
LatLng = require('cordova-plugin-googlemaps.LatLng');
function PluginMarker(pluginMap) {
var self = this;
BaseClass.apply(self);
Object.defineProperty(self, 'pluginMap', {
value: pluginMap,
writable: false
});
Object.defineProperty(self, 'infoWnd', {
value: null,
writable: true
});
}
utils.extend(PluginMarker, BaseClass);
PluginMarker.prototype._create = function(onSuccess, onError, args) {
var self = this,
markerId = 'marker_' + args[2],
pluginOptions = args[1];
self.__create.call(self, markerId, pluginOptions, function(marker, properties) {
onSuccess(properties);
}, onError);
};
/*eslint-disable no-unused-vars*/
PluginMarker.prototype.__create = function(markerId, pluginOptions, onSuccess, onError) {
/*eslint-enable no-unused-vars*/
var self = this,
map = self.pluginMap.get('map');
var markerOpts = {
'overlayId': markerId,
'position': pluginOptions.position,
'disableAutoPan': pluginOptions.disableAutoPan,
'draggable': pluginOptions.draggable,
'visible': pluginOptions.visible
};
var iconSize = null;
if (pluginOptions.animation) {
markerOpts.animation = google.maps.Animation[pluginOptions.animation.toUpperCase()];
}
if (pluginOptions.icon) {
var icon = pluginOptions.icon;
markerOpts.icon = {};
if (Array.isArray(pluginOptions.icon) && pluginOptions.icon.length === 4) {
pluginOptions.icon = {
'url': pluginOptions.icon
};
}
if (typeof pluginOptions.icon === 'string') {
// Specifies path or url to icon image
markerOpts.icon.url = pluginOptions.icon;
} else if (typeof pluginOptions.icon === 'object') {
if (Array.isArray(pluginOptions.icon.url)) {
// Specifies color name or rule
markerOpts.icon = {
'path': 'm12 0c-4.4183 2.3685e-15 -8 3.5817-8 8 0 1.421 0.3816 2.75 1.0312 3.906 0.1079 0.192 0.221 0.381 0.3438 0.563l6.625 11.531 6.625-11.531c0.102-0.151 0.19-0.311 0.281-0.469l0.063-0.094c0.649-1.156 1.031-2.485 1.031-3.906 0-4.4183-3.582-8-8-8zm0 4c2.209 0 4 1.7909 4 4 0 2.209-1.791 4-4 4-2.2091 0-4-1.791-4-4 0-2.2091 1.7909-4 4-4z',
'fillColor': 'rgb(' + pluginOptions.icon.url[0] + ',' + pluginOptions.icon.url[1] + ',' + pluginOptions.icon.url[2] + ')',
'fillOpacity': pluginOptions.icon.url[3] / 255,
'scale': 1.3,
'strokeWeight': 1,
'strokeColor': 'rgb(255, 255, 255)',
'strokeOpacity': 0.65,
'anchor': new google.maps.Point(12, 27)
};
iconSize = {
'width': 22,
'height': 28
};
} else {
markerOpts.icon.url = pluginOptions.icon.url;
if (pluginOptions.icon.size) {
markerOpts.icon.scaledSize = new google.maps.Size(icon.size.width, icon.size.height);
iconSize = icon.size;
}
}
if ('anchor' in icon && Array.isArray(icon.anchor)) {
markerOpts.icon.anchor = new google.maps.Point(icon.anchor[0], icon.anchor[1]);
}
}
}
if (!markerOpts.icon ||
!markerOpts.icon.url && !markerOpts.icon.path) {
// default marker
markerOpts.icon = {
'path': 'm12 0c-4.4183 2.3685e-15 -8 3.5817-8 8 0 1.421 0.3816 2.75 1.0312 3.906 0.1079 0.192 0.221 0.381 0.3438 0.563l6.625 11.531 6.625-11.531c0.102-0.151 0.19-0.311 0.281-0.469l0.063-0.094c0.649-1.156 1.031-2.485 1.031-3.906 0-4.4183-3.582-8-8-8zm0 4c2.209 0 4 1.7909 4 4 0 2.209-1.791 4-4 4-2.2091 0-4-1.791-4-4 0-2.2091 1.7909-4 4-4z',
'fillColor': 'rgb(255, 0, 0)',
'fillOpacity': 1,
'scale': 1.3,
'strokeWeight': 1,
'strokeColor': 'rgb(255, 255, 255)',
'strokeOpacity': 0.65,
'anchor': new google.maps.Point(12, 27)
};
iconSize = {
'width': 22,
'height': 28
};
}
if ('zIndex' in pluginOptions) {
markerOpts.zIndex = pluginOptions.zIndex;
}
markerOpts.map = map;
var marker = new google.maps.Marker(markerOpts);
marker.addListener('click', self.onMarkerClickEvent.bind(self, event.MARKER_CLICK, marker), {passive: true});
marker.addListener('dragstart', self.onMarkerEvent.bind(self, event.MARKER_DRAG_START, marker));
marker.addListener('drag', self.onMarkerEvent.bind(self, event.MARKER_DRAG, marker));
marker.addListener('dragend', self.onMarkerEvent.bind(self, event.MARKER_DRAG_END, marker));
if (pluginOptions.title) {
marker.set('title', pluginOptions.title);
}
if (pluginOptions.snippet) {
marker.set('snippet', pluginOptions.snippet);
}
self.pluginMap.objects[markerId] = marker;
self.pluginMap.objects['marker_property_' + markerId] = markerOpts;
if (iconSize) {
onSuccess(marker, {
'__pgmId': markerId,
'width': iconSize.width,
'height': iconSize.height
});
} else {
var markerIcon = marker.getIcon();
if (markerIcon && markerIcon.size) {
onSuccess({
'__pgmId': markerId,
'width': markerIcon.size.width,
'height': markerIcon.size.height
});
} else {
var img = new Image();
img.onload = function() {
onSuccess(marker, {
'__pgmId': markerId,
'width': img.width,
'height': img.height
});
};
img.onerror = function(error) {
console.warn(error.getMessage());
onSuccess(marker, {
'__pgmId': markerId,
'width': 20,
'height': 42
});
};
if (typeof markerOpts.icon === 'string') {
img.src = markerOpts.icon;
} else {
img.src = markerOpts.icon.url;
}
}
}
setTimeout(function() {
marker.setAnimation(null);
}, 500);
};
PluginMarker.prototype._removeMarker = function(marker) {
marker.setMap(null);
marker = undefined;
};
PluginMarker.prototype.setDisableAutoPan = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var disableAutoPan = args[1];
var marker = self.pluginMap.objects[overlayId];
if (marker) {
marker.set('disableAutoPan', disableAutoPan);
}
onSuccess();
};
PluginMarker.prototype.setFlat = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var flat = args[1];
var marker = self.pluginMap.objects[overlayId];
if (marker) {
marker.set('flat', flat);
}
onSuccess();
};
PluginMarker.prototype.setVisible = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var visible = args[1];
var marker = self.pluginMap.objects[overlayId];
if (marker) {
marker.setVisible(visible);
}
onSuccess();
};
PluginMarker.prototype.setAnimation = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var animation = args[1];
var marker = self.pluginMap.objects[overlayId];
if (marker) {
marker.setAnimation(google.maps.Animation[animation]);
setTimeout(function() {
marker.setAnimation(null);
}, 500);
}
onSuccess();
};
PluginMarker.prototype.setRotation = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var rotation = args[1];
var marker = self.pluginMap.objects[overlayId];
if (marker) {
var icon = marker.getIcon();
if (icon && icon.path) {
icon.rotation = rotation;
marker.setIcon(icon);
}
}
onSuccess();
};
PluginMarker.prototype.setDraggable = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var marker = self.pluginMap.objects[overlayId];
if (marker) {
marker.setDraggable(args[1]);
}
onSuccess();
};
PluginMarker.prototype.setInfoWindowAnchor = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var marker = self.pluginMap.objects[overlayId];
if (marker) {
var icon = marker.getIcon();
var anchorX = args[1];
anchorX = anchorX - icon.size.width / 2;
var anchorY = args[2];
anchorY = anchorY - icon.size.height / 2;
marker.setOptions({
'anchorPoint': new google.maps.Point(anchorX, anchorY)
});
if (self.infoWnd) {
self._showInfoWindow.call(self, marker);
}
}
onSuccess();
};
PluginMarker.prototype.setTitle = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var title = args[1];
var marker = self.pluginMap.objects[overlayId];
if (marker) {
marker.set('title', title);
}
onSuccess();
};
PluginMarker.prototype.setSnippet = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var snippet = args[1];
var marker = self.pluginMap.objects[overlayId];
if (marker) {
marker.set('snippet', snippet);
}
onSuccess();
};
PluginMarker.prototype.setOpacity = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var opacity = args[1];
var marker = self.pluginMap.objects[overlayId];
if (marker) {
marker.setOpacity(opacity);
}
onSuccess();
};
PluginMarker.prototype.setIconAnchor = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var anchorX = args[1],
anchorY = args[2];
var marker = self.pluginMap.objects[overlayId];
if (marker) {
var icon = marker.getIcon();
if (typeof icon === 'string') {
icon = {
'url': icon
};
}
icon.anchor = new google.maps.Point(anchorX, anchorY);
marker.setIcon(icon);
}
onSuccess();
};
PluginMarker.prototype.setZIndex = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var zIndex = args[1];
var marker = self.pluginMap.objects[overlayId];
if (marker) {
marker.setZIndex(zIndex);
}
onSuccess();
};
PluginMarker.prototype.showInfoWindow = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var marker = self.pluginMap.objects[overlayId];
if (marker) {
if (self.pluginMap.activeMarker && self.pluginMap.activeMarker !== marker) {
self.onMarkerClickEvent(event.INFO_CLICK, self.pluginMap.activeMarker);
}
self.pluginMap.activeMarker = marker;
self._showInfoWindow.call(self, marker);
}
onSuccess();
};
PluginMarker.prototype.hideInfoWindow = function(onSuccess) {
var self = this;
if (self.infoWnd) {
google.maps.event.trigger(self.infoWnd, 'closeclick');
self.infoWnd.close();
self.infoWnd = null;
}
onSuccess();
};
PluginMarker.prototype.setIcon = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var marker = self.pluginMap.objects[overlayId];
self.setIcon_(marker, args[1])
.then(onSuccess)
.catch(onError);
};
PluginMarker.prototype.setIcon_ = function(marker, iconOpts) {
return new Promise(function(resolve) {
if (marker) {
if (Array.isArray(iconOpts)) {
// Specifies color name or rule
iconOpts = {
'path': 'm12 0c-4.4183 2.3685e-15 -8 3.5817-8 8 0 1.421 0.3816 2.75 1.0312 3.906 0.1079 0.192 0.221 0.381 0.3438 0.563l6.625 11.531 6.625-11.531c0.102-0.151 0.19-0.311 0.281-0.469l0.063-0.094c0.649-1.156 1.031-2.485 1.031-3.906 0-4.4183-3.582-8-8-8zm0 4c2.209 0 4 1.7909 4 4 0 2.209-1.791 4-4 4-2.2091 0-4-1.791-4-4 0-2.2091 1.7909-4 4-4z',
'fillColor': 'rgb(' + iconOpts[0] + ',' + iconOpts[1] + ',' + iconOpts[2] + ')',
'fillOpacity': iconOpts[3] / 256,
'scale': 1.3,
'strokeWeight': 0,
'anchor': new google.maps.Point(12, 27)
};
} else if (typeof iconOpts === 'object') {
if (typeof iconOpts.size === 'object') {
iconOpts.size = new google.maps.Size(iconOpts.size.width, iconOpts.size.height, 'px', 'px');
iconOpts.scaledSize = iconOpts.size;
}
if (Array.isArray(iconOpts.anchor)) {
iconOpts.anchor = new google.maps.Point(iconOpts.anchor[0], iconOpts.anchor[1]);
}
}
marker.setIcon(iconOpts);
}
resolve();
});
};
PluginMarker.prototype.setPosition = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var marker = self.pluginMap.objects[overlayId];
if (marker) {
marker.setPosition({'lat': args[1], 'lng': args[2]});
}
onSuccess();
};
PluginMarker.prototype.remove = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var marker = self.pluginMap.objects[overlayId];
if (marker) {
google.maps.event.clearInstanceListeners(marker);
marker.setMap(null);
marker = undefined;
self.pluginMap.objects[overlayId] = undefined;
delete self.pluginMap.objects[overlayId];
delete self.pluginMap.objects['marker_property_' + overlayId];
}
onSuccess();
};
PluginMarker.prototype.onMarkerEvent = function(evtName, marker) {
var self = this,
mapId = self.pluginMap.__pgmId;
if (mapId in plugin.google.maps) {
var latLng = marker.getPosition();
plugin.google.maps[mapId]({
'evtName': evtName,
'callback': '_onMarkerEvent',
'args': [marker.overlayId, new LatLng(latLng.lat(), latLng.lng())]
});
}
};
PluginMarker.prototype._showInfoWindow = function(marker) {
var self = this;
if (!self.infoWnd) {
self.infoWnd = new google.maps.InfoWindow({
'pixelOffset': new google.maps.Size(0, 0)
});
}
var container = document.createElement('div');
if (self.pluginMap.activeMarker && self.pluginMap.activeMarker !== marker) {
self.onMarkerClickEvent(event.INFO_CLICK, self.pluginMap.activeMarker);
}
self.pluginMap.activeMarker = marker;
self.pluginMap._syncInfoWndPosition.call(self);
var maxWidth = marker.getMap().getDiv().offsetWidth * 0.7;
var html = [];
if (marker.get('title')) {
html.push(marker.get('title'));
}
if (marker.get('snippet')) {
html.push('<small>' + marker.get('snippet') + '</small>');
}
if (html.length > 0) {
container.innerHTML = html.join('<br>');
google.maps.event.addListenerOnce(self.infoWnd, 'domready', function() {
self.onMarkerClickEvent(event.INFO_OPEN, marker);
if (container.parentNode) {
google.maps.event.addDomListener(container.parentNode.parentNode.parentNode, 'click', function() {
self.onMarkerClickEvent(event.INFO_CLICK, marker);
}, true);
}
});
self.infoWnd.setOptions({
content: container,
disableAutoPan: marker.disableAutoPan,
maxWidth: maxWidth
});
google.maps.event.addListener(self.infoWnd, 'closeclick', function() {
google.maps.event.clearInstanceListeners(self.infoWnd);
self.onMarkerClickEvent(event.INFO_CLOSE, marker);
});
self.infoWnd.open(marker.getMap(), marker);
}
};
PluginMarker.prototype.onMarkerClickEvent = function(evtName, marker) {
var self = this;
var overlayId = marker.get('overlayId');
if (self.pluginMap.activeMarker && self.pluginMap.activeMarker !== marker) {
self.onMarkerEvent(event.INFO_CLOSE, self.pluginMap.activeMarker);
}
self.pluginMap.activeMarker = marker;
if (marker.get('disableAutoPan') === false) {
self.pluginMap.get('map').panTo(marker.getPosition());
}
if (overlayId.indexOf('markercluster_') > -1) {
self.onClusterEvent(evtName, marker);
} else {
self.onMarkerEvent(evtName, marker);
}
};
module.exports = PluginMarker;
@@ -0,0 +1,714 @@
var utils = require('cordova/utils'),
event = require('cordova-plugin-googlemaps.event'),
PluginMarker = require('cordova-plugin-googlemaps.PluginMarker'),
Thread = require('cordova-plugin-googlemaps.Thread'),
LatLng = require('cordova-plugin-googlemaps.LatLng'),
BaseArrayClass = require('cordova-plugin-googlemaps.BaseArrayClass');
var STATUS = {
'WORKING': 0,
'CREATED': 1,
'DELETED': 2
};
function PluginMarkerCluster(pluginMap) {
var self = this;
PluginMarker.call(self, pluginMap);
Object.defineProperty(self, 'pluginMarkers', {
value: {},
writable: false
});
Object.defineProperty(self, 'debugFlags', {
value: {},
writable: false
});
var deleteMarkers = new BaseArrayClass();
Object.defineProperty(self, 'deleteMarkers', {
value: deleteMarkers,
writable: false
});
deleteMarkers.on('insert_at', function() {
var key = deleteMarkers.removeAt(0);
var marker = self.pluginMap.objects[key];
if (marker) {
self._removeMarker(marker);
}
self.pluginMap.objects[key] = undefined;
self.pluginMap.objects['marker_property_' + key] = undefined;
self.pluginMarkers[key] = undefined;
delete self.pluginMap.objects[key];
delete self.pluginMap.objects['marker_property_' + key];
delete self.pluginMarkers[key];
self.pluginMarkers[key] = STATUS.DELETED;
});
}
utils.extend(PluginMarkerCluster, PluginMarker);
PluginMarkerCluster.prototype._create = function(onSuccess, onError, args) {
var self = this,
params = args[1],
hashCode = args[2],
positionList = params.positionList;
//---------------------------------------------
// Calculate geocell hashCode on multi thread
//---------------------------------------------
var tasks = [];
while(positionList.length > 0) {
var list = positionList.splice(0, 10);
tasks.push(new Promise(function(resolve, reject) {
var thread = new Thread(function(params) {
var GEOCELL_GRID_SIZE = 4;
var GEOCELL_ALPHABET = '0123456789abcdef';
function getGeocell(lat, lng, resolution) {
var north = 90.0,
south = -90.0,
east = 180.0,
west = -180.0,
subcell_lng_span, subcell_lat_span,
x, y, cell = [];
while(cell.length < resolution + 1) {
subcell_lng_span = (east - west) / GEOCELL_GRID_SIZE;
subcell_lat_span = (north - south) / GEOCELL_GRID_SIZE;
x = Math.min(Math.floor(GEOCELL_GRID_SIZE * (lng - west) / (east - west)), GEOCELL_GRID_SIZE - 1);
y = Math.min(Math.floor(GEOCELL_GRID_SIZE * (lat - south) / (north - south)), GEOCELL_GRID_SIZE - 1);
cell.push(_subdiv_char(x, y));
south += subcell_lat_span * y;
north = south + subcell_lat_span;
west += subcell_lng_span * x;
east = west + subcell_lng_span;
}
return cell.join('');
}
function _subdiv_char(posX, posY) {
return GEOCELL_ALPHABET.charAt(
(posY & 2) << 2 |
(posX & 2) << 1 |
(posY & 1) << 1 |
(posX & 1) << 0);
}
return params.positionList.map(function(position) {
return getGeocell(position.lat, position.lng, params.resolution);
});
});
thread
.once({
'positionList': list,
'resolution': 12
})
.done(resolve)
.fail(reject);
}));
}
Promise.all(tasks)
.then(function(results) {
var id = 'markerclister_' + hashCode;
self.debugFlags[id] = params.debug;
var result = {
'geocellList': Array.prototype.concat.apply([], results),
'hashCode': hashCode,
'__pgmId': id
};
onSuccess(result);
})
.catch(onError);
};
PluginMarkerCluster.prototype.redrawClusters = function(onSuccess, onError, args) {
var self = this;
var updateClusterIDs = [],
changeProperties = {},
clusterId = args[0],
isDebug = self.debugFlags[clusterId],
params = args[1],
map = self.pluginMap.get('map');
if ('new_or_update' in params) {
//---------------------------
// Determine new or update
//---------------------------
params.new_or_update.forEach(function(clusterData) {
var positionJSON = clusterData.position,
markerId = clusterData.__pgmId,
clusterId_markerId = clusterId + '-' + markerId;
// Save the marker properties
self.pluginMap.objects['marker_property_' + clusterId_markerId] = clusterData;
// Set the WORKING status flag
self.pluginMarkers[clusterId_markerId] = STATUS.WORKING;
updateClusterIDs.push(clusterId_markerId);
// Prepare the marker properties for addMarker()
var properties = {
'lat': positionJSON.lat,
'lng': positionJSON.lng,
'__pgmId': clusterId_markerId
};
if ('title' in clusterData) {
properties.title = clusterData.title;
}
if ('icon' in clusterData) {
var iconObj = clusterData.icon,
iconProperties = {},
label;
if (typeof iconObj === 'string') {
iconProperties.url = iconObj;
properties.icon = iconProperties;
} else if (typeof iconObj === 'object') {
iconProperties = iconObj;
if (clusterData.isClusterIcon) {
if (iconObj.label) {
label = iconObj.label;
if (isDebug) {
label.text = markerId;
} else {
label.text = clusterData.count;
}
} else {
label = {};
if (isDebug) {
label.text = markerId;
} else {
label.fontSize = 15;
label.bold = true;
label.text = clusterData.count;
}
}
iconProperties.label = label;
}
if ('anchor' in iconObj) {
iconProperties.anchor = iconObj.anchor;
}
if ('infoWindowAnchor' in iconObj) {
iconProperties.anchor = iconObj.infoWindowAnchor;
}
properties.icon = iconProperties;
}
}
changeProperties[clusterId_markerId] = JSON.parse(JSON.stringify(properties));
});
if (updateClusterIDs.length === 0) {
self.deleteProcess(clusterId, params);
onSuccess({});
return;
}
//---------------------------
// mapping markers on the map
//---------------------------
var allResults = {};
//---------------
// new or update
//---------------
var tasks = [];
updateClusterIDs.forEach(function(clusterId_markerId) {
self.pluginMarkers[clusterId_markerId] = STATUS.WORKING;
var isNew = !(clusterId_markerId in self.pluginMap.objects);
// Get the marker properties
var markerProperties = changeProperties[clusterId_markerId],
properties, marker;
if (clusterId_markerId.indexOf('-marker_') > -1) {
//-------------------
// regular marker
//-------------------
if (isNew) {
properties = self.pluginMap.objects['marker_property_' + clusterId_markerId];
tasks.push(new Promise(function(resolve) {
self.__create.call(self, clusterId_markerId, {
'position': properties.position,
'icon': properties.icon,
'disableAutoPan': properties.disableAutoPan
}, function(marker, properties) {
if (markerProperties.title) {
marker.set('title', markerProperties.title);
}
if (markerProperties.snippet) {
marker.set('snippet', markerProperties.snippet);
}
if (self.pluginMarkers[clusterId_markerId] === STATUS.DELETED) {
self._removeMarker.call(self, marker);
delete self.pluginMarkers[clusterId_markerId];
resolve();
} else {
self.pluginMarkers[clusterId_markerId] = STATUS.CREATED;
allResults[clusterId_markerId.split('-')[1]] = {
'width': properties.width,
'height': properties.height
};
resolve();
}
});
}));
} else {
marker = self.pluginMap.objects[clusterId_markerId];
//----------------------------------------
// Set the title and snippet properties
//----------------------------------------
if (markerProperties.title) {
marker.set('title', markerProperties.title);
}
if (markerProperties.snippet) {
marker.set('snippet', markerProperties.snippet);
}
if (self.pluginMarkers[clusterId_markerId] === STATUS.DELETED) {
self._removeMarker.call(self, marker);
delete self.pluginMarkers[clusterId_markerId];
} else {
self.pluginMarkers[clusterId_markerId] = STATUS.CREATED;
}
}
} else {
//--------------------------
// cluster icon
//--------------------------
if (isNew) {
// If the requested id is new location, create a marker
marker = newClusterIcon({
'map': map,
'position': {
'lat': markerProperties.lat,
'lng': markerProperties.lng
},
'overlayId': clusterId_markerId,
'opacity': 0
});
marker.addListener('click', self.onClusterEvent.bind(self, 'cluster_click', marker));
// Store the marker instance with markerId
self.pluginMap.objects[clusterId_markerId] = marker;
} else {
marker = self.pluginMap.objects[clusterId_markerId];
}
//----------------------------------------
// Set the title and snippet properties
//----------------------------------------
if (markerProperties.title) {
marker.set('title', markerProperties.title);
}
if (markerProperties.snippet) {
marker.set('snippet', markerProperties.snippet);
}
if (markerProperties.icon) {
var icon = markerProperties.icon;
tasks.push(new Promise(function(resolve) {
self.setIconToClusterMarker.call(self, clusterId_markerId, marker, icon)
.then(function() {
//--------------------------------------
// Marker was updated
//--------------------------------------
marker.setVisible(true);
self.pluginMarkers[clusterId_markerId] = STATUS.CREATED;
resolve();
})
.catch(function(error) {
//--------------------------------------
// Could not read icon for some reason
//--------------------------------------
if (marker.get('overlayId')) {
self._removeMarker.call(self, marker);
}
self.pluginMarkers[clusterId_markerId] = STATUS.DELETED;
console.warn(error.getMessage());
self.deleteMarkers.push(clusterId_markerId);
resolve();
});
}));
} else {
//--------------------
// No icon for marker
//--------------------
self.pluginMarkers[clusterId_markerId] = STATUS.CREATED;
}
}
});
Promise.all(tasks).then(function() {
self.deleteProcess(clusterId, params);
onSuccess(allResults);
}).catch(onError);
}
};
PluginMarkerCluster.prototype.deleteProcess = function(clusterId, params) {
var self = this;
if (!params.delete || params.delete.length === 0) {
return;
}
params.delete.forEach(function(key) {
self.deleteMarkers.push(clusterId + '-' + key);
});
};
PluginMarkerCluster.prototype.setIconToClusterMarker = function(markerId, marker, iconProperty) {
var self = this;
return new Promise(function(resolve, reject) {
if (self.pluginMarkers[markerId] === STATUS.DELETED) {
self._removeMarker.call(self, marker);
delete self.pluginMap.objects[markerId];
delete self.pluginMap.objects['marker_property_' + markerId];
delete self.pluginMarkers[markerId];
reject('marker has been removed');
return;
}
self.setIcon_.call(self, marker, iconProperty)
.then(function() {
if (self.pluginMarkers[markerId] === STATUS.DELETED) {
self._removeMarker.call(self, marker);
delete self.pluginMap.objects[markerId];
delete self.pluginMap.objects['marker_property_' + markerId];
self.pluginMarkers.remove(markerId);
resolve();
return;
}
marker.setVisible(true);
self.pluginMarkers[markerId] = STATUS.CREATED;
resolve();
});
});
};
PluginMarkerCluster.prototype.remove = function(onSuccess, onError, args) {
var self = this,
clusterId = args[0],
keys = Object.keys(self.pluginMarkers);
keys.forEach(function(key) {
if (key.indexOf(clusterId) === 0) {
if (self.pluginMap.objects[key]) {
self.pluginMap.objects[key].setMap(null);
}
self.pluginMarkers[key] = STATUS.DELETED;
delete self.pluginMap.objects[key];
delete self.pluginMap.objects['marker_property_' + key];
}
});
onSuccess();
};
PluginMarkerCluster.prototype.onClusterEvent = function(evtName, marker) {
var self = this,
mapId = self.pluginMap.__pgmId;
var overlayId = marker.get('overlayId');
var tmp = overlayId.split('-');
var clusterId = tmp[0];
var markerId = tmp[1];
var latLng = marker.getPosition();
if (mapId in plugin.google.maps) {
if (self.pluginMap.activeMarker) {
self.onMarkerEvent(event.INFO_CLOSE, self.pluginMap.activeMarker);
}
plugin.google.maps[mapId]({
'evtName': evtName,
'callback': '_onClusterEvent',
'args': [clusterId, markerId, new LatLng(latLng.lat(), latLng.lng())]
});
}
};
module.exports = PluginMarkerCluster;
function newClusterIcon(options) {
// https://github.com/apache/cordova-js/blob/c75e8059114255d1dbae1ede398e6626708ee9f3/src/common/utils.js#L167
if (ClusterIconClass.__super__ !== google.maps.OverlayView.prototype) {
var key, defined = {};
for (key in ClusterIconClass.prototype) {
defined[key] = ClusterIconClass.prototype[key];
}
utils.extend(ClusterIconClass, google.maps.OverlayView);
for (key in defined) {
ClusterIconClass.prototype[key] = defined[key];
}
}
return new ClusterIconClass(options);
}
function ClusterIconClass(options) {
google.maps.OverlayView.apply(this);
var self = this;
//-----------------------------------------
// Create a canvas to draw label
//-----------------------------------------
var canvas = document.createElement('canvas');
canvas.width = 50;
canvas.height = 50;
canvas.style.visibility = 'hidden';
canvas.style.position = 'absolute';
canvas.style.zIndex = -100;
canvas.setAttribute('id', 'canvas_' + options.overlayId);
self.set('canvas', canvas);
//-----------------------------------------
// Create two markers for icon and label
//-----------------------------------------
var iconMarker = new google.maps.Marker({
'clickable': false,
'icon': options.icon,
'zIndex': 0,
'opacity': 0
});
var labelMarker = new google.maps.Marker({
'clickable': true,
'zIndex': 1,
'icon': self.get('label'),
'opacity': 0
});
labelMarker.addListener('click', function() {
google.maps.event.trigger(self, 'click');
});
self.set('iconMarker', iconMarker);
self.set('labelMarker', labelMarker);
self.set('opacity', 0);
iconMarker.bindTo('opacity', labelMarker);
iconMarker.bindTo('visible', labelMarker);
iconMarker.bindTo('position', labelMarker);
iconMarker.bindTo('map', labelMarker);
self.bindTo('opacity', iconMarker);
self.bindTo('visible', iconMarker);
self.bindTo('map', iconMarker);
self.bindTo('position', iconMarker);
self.set('labelMarkerAnchor', new google.maps.Point(canvas.width / 2, canvas.height / 2));
self.addListener('icon_changed', function() {
var icon = self.get('icon');
if (typeof icon === 'string') {
icon = {
'url': icon
};
}
var iconUrl = icon.url;
if (typeof icon === 'object') {
if (typeof icon.size === 'object' &&
icon.size.width && icon.size.height) {
icon.anchor = new google.maps.Point(icon.size.width / 2, icon.size.height / 2);
iconMarker.setIcon(icon);
return;
}
}
var img = new Image();
img.onload = function() {
icon.size = new google.maps.Size(img.width, img.height);
icon.scaledSize = new google.maps.Size(img.width, img.height);
icon.anchor = new google.maps.Point(img.width / 2, img.height / 2);
self.set('labelMarkerAnchor', new google.maps.Point(img.width / 2, img.height / 2));
iconMarker.setIcon(icon);
};
img.onerror = function(e) {
console.error(e);
};
img.src = iconUrl;
});
//debug
//var positionConfirmMarker = new google.maps.Marker();
//labelMarker.bindTo('position', positionConfirmMarker);
//labelMarker.bindTo('map', positionConfirmMarker);
for (var key in options) {
self.set(key, options[key]);
}
}
ClusterIconClass.prototype.onAdd = function() {
var self = this;
var canvas = self.get('canvas');
self.get('map').getDiv().appendChild(canvas);
};
ClusterIconClass.prototype.onRemove = function() {
var self = this;
var canvas = self.get('canvas');
self.set('map', null);
google.maps.event.clearInstanceListeners(self.get('iconMarker'));
google.maps.event.clearInstanceListeners(self.get('labelMarker'));
var parent = canvas.parentNode;
if (parent) {
parent.removeChild(canvas);
}
canvas = undefined;
};
ClusterIconClass.prototype.draw = function() {
var self = this,
icon = self.get('icon');
if (typeof icon === 'string') {
icon = {
'url': icon
};
}
icon.label = icon.label || {};
if (self.get('prevText') === icon.label.text) {
return;
}
self.set('prevText', icon.label.text);
self.get('labelMarker').set('opacity', 0);
(new Promise(function(resolve) {
var iconUrl = icon.url;
if (typeof icon === 'object') {
if (typeof icon.size === 'object' &&
icon.size.width && icon.size.height) {
return resolve(icon.size);
}
}
var img = new Image();
img.onload = function() {
var newIconInfo = {
width: img.width,
height: img.height
};
icon.size = newIconInfo;
self.set('labelMarkerAnchor', new google.maps.Point(newIconInfo.width / 2, newIconInfo.height / 2));
self.set('icon', icon, true);
resolve(newIconInfo);
};
img.onerror = function() {
var newIconInfo = {
width: img.width,
height: img.height
};
icon.size = newIconInfo;
self.set('icon', icon, true);
resolve(newIconInfo);
};
img.src = iconUrl;
}))
.then(function(iconSize) {
var canvas = self.get('canvas'),
ctx = canvas.getContext('2d');
canvas.width = iconSize.width;
canvas.height = iconSize.height;
var labelOptions = icon.label || {
fontSize: 10,
bold: false,
italic: false
};
ctx.clearRect(0, 0, canvas.width, canvas.height);
// debug
//ctx.fillStyle="#FF000077";
//ctx.fillRect(0, 0, canvas.width, canvas.height);
if (labelOptions.text) {
var fontStyles = [];
if ('color' in labelOptions) {
ctx.fillStyle = [
'rgba(',
labelOptions.color[0],
',',
labelOptions.color[1],
',',
labelOptions.color[2],
',',
labelOptions.color[3] / 255,
')'].join('');
} else {
ctx.fillStyle = 'black';
}
if (labelOptions.italic === true) {
fontStyles.push('italic');
}
if (labelOptions.bold === true) {
fontStyles.push('bold');
}
fontStyles.push(parseInt(labelOptions.fontSize || '10', 10) + 'px');
fontStyles.push('Arial');
ctx.font = fontStyles.join(' ');
ctx.textBaseline = 'middle';
ctx.textAlign = 'center';
ctx.fillText(labelOptions.text, iconSize.width / 2, iconSize.height / 2);
// debug
//ctx.fillText(selfId.split("-")[1], iconSize.width / 2, iconSize.height / 2);
}
self.get('labelMarker').set('icon', {
'url': canvas.toDataURL(),
'anchor': self.get('labelMarkerAnchor')
});
setTimeout(function() {
self.set('opacity', 1);
}, 10);
});
};
ClusterIconClass.prototype.setIcon = function(icon) {
var self = this;
self.set('icon', icon);
};
ClusterIconClass.prototype.setVisible = function(visible) {
var self = this;
self.set('visible', visible);
};
ClusterIconClass.prototype.setPosition = function(position) {
var self = this;
self.set('position', position);
};
ClusterIconClass.prototype.getPosition = function() {
var self = this;
return self.get('position');
};
ClusterIconClass.prototype.setMap = function(map) {
var self = this;
self.set('map', map);
};
+371
View File
@@ -0,0 +1,371 @@
var utils = require('cordova/utils'),
event = require('cordova-plugin-googlemaps.event'),
BaseClass = require('cordova-plugin-googlemaps.BaseClass');
function PluginPolygon(pluginMap) {
var self = this;
BaseClass.apply(self);
Object.defineProperty(self, 'pluginMap', {
value: pluginMap,
writable: false
});
}
utils.extend(PluginPolygon, BaseClass);
PluginPolygon.prototype._create = function(onSuccess, onError, args) {
var self = this,
map = self.pluginMap.get('map'),
polygonId = 'polygon_' + args[2],
pluginOptions = args[1];
var polygonOpts = {
'overlayId': polygonId,
'map': map,
'paths': new google.maps.MVCArray()
};
if (pluginOptions.points) {
var strokePath = new google.maps.MVCArray();
pluginOptions.points.forEach(function(point) {
strokePath.push(new google.maps.LatLng(point.lat, point.lng));
});
polygonOpts.paths.push(strokePath);
if (Array.isArray(pluginOptions.holes)) {
pluginOptions.holes.forEach(function(hole) {
var holeMvc = new google.maps.MVCArray();
hole.forEach(function(vertix) {
holeMvc.push(new google.maps.LatLng(vertix.lat, vertix.lng));
});
polygonOpts.paths.push(holeMvc);
});
}
}
if (Array.isArray(pluginOptions.strokeColor)) {
polygonOpts.strokeColor = 'rgb(' + pluginOptions.strokeColor[0] + ',' + pluginOptions.strokeColor[1] + ',' + pluginOptions.strokeColor[2] + ')';
polygonOpts.strokeOpacity = pluginOptions.strokeColor[3] / 256;
}
if (Array.isArray(pluginOptions.fillColor)) {
polygonOpts.fillColor = 'rgb(' + pluginOptions.fillColor[0] + ',' + pluginOptions.fillColor[1] + ',' + pluginOptions.fillColor[2] + ')';
polygonOpts.fillOpacity = pluginOptions.fillColor[3] / 256;
}
if ('width' in pluginOptions) {
polygonOpts.strokeWeight = pluginOptions.width;
}
if ('zIndex' in pluginOptions) {
polygonOpts.zIndex = pluginOptions.zIndex;
}
if ('visible' in pluginOptions) {
polygonOpts.visible = pluginOptions.visible;
}
if ('geodesic' in pluginOptions) {
polygonOpts.geodesic = pluginOptions.geodesic;
}
if ('clickable' in pluginOptions) {
polygonOpts.clickable = pluginOptions.clickable;
}
var polygon = new google.maps.Polygon(polygonOpts);
polygon.addListener('click', function(polyMouseEvt) {
self._onPolygonEvent.call(self, polygon, polyMouseEvt);
});
self.pluginMap.objects[polygonId] = polygon;
onSuccess({
'__pgmId': polygonId
});
};
PluginPolygon.prototype.setFillColor = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var fillColor = args[1];
var polygon = self.pluginMap.objects[overlayId];
if (polygon) {
if (Array.isArray(fillColor)) {
polygon.setOptions({
'fillColor': 'rgb(' + fillColor[0] + ',' + fillColor[1] + ',' + fillColor[2] + ')',
'fillOpacity': fillColor[3] / 256
});
}
}
onSuccess();
};
PluginPolygon.prototype.setStrokeColor = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var polygon = self.pluginMap.objects[overlayId];
var strokeColor = args[1];
if (polygon) {
if (Array.isArray(strokeColor)) {
polygon.setOptions({
'strokeColor': 'rgb(' + strokeColor[0] + ',' + strokeColor[1] + ',' + strokeColor[2] + ')',
'strokeOpacity': strokeColor[3] / 256
});
}
}
onSuccess();
};
PluginPolygon.prototype.setStrokeWidth = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var width = args[1];
var polygon = self.pluginMap.objects[overlayId];
if (polygon) {
polygon.setOptions({
'strokeWeight': width
});
}
onSuccess();
};
PluginPolygon.prototype.setZIndex = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var zIndex = args[1];
var polygon = self.pluginMap.objects[overlayId];
if (polygon) {
polygon.setOptions({
'zIndex': zIndex
});
}
onSuccess();
};
PluginPolygon.prototype.setPoints = function(onSuccess, onError, args) {
var self = this,
polygonId = args[0],
positionList = args[1],
polygon = self.pluginMap.objects[polygonId];
if (polygon) {
//------------------------
// Update the points list
//------------------------
polygon.setPath(positionList);
}
onSuccess();
};
PluginPolygon.prototype.setClickable = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var clickable = args[1];
var polygon = self.pluginMap.objects[overlayId];
if (polygon) {
polygon.setOptions({
'clickable': clickable === true
});
}
onSuccess();
};
PluginPolygon.prototype.setVisible = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var polygon = self.pluginMap.objects[overlayId];
if (polygon) {
polygon.setVisible(args[1]);
}
onSuccess();
};
PluginPolygon.prototype.setGeodesic = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var polygon = self.pluginMap.objects[overlayId];
if (polygon) {
polygon.setOptions({
'geodesic': args[1] === true
});
}
onSuccess();
};
PluginPolygon.prototype.insertPointAt = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var polygon = self.pluginMap.objects[overlayId];
if (polygon) {
var index = args[1];
var latLng = new google.maps.LatLng(args[2].lat, args[2].lng);
polygon.getPath().insertAt(index, latLng);
}
onSuccess();
};
PluginPolygon.prototype.setPointAt = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var polygon = self.pluginMap.objects[overlayId];
if (polygon) {
var index = args[1];
var latLng = new google.maps.LatLng(args[2].lat, args[2].lng);
polygon.getPath().setAt(index, latLng);
}
onSuccess();
};
PluginPolygon.prototype.removePointAt = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var polygon = self.pluginMap.objects[overlayId];
if (polygon) {
var index = args[1];
polygon.getPath().removeAt(index);
}
onSuccess();
};
PluginPolygon.prototype.setHoles = function(onSuccess, onError, args) {
var self = this,
overlayId = args[0],
polygon = self.pluginMap.objects[overlayId];
if (polygon) {
var paths = polygon.getPaths(),
holeList = args[1],
newPaths = new google.maps.MVCArray([paths.getAt(0)]);
holeList.forEach(function(holeVertixes) {
var hole = new google.maps.MVCArray();
holeVertixes.forEach(function(vertix) {
hole.push(new google.maps.LatLng(vertix.lat, vertix.lng));
});
newPaths.push(hole);
});
polygon.setPaths(newPaths);
}
onSuccess();
};
PluginPolygon.prototype.insertPointOfHoleAt = function(onSuccess, onError, args) {
var self = this,
overlayId = args[0],
holeIndex = args[1] + 1, // idx=0 is for outter vertixes
pointIndex = args[2],
position = args[3],
polygon = self.pluginMap.objects[overlayId];
if (polygon) {
var latLng = new google.maps.LatLng(position.lat, position.lng);
var paths = polygon.getPaths();
var hole = null;
if (holeIndex < paths.getLength()) {
hole = paths.getAt(holeIndex);
}
if (!hole) {
hole = new google.maps.MVCArray();
paths.push(hole);
}
hole.insertAt(pointIndex, latLng);
}
onSuccess();
};
PluginPolygon.prototype.setPointOfHoleAt = function(onSuccess, onError, args) {
var self = this,
overlayId = args[0],
holeIndex = args[1] + 1,
pointIndex = args[2],
position = args[3],
polygon = self.pluginMap.objects[overlayId];
if (polygon) {
var latLng = new google.maps.LatLng(position.lat, position.lng);
polygon.getPaths().getAt(holeIndex).setAt(pointIndex, latLng);
}
onSuccess();
};
PluginPolygon.prototype.removePointOfHoleAt = function(onSuccess, onError, args) {
var self = this,
overlayId = args[0],
holeIndex = args[1] + 1,
pointIndex = args[2],
polygon = self.pluginMap.objects[overlayId];
if (polygon) {
polygon.getPaths().getAt(holeIndex).removeAt(pointIndex);
}
onSuccess();
};
PluginPolygon.prototype.insertHoleAt = function(onSuccess, onError, args) {
var self = this,
overlayId = args[0],
holeIndex = args[1] + 1,
vertixes = args[2],
polygon = self.pluginMap.objects[overlayId];
if (polygon) {
var hole = new google.maps.MVCArray();
vertixes.forEach(function(vertix) {
hole.push(new google.maps.LatLng(vertix.lat, vertix.lng));
});
polygon.getPaths().insertAt(holeIndex, hole);
}
onSuccess();
};
PluginPolygon.prototype.setHoleAt = function(onSuccess, onError, args) {
var self = this,
overlayId = args[0],
holeIndex = args[1] + 1,
vertixes = args[2],
polygon = self.pluginMap.objects[overlayId];
if (polygon) {
var hole = new google.maps.MVCArray();
vertixes.forEach(function(vertix) {
hole.push(new google.maps.LatLng(vertix.lat, vertix.lng));
});
polygon.getPaths().setAt(holeIndex, hole);
}
onSuccess();
};
PluginPolygon.prototype.removeHoleAt = function(onSuccess, onError, args) {
var self = this,
overlayId = args[0],
holeIndex = args[1] + 1,
polygon = self.pluginMap.objects[overlayId];
if (polygon) {
polygon.getPaths().removeAt(holeIndex);
}
onSuccess();
};
PluginPolygon.prototype.remove = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var polygon = self.pluginMap.objects[overlayId];
if (polygon) {
google.maps.event.clearInstanceListeners(polygon);
polygon.setMap(null);
polygon = undefined;
self.pluginMap.objects[overlayId] = undefined;
delete self.pluginMap.objects[overlayId];
}
onSuccess();
};
PluginPolygon.prototype._onPolygonEvent = function(polygon, polyMouseEvt) {
var self = this,
mapId = self.pluginMap.__pgmId;
if (mapId in plugin.google.maps) {
plugin.google.maps[mapId]({
'evtName': event.POLYGON_CLICK,
'callback': '_onOverlayEvent',
'args': [polygon.overlayId, new plugin.google.maps.LatLng(polyMouseEvt.latLng.lat(), polyMouseEvt.latLng.lng())]
});
}
};
module.exports = PluginPolygon;
+216
View File
@@ -0,0 +1,216 @@
var utils = require('cordova/utils'),
event = require('cordova-plugin-googlemaps.event'),
BaseClass = require('cordova-plugin-googlemaps.BaseClass');
function PluginPolyline(pluginMap) {
var self = this;
BaseClass.apply(self);
Object.defineProperty(self, 'pluginMap', {
value: pluginMap,
writable: false
});
}
utils.extend(PluginPolyline, BaseClass);
PluginPolyline.prototype._create = function(onSuccess, onError, args) {
var self = this,
map = self.pluginMap.get('map'),
polylineId = 'polyline_' + args[2],
pluginOptions = args[1];
var polylineOpts = {
'overlayId': polylineId,
'map': map,
'path': new google.maps.MVCArray()
};
if (pluginOptions.points) {
pluginOptions.points.forEach(function(point) {
polylineOpts.path.push(new google.maps.LatLng(point.lat, point.lng));
});
}
if (Array.isArray(pluginOptions.color)) {
polylineOpts.strokeColor = 'rgb(' + pluginOptions.color[0] + ',' + pluginOptions.color[1] + ',' + pluginOptions.color[2] + ')';
polylineOpts.strokeOpacity = pluginOptions.color[3] / 256;
}
if ('width' in pluginOptions) {
polylineOpts.strokeWeight = pluginOptions.width;
}
if ('zIndex' in pluginOptions) {
polylineOpts.zIndex = pluginOptions.zIndex;
}
if ('visible' in pluginOptions) {
polylineOpts.visible = pluginOptions.visible;
}
if ('geodesic' in pluginOptions) {
polylineOpts.geodesic = pluginOptions.geodesic;
}
if ('clickable' in pluginOptions) {
polylineOpts.clickable = pluginOptions.clickable;
}
var polyline = new google.maps.Polyline(polylineOpts);
polyline.addListener('click', function(polyMouseEvt) {
self._onPolylineEvent.call(self, polyline, polyMouseEvt);
});
self.pluginMap.objects[polylineId] = polyline;
onSuccess({
'__pgmId': polylineId
});
};
PluginPolyline.prototype.remove = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var polyline = self.pluginMap.objects[overlayId];
if (polyline) {
google.maps.event.clearInstanceListeners(polyline);
polyline.setMap(null);
polyline = undefined;
self.pluginMap.objects[overlayId] = undefined;
delete self.pluginMap.objects[overlayId];
}
onSuccess();
};
PluginPolyline.prototype.setStrokeColor = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var polyline = self.pluginMap.objects[overlayId];
var strokeColor = args[1];
if (polyline) {
if (Array.isArray(strokeColor)) {
polyline.setOptions({
'strokeColor': 'rgb(' + strokeColor[0] + ',' + strokeColor[1] + ',' + strokeColor[2] + ')',
'strokeOpacity': strokeColor[3] / 256
});
}
}
onSuccess();
};
PluginPolyline.prototype.setStrokeWidth = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var width = args[1];
var polyline = self.pluginMap.objects[overlayId];
if (polyline) {
polyline.setOptions({
'strokeWeight': width
});
}
onSuccess();
};
PluginPolyline.prototype.setZIndex = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var zIndex = args[1];
var polyline = self.pluginMap.objects[overlayId];
if (polyline) {
polyline.setOptions({
'zIndex': zIndex
});
}
onSuccess();
};
PluginPolyline.prototype.setPoints = function(onSuccess, onError, args) {
var self = this,
polylineId = args[0],
positionList = args[1],
polyline = self.pluginMap.objects[polylineId];
if (polyline) {
//------------------------
// Update the points list
//------------------------
polyline.setPath(positionList);
}
onSuccess();
};
PluginPolyline.prototype.setClickable = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var clickable = args[1];
var polyline = self.pluginMap.objects[overlayId];
if (polyline) {
polyline.setOptions({
'clickable': clickable === true
});
}
onSuccess();
};
PluginPolyline.prototype.setVisible = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var polyline = self.pluginMap.objects[overlayId];
if (polyline) {
polyline.setVisible(args[1]);
}
onSuccess();
};
PluginPolyline.prototype.setGeodesic = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var polyline = self.pluginMap.objects[overlayId];
if (polyline) {
polyline.setOptions({
'geodesic': args[1] === true
});
}
onSuccess();
};
PluginPolyline.prototype.insertPointAt = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var polyline = self.pluginMap.objects[overlayId];
if (polyline) {
var index = args[1];
var latLng = new google.maps.LatLng(args[2].lat, args[2].lng);
polyline.getPath().insertAt(index, latLng);
}
onSuccess();
};
PluginPolyline.prototype.setPointAt = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var polyline = self.pluginMap.objects[overlayId];
if (polyline) {
var index = args[1];
var latLng = new google.maps.LatLng(args[2].lat, args[2].lng);
polyline.getPath().setAt(index, latLng);
}
onSuccess();
};
PluginPolyline.prototype.removePointAt = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var polyline = self.pluginMap.objects[overlayId];
if (polyline) {
var index = args[1];
polyline.getPath().removeAt(index);
}
onSuccess();
};
PluginPolyline.prototype._onPolylineEvent = function(polyline, polyMouseEvt) {
var self = this,
mapId = self.pluginMap.__pgmId;
if (mapId in plugin.google.maps) {
plugin.google.maps[mapId]({
'evtName': event.POLYLINE_CLICK,
'callback': '_onOverlayEvent',
'args': [polyline.overlayId, new plugin.google.maps.LatLng(polyMouseEvt.latLng.lat(), polyMouseEvt.latLng.lng())]
});
}
};
module.exports = PluginPolyline;
@@ -0,0 +1,346 @@
var utils = require('cordova/utils'),
event = require('cordova-plugin-googlemaps.event'),
BaseClass = require('cordova-plugin-googlemaps.BaseClass');
function displayGrayMap(container) {
var gmErrorContent = document.querySelector('.gm-err-container');
var gmnoprint = document.querySelector('.gmnoprint');
if (!gmErrorContent && !gmnoprint) {
container.innerHTML = [
'<div style="position: absolute; left: 0; top: 0; width: 100%; height: 100%; background-color:rgb(229, 227, 223)">',
'<div style="text-align: center; position: absolute; left: 0; top: 0; bottom: 0; right: 0; width: 80%; height: 100px; margin: auto; color: #616161">',
'<img src="https://maps.gstatic.com/mapfiles/api-3/images/icon_error.png"><br>',
'<h3>Can not display panorama.<br>Check the developer console.</h3>',
'</div>',
'</div>'
].join('\n');
}
}
function PluginStreetViewPanorama(panoramaId, options) {
var self = this;
BaseClass.apply(this);
var panoramaDiv = document.querySelector('[__pluginMapId=\'' + panoramaId + '\']');
panoramaDiv.style.backgroundColor = 'rgb(229, 227, 223)';
var container = document.createElement('div');
container.style.userSelect='none';
container.style['-webkit-user-select']='none';
container.style['-moz-user-select']='none';
container.style['-ms-user-select']='none';
panoramaDiv.style.position = 'relative';
container.style.position = 'absolute';
container.style.top = 0;
container.style.bottom = 0;
container.style.right = 0;
container.style.left = 0;
container.style.zIndex = 0;
panoramaDiv.insertBefore(container, panoramaDiv.firstElementChild);
self.set('isGoogleReady', false);
self.set('container', container);
self.PLUGINS = {};
Object.defineProperty(self, '__pgmId', {
value: panoramaId,
writable: false
});
self.one('googleready', function() {
self.set('isGoogleReady', true);
var service = new google.maps.StreetViewService();
self.set('service', service);
new Promise(function(resolve, reject) {
if (options.camera) {
var request = {};
if (typeof options.camera.target === 'string') {
request.pano = options.camera.target;
} else {
request.location = options.camera.target;
request.radius = options.camera.radius | 50;
request.source = options.camera.source === 'OUTDOOR' ?
google.maps.StreetViewSource.OUTDOOR : google.maps.StreetViewSource.DEFAULT;
}
var timeoutError = setTimeout(function() {
self.trigger('load_error');
displayGrayMap(panoramaDiv);
reject();
}, 3000);
service.getPanorama(request, function(data, status) {
clearTimeout(timeoutError);
if (status === google.maps.StreetViewStatus.OK) {
resolve(data.location.pano);
} else {
resolve(null);
}
});
} else {
resolve(null);
}
})
.then(function(panoId) {
var stOptions = {
'addressControl': options.controls.streetNames,
'showRoadLabels': options.controls.streetNames,
'linksControl': options.controls.navigation,
'panControl': options.gestures.panning,
'zoomControl': options.gestures.zoom,
'scrollwheel': options.gestures.zoom,
'pano': panoId
};
if (options.camera) {
if ('zoom' in options.camera) {
stOptions.zoom = options.camera.zoom;
}
var pov;
if ('tilt' in options.camera) {
pov = {};
pov.pitch = options.camera.tilt;
}
if ('bearing' in options.camera) {
pov = pov || {};
pov.heading = options.camera.bearing;
}
if (pov) {
stOptions.pov = pov;
}
}
google.maps.event.addDomListener(container, 'click', function(evt) {
var pov = panorama.getPov();
var clickInfo = {
'orientation': {
'bearing': pov.heading,
'tilt': pov.pitch
},
'point': [evt.clientX, evt.clientY]
};
if (self.__pgmId in plugin.google.maps) {
plugin.google.maps[self.__pgmId]({
'evtName': event.PANORAMA_CLICK,
'callback': '_onPanoramaEvent',
'args': [clickInfo]
});
}
});
var panorama = new google.maps.StreetViewPanorama(container, stOptions);
self.set('panorama', panorama);
self.trigger(event.PANORAMA_READY);
panorama.addListener('position_changed', self._onPanoChangedEvent.bind(self, panorama));
panorama.addListener('pov_changed', self._onCameraEvent.bind(self, panorama));
panorama.addListener('zoom_changed', self._onCameraEvent.bind(self, panorama));
if (!panoId) {
self._onPanoChangedEvent(null);
}
});
});
}
utils.extend(PluginStreetViewPanorama, BaseClass);
PluginStreetViewPanorama.prototype.setPanningGesturesEnabled = function(onSuccess, onError, args) {
var self = this;
var panorama = self.get('panorama');
var boolValue = args[0] === true;
if (panorama) {
panorama.setOptions({
'panControl': boolValue
});
}
onSuccess();
};
PluginStreetViewPanorama.prototype.setZoomGesturesEnabled = function(onSuccess, onError, args) {
var self = this;
var panorama = self.get('panorama');
var boolValue = args[0] === true;
if (panorama) {
panorama.setOptions({
'zoomControl': boolValue,
'scrollwheel': boolValue,
});
}
onSuccess();
};
PluginStreetViewPanorama.prototype.setNavigationEnabled = function(onSuccess, onError, args) {
var self = this;
var panorama = self.get('panorama');
var boolValue = args[0] === true;
if (panorama) {
panorama.setOptions({
'linksControl': boolValue
});
}
onSuccess();
};
PluginStreetViewPanorama.prototype.setStreetNamesEnabled = function(onSuccess, onError, args) {
var self = this;
var panorama = self.get('panorama');
var boolValue = args[0] === true;
if (panorama) {
panorama.setOptions({
'addressControl': boolValue,
'showRoadLabels': boolValue
});
}
onSuccess();
};
PluginStreetViewPanorama.prototype.setStreetNamesEnabled = function(onSuccess, onError, args) {
var self = this;
var panorama = self.get('panorama');
var boolValue = args[0] === true;
if (panorama) {
panorama.setOptions({
'visible': boolValue
});
}
onSuccess();
};
PluginStreetViewPanorama.prototype.setPosition = function(onSuccess, onError, args) {
var self = this;
var panorama = self.get('panorama');
var camera = args[0];
if (panorama) {
var service = self.get('service');
new Promise(function(resolve, reject) {
var request = {};
if (typeof camera.target === 'string') {
request.pano = camera.target;
} else {
request.location = camera.target;
request.radius = camera.radius | 50;
request.source = camera.source === 'OUTDOOR' ?
google.maps.StreetViewSource.OUTDOOR : google.maps.StreetViewSource.DEFAULT;
}
var timeoutError = setTimeout(function() {
reject('timeout error');
}, 3000);
service.getPanorama(request, function(data, status) {
clearTimeout(timeoutError);
if (status === google.maps.StreetViewStatus.OK) {
resolve(data.location.pano);
} else {
resolve(null);
}
});
})
.then(function(panoId) {
panorama.setPano(panoId);
if (!panoId) {
self._onPanoChangedEvent(null);
}
onSuccess();
})
.catch(onError);
} else {
onError('panorama has been already removed.');
}
};
PluginStreetViewPanorama.prototype.setPov = function(onSuccess, onError, args) {
var self = this;
var panorama = self.get('panorama');
var povRequest = args[0];
if (panorama) {
var options = {
pov: panorama.getPov()
};
options.pov = options.pov || {
heading: 0,
pitch: 0
};
if ('bearing' in povRequest) {
options.pov.heading = povRequest.bearing;
}
if ('tilt' in povRequest) {
options.pov.pitch = povRequest.tilt;
}
if ('zoom' in povRequest) {
options.zoom = povRequest.zoom;
}
panorama.setOptions(options);
}
onSuccess();
};
PluginStreetViewPanorama.prototype._onCameraEvent = function(panorama) {
var self = this;
var pov = panorama.getPov();
var camera = {
'bearing': pov.heading,
'tilt': pov.pitch,
'zoom': panorama.getZoom()
};
if (self.__pgmId in plugin.google.maps) {
plugin.google.maps[self.__pgmId]({
'evtName': event.PANORAMA_CAMERA_CHANGE,
'callback': '_onPanoramaCameraChange',
'args': [camera]
});
}
};
PluginStreetViewPanorama.prototype._onPanoChangedEvent = function(panorama) {
var self = this;
var locationInfo = null;
if (panorama) {
var location = panorama.getLocation();
locationInfo = {
'panoId': location.pano,
'latLng': {
'lat': location.latLng.lat(),
'lng': location.latLng.lng()
}
};
var links = panorama.getLinks() || [];
if (links) {
locationInfo.links = links.map(function(link) {
return {
'panoId': link.pano,
'bearing': link.heading
};
});
}
}
if (self.__pgmId in plugin.google.maps) {
plugin.google.maps[self.__pgmId]({
'evtName': event.PANORAMA_LOCATION_CHANGE,
'callback': '_onPanoramaLocationChange',
'args': [locationInfo]
});
}
};
module.exports = PluginStreetViewPanorama;
+299
View File
@@ -0,0 +1,299 @@
var utils = require('cordova/utils'),
BaseClass = require('cordova-plugin-googlemaps.BaseClass');
function TileOverlay(mapId, hashCode, options) {
var tileSize = 256,
tileCaches = {},
_opacity = 'opacity' in options ? options.opacity : 1;
var mapType = {
zIndex: options.zIndex || 0,
name: options.name || hashCode,
mapTypeId: hashCode,
fadeIn: options.fadeIn === false ? false : true,
visible: true,
setOpacity: function(opacity) {
_opacity = opacity;
var keys = Object.keys(tileCaches);
keys.forEach(function(key) {
tileCaches[key].style.opacity = _opacity;
});
},
getTileFromCache: function(cacheId) {
return tileCaches[cacheId];
},
getZIndex: function() {
return this.zIndex;
},
setZIndex: function(zIndexVal) {
this.zIndex = parseInt(zIndexVal, 10);
},
getOpacity: function() {
return _opacity;
},
tileSize: new google.maps.Size(tileSize, tileSize),
setVisible: function(visible) {
this.visible = visible;
var visibility = visible ? 'visible': 'hidden';
var keys = Object.keys(tileCaches);
keys.forEach(function(key) {
tileCaches[key].style.visibility = visibility;
});
},
getTile: function(coord, zoom) {
var cacheId = [mapId, hashCode, coord.x, coord.y, zoom].join('-');
if (cacheId in tileCaches) {
return tileCaches[cacheId];
} else {
var div = document.createElement('div');
div.style.width = tileSize + 'px';
div.style.height = tileSize + 'px';
div.style.opacity = 0;
div.style.visibility = 'hidden';
if (options.debug) {
div.style.borderLeft = '1px solid red';
div.style.borderTop = '1px solid red';
div.style.color = 'red';
div.style.fontSize = '12px';
div.style.padding = '1em';
div.innerHTML = 'x = ' + coord.x + ', y = ' + coord.y + ', zoom = ' + zoom;
}
div.setAttribute('cacheId', cacheId);
tileCaches[cacheId] = div;
options.getTile(coord.x, coord.y, zoom, cacheId);
return div;
}
},
releaseTile: function(div) {
var cacheId = div.getAttribute('cacheId');
delete tileCaches[cacheId];
},
remove: function() {
var keys = Object.keys(tileCaches);
keys.forEach(function(key) {
if (tileCaches[key].parentNode) {
tileCaches[key].parentNode.removeChild(tileCaches[key]);
}
tileCaches[key] = undefined;
delete tileCaches[key];
});
}
};
return mapType;
}
function PluginTileOverlay(pluginMap) {
var self = this;
BaseClass.apply(self);
Object.defineProperty(self, 'pluginMap', {
value: pluginMap,
enumerable: false,
writable: false
});
}
utils.extend(PluginTileOverlay, BaseClass);
PluginTileOverlay.prototype._create = function(onSuccess, onError, args) {
var self = this,
map = self.pluginMap.get('map'),
hashCode = args[2],
tileoverlayId = 'tileoverlay_' + hashCode,
pluginOptions = args[1],
mapId = self.pluginMap.__pgmId,
getTileEventName = mapId + '-' + hashCode + '-tileoverlay';
pluginOptions.getTile = function(x, y, zoom, urlCallbackId) {
cordova.fireDocumentEvent(getTileEventName, {
key: urlCallbackId,
x: x,
y: y,
zoom: zoom
});
};
var tileoverlay = new TileOverlay(self.pluginMap.__pgmId, hashCode, pluginOptions);
var layers = map.overlayMapTypes.getArray();
layers = layers.filter(function(layer) {
return !!layer;
});
layers.forEach(function(layer, idx) {
if (!layer) {
return;
}
if (layer.zIndex === undefined) {
layer.zIndex = idx;
}
if (!layer.getZIndex) {
layer.getZIndex = function() {
return layer.zIndex;
};
}
if (!layer.setZIndex) {
layer.setZIndex = function(zIndexVal) {
layer.zIndex = zIndexVal;
};
}
});
layers.push(tileoverlay);
layers = layers.sort(function(a, b) {
return a.getZIndex() - b.getZIndex();
});
layers.forEach(function(layer, idx) {
if (idx < map.overlayMapTypes.getLength()) {
layer.zIndex = idx;
map.overlayMapTypes.setAt(idx, layer);
} else {
layer.zIndex = idx;
map.overlayMapTypes.push(layer);
}
});
self.pluginMap.objects[tileoverlayId] = tileoverlay;
onSuccess({
'__pgmId': tileoverlayId
});
};
PluginTileOverlay.prototype.onGetTileUrlFromJS = function(onSuccess, onError, args) {
var self = this,
cacheId = args[1],
tileUrl = args[2];
var tmp = cacheId.split(/-/),
hashCode = tmp[1],
tileoverlayId = 'tileoverlay_' + hashCode;
var tileLayer = self.pluginMap.objects[tileoverlayId];
if (tileLayer && tileLayer.getTileFromCache(cacheId)) {
var tile = tileLayer.getTileFromCache(cacheId);
tile.style.backgroundImage = 'url(\'' + tileUrl + '\')';
tile.style.visibility = tileLayer.visible ? 'visible': 'hidden';
if (tileLayer.fadeIn) {
fadeInAnimation(tile, 500, tileLayer.getOpacity());
} else {
tile.style.opacity = tileLayer.getOpacity();
}
}
onSuccess();
};
PluginTileOverlay.prototype.setVisible = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var tileoverlay = self.pluginMap.objects[overlayId];
if (tileoverlay) {
tileoverlay.setVisible(args[1]);
}
onSuccess();
};
PluginTileOverlay.prototype.setOpacity = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var opacity = args[1];
var tileoverlay = self.pluginMap.objects[overlayId];
if (tileoverlay) {
tileoverlay.setOpacity(opacity);
}
onSuccess();
};
PluginTileOverlay.prototype.setFadeIn = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var tileoverlay = self.pluginMap.objects[overlayId];
if (tileoverlay) {
tileoverlay.fadeIn = args[1] === false ? false : true;
}
onSuccess();
};
PluginTileOverlay.prototype.setZIndex = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var map = self.pluginMap.get('map');
var tileoverlay = self.pluginMap.objects[overlayId];
if (tileoverlay) {
tileoverlay.setZIndex(args[1]);
var layers = map.overlayMapTypes.getArray();
layers = layers.sort(function(a, b) {
return a.getZIndex() - b.getZIndex();
});
layers.forEach(function(layer, idx) {
if (idx < map.overlayMapTypes.getLength()) {
map.overlayMapTypes.setAt(idx, layer);
} else {
map.overlayMapTypes.push(layer);
}
});
}
onSuccess();
};
PluginTileOverlay.prototype.remove = function(onSuccess, onError, args) {
var self = this;
var overlayId = args[0];
var tileoverlay = self.pluginMap.objects[overlayId];
if (tileoverlay) {
google.maps.event.clearInstanceListeners(tileoverlay);
tileoverlay.remove();
var layers = self.pluginMap.get('map').overlayMapTypes.getArray();
layers.forEach(function(layer, idx) {
if (layer === tileoverlay) {
layers.splice(idx, 1);
}
});
tileoverlay = undefined;
self.pluginMap.objects[overlayId] = undefined;
delete self.pluginMap.objects[overlayId];
}
onSuccess();
};
module.exports = PluginTileOverlay;
function fadeInAnimation(el, time, maxOpacity) {
el.style.opacity = 0;
var timeFunc = typeof window.requestAnimationFrame === 'function' ? requestAnimationFrame : setTimeout;
var last = Date.now();
var tick = function() {
var now = Date.now();
el.style.opacity = +el.style.opacity + (now - last) / time;
last = now;
if (+el.style.opacity < maxOpacity) {
timeFunc.call(window, tick, 16);
}
};
tick();
}
@@ -0,0 +1,19 @@
const PluginMap = require('../PluginMap');
describe('PluginMap', () => {
it('should exist', () => {
expect(PluginMap).toBeDefined();
});
describe('toDataURL', () => {
afterEach(() => {
document.body.innerHTML = '';
});
it('should exist', () => {
var mapId = 'map';
document.body.innerHTML = '<div __pluginMapId="' + mapId + '"></div>';
expect(new PluginMap(mapId, {}).toDataURL).toBeDefined();
});
});
});
@@ -0,0 +1,33 @@
//
// CordovaGoogleMaps.h
// cordova-googlemaps-plugin v2
//
// Created by Masashi Katsumata.
//
//
#import <Cordova/CDV.h>
#import <GoogleMaps/GoogleMaps.h>
#import "PluginMapViewController.h"
#import "PluginStreetViewPanoramaController.h"
#import "PluginUtil.h"
#import "PluginMap.h"
#import "PluginStreetViewPanorama.h"
#import "MyPluginLayer.h"
#import "IPluginProtocol.h"
#import "IPluginView.h"
@interface CordovaGoogleMaps : CDVPlugin
@property (nonatomic) MyPluginLayer *pluginLayer;
@property (nonatomic) NSMutableDictionary *viewPlugins;
@property (nonatomic) NSOperationQueue *executeQueue;
- (void)getMap:(CDVInvokedUrlCommand*)command;
- (void)getPanorama:(CDVInvokedUrlCommand*)command;
- (void)clearHtmlElements:(CDVInvokedUrlCommand *)command;
- (void)putHtmlElements:(CDVInvokedUrlCommand *)command;
- (void)removeMap:(CDVInvokedUrlCommand *)command;
- (void)pause:(CDVInvokedUrlCommand *)command;
- (void)resume:(CDVInvokedUrlCommand *)command;
@end
@@ -0,0 +1,538 @@
//
// CordovaGoogleMaps.m
// cordova-googlemaps-plugin v2
//
// Created by Masashi Katsumata.
//
//
#import "CordovaGoogleMaps.h"
@implementation CordovaGoogleMaps
- (void)pluginInitialize
{
self.webView.backgroundColor = [UIColor clearColor];
self.webView.opaque = NO;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pageDidLoad) name:CDVPageDidLoadNotification object:nil];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
self.executeQueue = [NSOperationQueue new];
self.executeQueue.maxConcurrentOperationCount = 10;
});
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
//-------------------------------
// Check the Google Maps API key
//-------------------------------
NSString *APIKey = [((CDVViewController *)self.viewController).settings objectForKey:@"google_maps_ios_api_key"];
if (APIKey == nil) {
NSString *errorTitle = [PluginUtil PGM_LOCALIZATION:@"APIKEY_IS_UNDEFINED_TITLE"];
NSString *errorMsg = [PluginUtil PGM_LOCALIZATION:@"APIKEY_IS_UNDEFINED_MESSAGE"];
UIAlertController* alert = [UIAlertController alertControllerWithTitle:errorTitle
message:errorMsg
preferredStyle:UIAlertControllerStyleAlert];
NSString *closeBtnLabel = [PluginUtil PGM_LOCALIZATION:@"CLOSE_BUTTON"];
UIAlertAction* ok = [UIAlertAction actionWithTitle:closeBtnLabel
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action)
{
[alert dismissViewControllerAnimated:YES completion:nil];
}];
[alert addAction:ok];
[self.viewController presentViewController:alert
animated:YES
completion:nil];
return;
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didRotate:)
name:UIDeviceOrientationDidChangeNotification object:nil];
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[GMSServices provideAPIKey:APIKey];
}];
//-------------------------------
// Plugin initialization
//-------------------------------
self.viewPlugins = [[NSMutableDictionary alloc] init];
self.pluginLayer = [[MyPluginLayer alloc] initWithWebView:self.webView];
self.pluginLayer.backgroundColor = [UIColor whiteColor];
self.pluginLayer.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
NSArray *subViews = self.viewController.view.subviews;
//NSLog(@"--->subViews count=%lu", subViews.count);
UIView *view;
for (int i = 0; i < [subViews count]; i++) {
view = [subViews objectAtIndex:i];
//NSLog(@"--->remove i=%d class=%@", i, view.class);
[view removeFromSuperview];
[self.pluginLayer addSubview: view];
}
[self.viewController.view addSubview:self.pluginLayer];
}
- (void) didRotate:(id)sender
{
NSArray *keys = [self.viewPlugins allKeys];
NSString *key;
CDVPlugin<IPluginProtocol, IPluginView> *viewPlugin;
PluginMap *pluginMap;
for (int i = 0; i < keys.count; i++) {
key = [keys objectAtIndex:i];
if ([self.viewPlugins objectForKey:key]) {
viewPlugin = [self.viewPlugins objectForKey:key];
if ([viewPlugin isKindOfClass:[PluginMap class]]) {
pluginMap = (PluginMap *)viewPlugin;
// Trigger the CAMERA_MOVE_END mandatory
[pluginMap.mapCtrl mapView:pluginMap.mapCtrl.map idleAtCameraPosition:pluginMap.mapCtrl.map.camera];
}
}
}
}
-(void)viewDidLayoutSubviews {
[self.pluginLayer.pluginScrollView setContentSize: self.webView.scrollView.contentSize];
[self.pluginLayer.pluginScrollView flashScrollIndicators];
}
- (void)onReset
{
[super onReset];
// Reset the background color
self.pluginLayer.backgroundColor = [UIColor whiteColor];
dispatch_async(dispatch_get_main_queue(), ^{
// Remove all url caches
[[NSURLCache sharedURLCache] removeAllCachedResponses];
// Remove old plugins that are used in the previous html.
NSString *mapId;
NSArray *keys=[self.viewPlugins allKeys];
for (int i = 0; i < [keys count]; i++) {
mapId = [keys objectAtIndex:i];
[self _destroyMap:mapId];
}
[self.viewPlugins removeAllObjects];
@synchronized(self.pluginLayer.pluginScrollView.HTMLNodes) {
[self.pluginLayer.pluginScrollView.HTMLNodes removeAllObjects];
self.pluginLayer.pluginScrollView.HTMLNodes = nil;
}
[self.pluginLayer.pluginScrollView.mapCtrls removeAllObjects];
});
}
-(void)pageDidLoad {
self.webView.backgroundColor = [UIColor clearColor];
self.webView.opaque = NO;
}
- (void)_destroyMap:(NSString *)mapId {
if (![self.viewPlugins objectForKey:mapId]) {
return;
}
CDVViewController *cdvViewController = (CDVViewController*)self.viewController;
CDVPlugin<IPluginView> *pluginView = [self.viewPlugins objectForKey:mapId];
if ([mapId hasPrefix:@"streetview_"]) {
PluginStreetViewPanorama *pluginSV = (PluginStreetViewPanorama *)pluginView;
pluginSV.isRemoved = YES;
//[pluginSV clear:nil];
[pluginSV pluginUnload];
[cdvViewController.pluginObjects setObject:pluginView forKey:mapId];
[cdvViewController.pluginsMap setValue:mapId forKey:mapId];
[self.pluginLayer removePluginOverlay:pluginSV.panoramaCtrl];
pluginSV.panoramaCtrl.view = nil;
pluginSV = nil;
} else {
PluginMap *pluginMap = (PluginMap *)pluginView;
pluginMap.isRemoved = YES;
//[pluginMap clear:nil];
[pluginMap pluginUnload];
[cdvViewController.pluginObjects setObject:pluginView forKey:mapId];
[cdvViewController.pluginsMap setValue:mapId forKey:mapId];
[self.pluginLayer removePluginOverlay:pluginMap.mapCtrl];
pluginMap.mapCtrl.view = nil;
[pluginMap.mapCtrl.plugins removeAllObjects];
pluginMap.mapCtrl.plugins = nil;
pluginMap.mapCtrl.view = nil;
pluginMap.mapCtrl = nil;
pluginMap = nil;
}
[self.viewPlugins removeObjectForKey:mapId];
[cdvViewController.pluginObjects removeObjectForKey:mapId];
}
/**
* Remove the map
*/
- (void)removeMap:(CDVInvokedUrlCommand *)command {
NSString *mapId = [command.arguments objectAtIndex:0];
[self _destroyMap:mapId];
if (command != nil) {
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
}
/**
* Intialize the map
*/
- (void)getMap:(CDVInvokedUrlCommand *)command {
if (self.pluginLayer != nil) {
self.pluginLayer.isSuspended = false;
}
dispatch_async(dispatch_get_main_queue(), ^{
CDVViewController *cdvViewController = (CDVViewController*)self.viewController;
NSDictionary *meta = [command.arguments objectAtIndex:0];
NSString *mapId = [meta objectForKey:@"__pgmId"];
NSDictionary *initOptions = [command.arguments objectAtIndex:1];
// Wrapper view
PluginMapViewController* viewCtrl = [[PluginMapViewController alloc] initWithOptions:nil];
viewCtrl.webView = self.webView;
viewCtrl.isFullScreen = YES;
viewCtrl.overlayId = mapId;
viewCtrl.title = mapId;
viewCtrl.divId = nil;
[viewCtrl.view setHidden:YES];
// Create an instance of the Map class everytime.
PluginMap *pluginMap = [[PluginMap alloc] init];
[pluginMap pluginInitialize];
pluginMap.mapCtrl = viewCtrl;
// Hack:
// In order to load the plugin instance of the same class but different names,
// register the map plugin instance into the pluginObjects directly.
if ([pluginMap respondsToSelector:@selector(setViewController:)]) {
[pluginMap setViewController:cdvViewController];
}
if ([pluginMap respondsToSelector:@selector(setCommandDelegate:)]) {
[pluginMap setCommandDelegate:cdvViewController.commandDelegate];
}
[cdvViewController.pluginObjects setObject:pluginMap forKey:mapId];
[cdvViewController.pluginsMap setValue:mapId forKey:mapId];
[pluginMap pluginInitialize];
[self.viewPlugins setObject:pluginMap forKey:mapId];
CGRect rect = CGRectZero;
// Sets the map div id.
if ([command.arguments count] == 3) {
pluginMap.mapCtrl.divId = [command.arguments objectAtIndex:2];
if (pluginMap.mapCtrl.divId != nil) {
NSDictionary *domInfo = [self.pluginLayer.pluginScrollView.HTMLNodes objectForKey:pluginMap.mapCtrl.divId];
if (domInfo != nil) {
rect = CGRectFromString([domInfo objectForKey:@"size"]);
}
}
}
// Generate an instance of GMSMapView;
GMSCameraPosition *camera = nil;
int bearing = 0;
double angle = 0, zoom = 0;
NSDictionary *latLng = nil;
double latitude = 0;
double longitude = 0;
GMSCoordinateBounds *cameraBounds = nil;
NSDictionary *cameraOptions = [initOptions valueForKey:@"camera"];
if (cameraOptions) {
if ([cameraOptions valueForKey:@"bearing"] && [cameraOptions valueForKey:@"bearing"] != [NSNull null]) {
bearing = (int)[[cameraOptions valueForKey:@"bearing"] integerValue];
} else {
bearing = 0;
}
if ([cameraOptions valueForKey:@"tilt"] && [cameraOptions valueForKey:@"tilt"] != [NSNull null]) {
angle = [[cameraOptions valueForKey:@"tilt"] doubleValue];
} else {
angle = 0;
}
if ([cameraOptions valueForKey:@"zoom"] && [cameraOptions valueForKey:@"zoom"] != [NSNull null]) {
zoom = [[cameraOptions valueForKey:@"zoom"] doubleValue];
} else {
zoom = 0;
}
if ([cameraOptions objectForKey:@"target"] && [cameraOptions valueForKey:@"target"] != [NSNull null]) {
NSString *targetClsName = [[cameraOptions objectForKey:@"target"] className];
if ([targetClsName isEqualToString:@"__NSCFArray"] || [targetClsName isEqualToString:@"__NSArrayM"] ) {
//--------------------------------------------
// cameraPosition.target = [
// new plugin.google.maps.LatLng(),
// ...
// new plugin.google.maps.LatLng()
// ]
//---------------------------------------------
int i = 0;
NSArray *latLngList = [cameraOptions objectForKey:@"target"];
GMSMutablePath *path = [GMSMutablePath path];
for (i = 0; i < [latLngList count]; i++) {
latLng = [latLngList objectAtIndex:i];
latitude = [[latLng valueForKey:@"lat"] doubleValue];
longitude = [[latLng valueForKey:@"lng"] doubleValue];
[path addLatitude:latitude longitude:longitude];
}
cameraBounds = [[GMSCoordinateBounds alloc] initWithPath:path];
//CLLocationCoordinate2D center = cameraBounds.center;
latitude = cameraBounds.center.latitude;
longitude = cameraBounds.center.longitude;
} else {
//------------------------------------------------------------------
// cameraPosition.target = new plugin.google.maps.LatLng();
//------------------------------------------------------------------
latLng = [cameraOptions objectForKey:@"target"];
latitude = [[latLng valueForKey:@"lat"] floatValue];
longitude = [[latLng valueForKey:@"lng"] floatValue];
}
}
//[pluginMap.mapCtrl.view setHidden:YES];
}
camera = [GMSCameraPosition cameraWithLatitude:latitude
longitude:longitude
zoom: zoom
bearing: bearing
viewingAngle: angle];
viewCtrl.map = [GMSMapView mapWithFrame:rect camera:camera];
viewCtrl.view = viewCtrl.map;
//mapType
NSString *typeStr = [initOptions valueForKey:@"mapType"];
if (typeStr) {
NSDictionary *mapTypes = [NSDictionary dictionaryWithObjectsAndKeys:
^() {return kGMSTypeHybrid; }, @"MAP_TYPE_HYBRID",
^() {return kGMSTypeSatellite; }, @"MAP_TYPE_SATELLITE",
^() {return kGMSTypeTerrain; }, @"MAP_TYPE_TERRAIN",
^() {return kGMSTypeNormal; }, @"MAP_TYPE_NORMAL",
^() {return kGMSTypeNone; }, @"MAP_TYPE_NONE",
nil];
typedef GMSMapViewType (^CaseBlock)();
GMSMapViewType mapType;
CaseBlock caseBlock = mapTypes[typeStr];
if (caseBlock) {
// Change the map type
mapType = caseBlock();
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
((GMSMapView *)(viewCtrl.view)).mapType = mapType;
}];
}
}
viewCtrl.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
//indoor display
((GMSMapView *)(viewCtrl.view)).delegate = viewCtrl;
((GMSMapView *)(viewCtrl.view)).indoorDisplay.delegate = viewCtrl;
[self.pluginLayer addPluginOverlay:viewCtrl];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[pluginMap getMap:command];
});
});
}
/**
* Intialize the panorama
*/
- (void)getPanorama:(CDVInvokedUrlCommand *)command {
if (self.pluginLayer != nil) {
self.pluginLayer.isSuspended = false;
}
dispatch_async(dispatch_get_main_queue(), ^{
CDVViewController *cdvViewController = (CDVViewController*)self.viewController;
NSDictionary *meta = [command.arguments objectAtIndex:0];
NSString *panoramaId = [meta objectForKey:@"__pgmId"];
NSString *divId = [command.arguments objectAtIndex:2];
// Wrapper view
PluginStreetViewPanoramaController* panoramaCtrl = [[PluginStreetViewPanoramaController alloc] initWithOptions:nil];
panoramaCtrl.webView = self.webView;
panoramaCtrl.isFullScreen = YES;
panoramaCtrl.overlayId = panoramaId;
panoramaCtrl.divId = divId;
panoramaCtrl.title = panoramaId;
//[mapCtrl.view setHidden:YES];
// Create an instance of the PluginStreetViewPanorama class everytime.
PluginStreetViewPanorama *pluginStreetView = [[PluginStreetViewPanorama alloc] init];
[pluginStreetView pluginInitialize];
// Hack:
// In order to load the plugin instance of the same class but different names,
// register the street view plugin instance into the pluginObjects directly.
if ([pluginStreetView respondsToSelector:@selector(setViewController:)]) {
[pluginStreetView setViewController:cdvViewController];
}
if ([pluginStreetView respondsToSelector:@selector(setCommandDelegate:)]) {
[pluginStreetView setCommandDelegate:cdvViewController.commandDelegate];
}
[cdvViewController.pluginObjects setObject:pluginStreetView forKey:panoramaId];
[cdvViewController.pluginsMap setValue:panoramaId forKey:panoramaId];
[pluginStreetView pluginInitialize];
[self.viewPlugins setObject:pluginStreetView forKey:panoramaId];
CGRect rect = CGRectZero;
// Sets the panorama div id.
pluginStreetView.panoramaCtrl = panoramaCtrl;
pluginStreetView.panoramaCtrl.divId = divId;
if (pluginStreetView.panoramaCtrl.divId != nil) {
NSDictionary *domInfo = [self.pluginLayer.pluginScrollView.HTMLNodes objectForKey:pluginStreetView.panoramaCtrl.divId];
if (domInfo != nil) {
rect = CGRectFromString([domInfo objectForKey:@"size"]);
}
}
panoramaCtrl.panoramaView = [GMSPanoramaView panoramaWithFrame:rect nearCoordinate: CLLocationCoordinate2DMake(0, 0)];
panoramaCtrl.view = panoramaCtrl.panoramaView;
panoramaCtrl.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
((GMSPanoramaView *)(panoramaCtrl.view)).delegate = panoramaCtrl;
[self.pluginLayer addPluginOverlay:panoramaCtrl];
[pluginStreetView getPanorama:command];
});
}
- (void)clearHtmlElements:(CDVInvokedUrlCommand *)command {
[self.executeQueue addOperationWithBlock:^{
if (self.pluginLayer != nil) {
[self.pluginLayer clearHTMLElements];
}
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}
- (void)resume:(CDVInvokedUrlCommand *)command {
if (self.pluginLayer != nil) {
self.pluginLayer.isSuspended = NO;
}
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
- (void)pause:(CDVInvokedUrlCommand *)command {
if (self.pluginLayer != nil) {
if (!self.pluginLayer.isSuspended) {
self.pluginLayer.isSuspended = YES;
// cancel the timer
[self.pluginLayer stopRedrawTimer];
//[self.pluginLayer resizeTask:nil];
}
}
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
- (void)updateMapPositionOnly:(CDVInvokedUrlCommand *)command {
[self.executeQueue addOperationWithBlock:^{
if (self.pluginLayer != nil) {
NSDictionary *elementsDic = [command.arguments objectAtIndex:0];
NSString *domId;
CGRect rect = CGRectMake(0, 0, 0, 0);
NSMutableDictionary *domInfo, *size, *currentDomInfo;
@synchronized(self.pluginLayer.pluginScrollView.HTMLNodes) {
for (domId in elementsDic) {
domInfo = [elementsDic objectForKey:domId];
size = [domInfo objectForKey:@"size"];
rect.origin.x = [[size objectForKey:@"left"] doubleValue];
rect.origin.y = [[size objectForKey:@"top"] doubleValue];
rect.size.width = [[size objectForKey:@"width"] doubleValue];
rect.size.height = [[size objectForKey:@"height"] doubleValue];
currentDomInfo = [self.pluginLayer.pluginScrollView.HTMLNodes objectForKey:domId];
if (currentDomInfo == nil) {
currentDomInfo = domInfo;
}
[currentDomInfo setValue:NSStringFromCGRect(rect) forKey:@"size"];
[self.pluginLayer.pluginScrollView.HTMLNodes setObject:currentDomInfo forKey:domId];
}
}
self.pluginLayer.isSuspended = false;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self.pluginLayer resizeTask:nil];
}];
}
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}
- (void)putHtmlElements:(CDVInvokedUrlCommand *)command {
[self.executeQueue addOperationWithBlock:^{
NSDictionary *elements = [command.arguments objectAtIndex:0];
if (self.pluginLayer != nil) {
[self.pluginLayer putHTMLElements:elements];
}
/*
if (self.pluginLayer.needUpdatePosition) {
self.pluginLayer.needUpdatePosition = NO;
NSArray *keys=[self.viewPlugins allKeys];
NSString *mapId;
PluginMap *pluginMap;
for (int i = 0; i < [keys count]; i++) {
mapId = [keys objectAtIndex:i];
pluginMap = [self.viewPlugins objectForKey:mapId];
[self.pluginLayer updateViewPosition:pluginMap.mapCtrl];
}
}
*/
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
pluginResult = nil;
elements = nil;
}];
}
@end
@@ -0,0 +1,31 @@
//
// GMSCoordinateBounds+Geometry.h
//
// Created by Marius Feldmann on 8/12/14.
// Copyright (c) 2014 Marius Feldmann. All rights reserved.
//
#import <GoogleMaps/GoogleMaps.h>
@interface GMSCoordinateBounds (MFAdditions)
/** The North-West corner of these bounds. */
- (CLLocationCoordinate2D)southEast;
/** The South-East corner of these bounds. */
- (CLLocationCoordinate2D)northWest;
/** The center coordinate of these bounds. */
- (CLLocationCoordinate2D)center;
/** Return the path of the rect. */
- (GMSPath *)path;
/**
* Returns an NSArray of GMSCoordinateBounds
* Divides the current rectangular bounding box
* into |numberOfRects| smaller boxes.
*/
- (NSArray *)divideIntoNumberOfBoxes:(NSInteger)numberOfBoxesl;
@end
@@ -0,0 +1,106 @@
//
// GMSCoordinateBounds+Geometry.m
//
// Created by Marius Feldmann on 8/12/14.
// Copyright (c) 2014 Marius Feldmann. All rights reserved.
//
#import "GMSCoordinateBounds+Geometry.h"
#define RADIANS_TO_DEGREES(radians) ((radians) * (180.0 / M_PI))
@implementation GMSCoordinateBounds (MFAdditions)
- (CLLocationCoordinate2D)southEast {
CLLocationDegrees southEastLat = self.southWest.latitude;
CLLocationDegrees southEastLng = self.northEast.longitude;
CLLocationCoordinate2D southEast = CLLocationCoordinate2DMake(southEastLat, southEastLng);
return southEast;
}
- (CLLocationCoordinate2D)northWest {
CLLocationDegrees northWestLat = self.northEast.latitude;
CLLocationDegrees northWestLng = self.southWest.longitude;
CLLocationCoordinate2D northWest = CLLocationCoordinate2DMake(northWestLat, northWestLng);
return northWest;
}
- (CLLocationCoordinate2D)center {
double fraction = 0.5;
CLLocationCoordinate2D center = GMSGeometryInterpolate(self.northEast, self.southWest, fraction);
return center;
}
- (GMSPath *)path {
GMSMutablePath *path = [[GMSMutablePath alloc] init];
[path addCoordinate:self.northWest];
[path addCoordinate:self.northEast];
[path addCoordinate:self.southEast];
[path addCoordinate:self.southWest];
return path;
}
- (NSArray *)divideIntoNumberOfBoxes:(NSInteger)numberOfBoxes {
NSMutableArray *rectArray = [[NSMutableArray alloc] init];
NSInteger columns = ceil(sqrt(numberOfBoxes));
NSInteger fullRows = numberOfBoxes / columns;
NSInteger orphans = numberOfBoxes % columns;
double width = GMSGeometryDistance(self.northWest, self.northEast);
double boxWidth = width / columns;
double height = GMSGeometryDistance(self.northEast, self.southEast);
double boxHeight = height / (orphans == 0 ? fullRows : (fullRows+1));
for (int y=0; y<fullRows; y++) {
for (int x=0; x<columns; x++) {
// Calculate the diagonal of the rect
double diagonal = sqrt(pow(boxWidth, 2) + pow(boxHeight, 2));
// Get the angle of the diagonal
double angle = 180 + RADIANS_TO_DEGREES(atan2(boxWidth, boxHeight));
// Get the northEast Coord by moving the northEast Coord to the right
CLLocationCoordinate2D northEastCoord = GMSGeometryOffset(self.northWest, boxWidth*(x+1), 90);
// Make sure we are in the right row
northEastCoord = GMSGeometryOffset(northEastCoord, boxHeight*y, 180);
// Get the southWest Coord by following the diagonal line from the northEast Coord
CLLocationCoordinate2D southWestCoord = GMSGeometryOffset(northEastCoord, diagonal, angle);
// Create the new rect
GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] initWithCoordinate:southWestCoord coordinate:northEastCoord];
[rectArray addObject:bounds];
}
}
if (orphans > 0) {
NSInteger orphanWidth = width / orphans;
for (int x=0; x<orphans; x++) {
double diagonal = sqrt(pow(orphanWidth, 2) + pow(boxHeight, 2));
double angle = 180 + RADIANS_TO_DEGREES(atan2(orphanWidth, boxHeight));
CLLocationCoordinate2D northEastCoord = GMSGeometryOffset(self.northWest, orphanWidth*(x+1), 90);
northEastCoord = GMSGeometryOffset(northEastCoord, boxHeight*fullRows+1, 180);
CLLocationCoordinate2D southWestCoord = GMSGeometryOffset(northEastCoord, diagonal, angle);
GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] initWithCoordinate:southWestCoord coordinate:northEastCoord];
[rectArray addObject:bounds];
}
}
return rectArray;
}
@end
@@ -0,0 +1,17 @@
//
// IPluginProtocol.h
// cordova-googlemaps-plugin v2
//
// Created by Masashi Katsumata.
//
//
#import <Foundation/Foundation.h>
#import <Cordova/CDV.h>
#import "PluginViewController.h"
@protocol IPluginProtocol <NSObject>
//- (void)onHookedPluginResult:(CDVPluginResult*)result callbackId:(NSString*)callbackId;
- (void)pluginUnload;
- (void)setPluginViewController: (PluginViewController*)viewCtrl;
@end
@@ -0,0 +1,14 @@
//
// IPluginView
// cordova-googlemaps-plugin v2
//
// Created by Masashi Katsumata.
//
//
#import <Foundation/Foundation.h>
@protocol IPluginView <NSObject>
- (void)attachToWebView:(CDVInvokedUrlCommand*)command;
- (void)detachFromWebView:(CDVInvokedUrlCommand*)command;
@end
@@ -0,0 +1,31 @@
//
// GMSCoordinateBounds+Geometry.h
//
// Created by Marius Feldmann on 8/12/14.
// Copyright (c) 2014 Marius Feldmann. All rights reserved.
//
#import <GoogleMaps/GoogleMaps.h>
@interface GMSCoordinateBounds (MFAdditions)
/** The North-West corner of these bounds. */
- (CLLocationCoordinate2D)southEast;
/** The South-East corner of these bounds. */
- (CLLocationCoordinate2D)northWest;
/** The center coordinate of these bounds. */
- (CLLocationCoordinate2D)center;
/** Return the path of the rect. */
- (GMSPath *)path;
/**
* Returns an NSArray of GMSCoordinateBounds
* Divides the current rectangular bounding box
* into |numberOfRects| smaller boxes.
*/
- (NSArray *)divideIntoNumberOfBoxes:(NSInteger)numberOfBoxesl;
@end
@@ -0,0 +1,106 @@
//
// GMSCoordinateBounds+Geometry.m
//
// Created by Marius Feldmann on 8/12/14.
// Copyright (c) 2014 Marius Feldmann. All rights reserved.
//
#import "GMSCoordinateBounds+Geometry.h"
#define RADIANS_TO_DEGREES(radians) ((radians) * (180.0 / M_PI))
@implementation GMSCoordinateBounds (MFAdditions)
- (CLLocationCoordinate2D)southEast {
CLLocationDegrees southEastLat = self.southWest.latitude;
CLLocationDegrees southEastLng = self.northEast.longitude;
CLLocationCoordinate2D southEast = CLLocationCoordinate2DMake(southEastLat, southEastLng);
return southEast;
}
- (CLLocationCoordinate2D)northWest {
CLLocationDegrees northWestLat = self.northEast.latitude;
CLLocationDegrees northWestLng = self.southWest.longitude;
CLLocationCoordinate2D northWest = CLLocationCoordinate2DMake(northWestLat, northWestLng);
return northWest;
}
- (CLLocationCoordinate2D)center {
double fraction = 0.5;
CLLocationCoordinate2D center = GMSGeometryInterpolate(self.northEast, self.southWest, fraction);
return center;
}
- (GMSPath *)path {
GMSMutablePath *path = [[GMSMutablePath alloc] init];
[path addCoordinate:self.northWest];
[path addCoordinate:self.northEast];
[path addCoordinate:self.southEast];
[path addCoordinate:self.southWest];
return path;
}
- (NSArray *)divideIntoNumberOfBoxes:(NSInteger)numberOfBoxes {
NSMutableArray *rectArray = [[NSMutableArray alloc] init];
NSInteger columns = ceil(sqrt(numberOfBoxes));
NSInteger fullRows = numberOfBoxes / columns;
NSInteger orphans = numberOfBoxes % columns;
double width = GMSGeometryDistance(self.northWest, self.northEast);
double boxWidth = width / columns;
double height = GMSGeometryDistance(self.northEast, self.southEast);
double boxHeight = height / (orphans == 0 ? fullRows : (fullRows+1));
for (int y=0; y<fullRows; y++) {
for (int x=0; x<columns; x++) {
// Calculate the diagonal of the rect
double diagonal = sqrt(pow(boxWidth, 2) + pow(boxHeight, 2));
// Get the angle of the diagonal
double angle = 180 + RADIANS_TO_DEGREES(atan2(boxWidth, boxHeight));
// Get the northEast Coord by moving the northEast Coord to the right
CLLocationCoordinate2D northEastCoord = GMSGeometryOffset(self.northWest, boxWidth*(x+1), 90);
// Make sure we are in the right row
northEastCoord = GMSGeometryOffset(northEastCoord, boxHeight*y, 180);
// Get the southWest Coord by following the diagonal line from the northEast Coord
CLLocationCoordinate2D southWestCoord = GMSGeometryOffset(northEastCoord, diagonal, angle);
// Create the new rect
GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] initWithCoordinate:southWestCoord coordinate:northEastCoord];
[rectArray addObject:bounds];
}
}
if (orphans > 0) {
NSInteger orphanWidth = width / orphans;
for (int x=0; x<orphans; x++) {
double diagonal = sqrt(pow(orphanWidth, 2) + pow(boxHeight, 2));
double angle = 180 + RADIANS_TO_DEGREES(atan2(orphanWidth, boxHeight));
CLLocationCoordinate2D northEastCoord = GMSGeometryOffset(self.northWest, orphanWidth*(x+1), 90);
northEastCoord = GMSGeometryOffset(northEastCoord, boxHeight*fullRows+1, 180);
CLLocationCoordinate2D southWestCoord = GMSGeometryOffset(northEastCoord, diagonal, angle);
GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] initWithCoordinate:southWestCoord coordinate:northEastCoord];
[rectArray addObject:bounds];
}
}
return rectArray;
}
@end
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) {{{year}}} {{{fullname}}}
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,11 @@
# MFGoogleMapAdditions
A couple of helpful categories for the Google Maps SDK.
## Installation
Simply add the `GMSCoordinateBounds+Geometry.h` to your project
Enjoy.
@@ -0,0 +1,45 @@
//
// MyPluginLayer.h
// cordova-googlemaps-plugin v2
//
// Created by Masashi Katsumata.
//
//
#import <UIKit/UIKit.h>
#import "PluginMapViewController.h"
#import "MyPluginScrollView.h"
@interface CGDOMRect : NSObject
@property CGFloat left;
@property CGFloat right;
@property CGFloat top;
@property CGFloat bottom;
@end
@interface OverflowCSS : NSObject
@property BOOL cropX;
@property BOOL cropY;
@property CGRect rect;
@end
@interface MyPluginLayer : UIView<UIScrollViewDelegate>
@property (nonatomic) UIView *webView;
@property (nonatomic) MyPluginScrollView *pluginScrollView;
@property (atomic) NSTimer *redrawTimer;
@property (nonatomic) BOOL isSuspended;
@property (nonatomic) NSOperationQueue *executeQueue;
@property (atomic, strong) NSObject *_lockObject;
@property (atomic, strong) NSMutableDictionary *CACHE_FIND_DOM;
- (id)initWithWebView:(UIView *)webView;
- (void)resizeTask:(NSTimer *)timer;
- (void)clearHTMLElements;
- (void)putHTMLElements:(NSDictionary *)elementsDic;
- (void)addPluginOverlay:(PluginViewController *)pluginViewCtrl;
- (void)removePluginOverlay:(PluginViewController *)pluginViewCtrl;
- (void)updateViewPosition:(PluginViewController *)pluginViewCtrl;
- (void)startRedrawTimer;
- (void)stopRedrawTimer;
@end
@@ -0,0 +1,695 @@
//
// MyPluginLayer.m
// cordova-googlemaps-plugin v2
//
// Created by Masashi Katsumata.
//
//
#import "MyPluginLayer.h"
@implementation OverflowCSS : NSObject
BOOL cropX;
BOOL cropY;
CGRect rect;
@end
@implementation CGDOMRect : NSObject
CGFloat left;
CGFloat right;
CGFloat top;
CGFloat bottom;
@end
@implementation MyPluginLayer
- (id)initWithWebView:(UIView *)webView {
self.executeQueue = [NSOperationQueue new];
self._lockObject = [[NSObject alloc] init];
self.CACHE_FIND_DOM = [NSMutableDictionary dictionary];
self = [super initWithFrame:[webView frame]];
self.webView = webView;
self.isSuspended = false;
self.opaque = NO;
[self.webView removeFromSuperview];
// prevent webView from bouncing
if ([self.webView respondsToSelector:@selector(scrollView)]) {
((UIScrollView*)[self.webView scrollView]).bounces = NO;
} else {
for (id subview in [self.webView subviews]) {
if ([[subview class] isSubclassOfClass:[UIScrollView class]]) {
((UIScrollView*)subview).bounces = NO;
}
}
}
self.pluginScrollView = [[MyPluginScrollView alloc] initWithFrame:[self.webView frame]];
self.pluginScrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.pluginScrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
UIView *uiview = self.webView;
uiview.scrollView.delegate = self;
[self.pluginScrollView setContentSize:self.webView.scrollView.frame.size ];
[self addSubview:self.pluginScrollView];
[self addSubview:self.webView];
// dispatch_queue_t q_background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
//
// dispatch_async(q_background, ^{
// [self startRedrawTimer];
// });
return self;
}
- (void)startRedrawTimer {
@synchronized(self._lockObject) {
if (!self.redrawTimer) {
self.redrawTimer = [NSTimer scheduledTimerWithTimeInterval:0.1
target:self
selector:@selector(resizeTask:)
userInfo:nil
repeats:YES];
}
}
}
- (void)stopRedrawTimer {
@synchronized(self._lockObject) {
if (self.redrawTimer) {
[self.redrawTimer invalidate];
self.redrawTimer = nil;
}
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
CGPoint offset = self.webView.scrollView.contentOffset;
self.pluginScrollView.contentOffset = offset;
}
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
CGPoint offset = self.webView.scrollView.contentOffset;
self.pluginScrollView.contentOffset = offset;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGPoint offset = self.webView.scrollView.contentOffset;
self.pluginScrollView.contentOffset = offset;
}
- (void)clearHTMLElements {
@synchronized(self.pluginScrollView.HTMLNodes) {
NSMutableDictionary *domInfo;
NSString *domId;
NSArray *keys=[self.pluginScrollView.HTMLNodes allKeys];
NSArray *keys2;
int i, j;
for (i = 0; i < [keys count]; i++) {
domId = [keys objectAtIndex:i];
domInfo = [self.pluginScrollView.HTMLNodes objectForKey:domId];
if (domInfo) {
keys2 = [domInfo allKeys];
for (j = 0; j < [keys2 count]; j++) {
[domInfo removeObjectForKey:[keys2 objectAtIndex:j]];
}
domInfo = nil;
}
[self.pluginScrollView.HTMLNodes removeObjectForKey:domId];
}
}
}
- (void)putHTMLElements:(NSDictionary *)elementsDic {
[self.executeQueue addOperationWithBlock:^{
CGRect rect = CGRectMake(0, 0, 0, 0);
NSMutableDictionary *domInfo, *size;
NSString *domId;
@synchronized(self._lockObject) {
if (self.pluginScrollView.HTMLNodes != nil) {
NSArray *keys=[self.pluginScrollView.HTMLNodes allKeys];
NSArray *keys2;
int i, j;
for (i = 0; i < [keys count]; i++) {
domId = [keys objectAtIndex:i];
domInfo = [self.pluginScrollView.HTMLNodes objectForKey:domId];
if (domInfo) {
keys2 = [domInfo allKeys];
for (j = 0; j < [keys2 count]; j++) {
[domInfo removeObjectForKey:[keys2 objectAtIndex:j]];
}
domInfo = nil;
}
[self.pluginScrollView.HTMLNodes removeObjectForKey:domId];
}
}
self.pluginScrollView.HTMLNodes = nil;
NSMutableDictionary *newBuffer = [[NSMutableDictionary alloc] init];
for (domId in elementsDic) {
domInfo = [elementsDic objectForKey:domId];
size = [domInfo objectForKey:@"size"];
rect.origin.x = [[size objectForKey:@"left"] doubleValue];
rect.origin.y = [[size objectForKey:@"top"] doubleValue];
rect.size.width = [[size objectForKey:@"width"] doubleValue];
rect.size.height = [[size objectForKey:@"height"] doubleValue];
[domInfo setValue:NSStringFromCGRect(rect) forKey:@"size"];
[newBuffer setObject:domInfo forKey:domId];
domInfo = nil;
size = nil;
}
self.pluginScrollView.HTMLNodes = newBuffer;
}
}];
}
- (void)addPluginOverlay:(PluginViewController *)pluginViewCtrl {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// Hold the mapCtrl instance with mapId.
[self.pluginScrollView.mapCtrls setObject:pluginViewCtrl forKey:pluginViewCtrl.overlayId];
//When showing this plugin multiple times, a UIView from other plugin might be overlayed on
//top of this plugin so we must switch the view with the view that is right before the webview
//and update the background color for the webview to prevent issues with non transparent webview
[self exchangeSubviewAtIndex:[self.subviews indexOfObject:self.webView]-1 withSubviewAtIndex:[self.subviews indexOfObject:self.pluginScrollView]];
self.webView.backgroundColor = [UIColor clearColor];
// Add the mapView under the scroll view.
[pluginViewCtrl.view setTag:pluginViewCtrl.viewDepth];
[self.pluginScrollView attachView:pluginViewCtrl.view depth:pluginViewCtrl.viewDepth];
pluginViewCtrl.attached = YES;
[self updateViewPosition:pluginViewCtrl];
}];
}
- (void)removePluginOverlay:(PluginViewController *)pluginViewCtrl {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
pluginViewCtrl.attached = NO;
// Remove the mapCtrl instance with mapId.
[self.pluginScrollView.mapCtrls removeObjectForKey:pluginViewCtrl.overlayId];
// Remove the mapView from the scroll view.
[pluginViewCtrl willMoveToParentViewController:nil];
[pluginViewCtrl.view removeFromSuperview];
[pluginViewCtrl removeFromParentViewController];
[self.pluginScrollView detachView:pluginViewCtrl.view];
//[pluginViewCtrl.view setFrame:CGRectMake(0, -pluginViewCtrl.view.frame.size.height, pluginViewCtrl.view.frame.size.width, pluginViewCtrl.view.frame.size.height)];
//[pluginViewCtrl.view setNeedsDisplay];
}];
}
- (void)resizeTask:(NSTimer *)timer {
dispatch_async(dispatch_get_main_queue(), ^{
NSArray *keys=[self.pluginScrollView.mapCtrls allKeys];
NSString *mapId;
PluginMapViewController *mapCtrl;
for (int i = 0; i < [keys count]; i++) {
mapId = [keys objectAtIndex:i];
mapCtrl = [self.pluginScrollView.mapCtrls objectForKey:mapId];
[self updateViewPosition:mapCtrl];
}
});
}
- (void)updateViewPosition:(PluginViewController *)pluginViewCtrl {
CGFloat zoomScale = self.webView.scrollView.zoomScale;
[self.pluginScrollView setFrame:self.webView.frame];
CGPoint offset = self.webView.scrollView.contentOffset;
offset.x *= zoomScale;
offset.y *= zoomScale;
[self.pluginScrollView setContentOffset:offset];
if (!pluginViewCtrl.divId) {
return;
}
NSDictionary *domInfo = nil;
@synchronized(self.pluginScrollView.HTMLNodes) {
domInfo = [self.pluginScrollView.HTMLNodes objectForKey:pluginViewCtrl.divId];
if (domInfo == nil) {
return;
}
}
NSString *rectStr = [domInfo objectForKey:@"size"];
if (rectStr == nil || [rectStr isEqual: @"null"]) {
return;
}
__block CGRect rect = CGRectFromString(rectStr);
if (rect.origin.x == 0 &&
rect.origin.y == 0 &&
rect.size.width == 0 &&
rect.size.height == 0) {
return;
}
rect.origin.x *= zoomScale;
rect.origin.y *= zoomScale;
rect.size.width *= zoomScale;
rect.size.height *= zoomScale;
rect.origin.x += offset.x;
rect.origin.y += offset.y;
float webviewWidth = self.webView.frame.size.width;
float webviewHeight = self.webView.frame.size.height;
// Is the map is displayed?
if (pluginViewCtrl.view.hidden == NO) {
if (pluginViewCtrl.isRenderedAtOnce == YES ||
([pluginViewCtrl.overlayId hasPrefix:@"panorama_"] ||
([pluginViewCtrl.overlayId hasPrefix:@"map_"] &&
((GMSMapView *)pluginViewCtrl.view).mapType != kGMSTypeSatellite &&
((GMSMapView *)pluginViewCtrl.view).mapType != kGMSTypeHybrid)
)) {
if (rect.origin.y + rect.size.height >= offset.y &&
rect.origin.x + rect.size.width >= offset.x &&
rect.origin.y < offset.y + webviewHeight &&
rect.origin.x < offset.x + webviewWidth) {
// Attach the map view to the parent.
[pluginViewCtrl.view setTag:pluginViewCtrl.viewDepth];
[self.pluginScrollView attachView:pluginViewCtrl.view depth:pluginViewCtrl.viewDepth];
} else {
// Detach from the parent view
[pluginViewCtrl.view removeFromSuperview];
}
}
}
if (pluginViewCtrl.isRenderedAtOnce == YES &&
rect.origin.x == pluginViewCtrl.view.frame.origin.x &&
rect.origin.y == pluginViewCtrl.view.frame.origin.y &&
rect.size.width == pluginViewCtrl.view.frame.size.width &&
rect.size.height == pluginViewCtrl.view.frame.size.height) {
return;
}
if (pluginViewCtrl.attached) {
// if (pluginViewCtrl.isRenderedAtOnce) {
//
// __block int zPosition = pluginViewCtrl.view.layer.zPosition;
// [UIView animateWithDuration:0.075f animations:^{
// [pluginViewCtrl.view setFrame:rect];
// pluginViewCtrl.view.layer.zPosition = zPosition;
// //rect.origin.x = 0;
// //rect.origin.y = 0;
// }];
// } else {
[pluginViewCtrl.view setFrame:rect];
//rect.origin.x = 0;
//rect.origin.y = 0;
pluginViewCtrl.isRenderedAtOnce = YES;
// }
} else {
[pluginViewCtrl.view setFrame:CGRectMake(0, -pluginViewCtrl.view.frame.size.height, pluginViewCtrl.view.frame.size.width, pluginViewCtrl.view.frame.size.height)];
}
}
- (void)execJS: (NSString *)jsString {
if ([self.webView respondsToSelector:@selector(stringByEvaluatingJavaScriptFromString:)]) {
[self.webView performSelector:@selector(stringByEvaluatingJavaScriptFromString:) withObject:jsString];
} else if ([self.webView respondsToSelector:@selector(evaluateJavaScript:completionHandler:)]) {
[self.webView performSelector:@selector(evaluateJavaScript:completionHandler:) withObject:jsString withObject:nil];
}
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
CGPoint browserClickPoint = CGPointMake(point.x - self.webView.frame.origin.x, point.y - self.webView.frame.origin.y);
//NSLog(@"-->zoomScale = %f", self.webView.scrollView.zoomScale);
[self.CACHE_FIND_DOM removeAllObjects];
// Check other views of other plugins before this plugin
// e.g. PhoneGap-Plugin-ListPicker, etc
UIView *subview;
NSArray *subviews = [self.webView.superview subviews];
//CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame;
//CGPoint subviewPoint = CGPointMake(browserClickPoint.x, browserClickPoint.y - statusBarFrame.size.height);
for (int i = ((int)[subviews count] - 1); i >= 0; i--) {
subview = [subviews objectAtIndex: i];
//NSLog(@"--->subview[%d] = %@", i, subview);
// we only want to check against other views
if (subview == self.pluginScrollView) {
continue;
}
if (subview.isHidden || !subview.isUserInteractionEnabled) {
continue;
}
CGPoint subviewPoint = CGPointMake(point.x, point.y - subview.frame.origin.y);
UIView *hit = [subview hitTest:subviewPoint withEvent:event];
if (hit) {
if (subview == self.webView) {
break;
}
return hit;
}
}
if (self.pluginScrollView.mapCtrls == nil || self.pluginScrollView.mapCtrls.count == 0) {
// Assumes all touches for the browser
//NSLog(@"--->browser!");
return [self.webView hitTest:browserClickPoint withEvent:event];
}
float offsetX = self.webView.scrollView.contentOffset.x;
float offsetY = self.webView.scrollView.contentOffset.y;
float webviewWidth = self.webView.frame.size.width;
float webviewHeight = self.webView.frame.size.height;
CGRect rect;
NSEnumerator *mapIDs = [self.pluginScrollView.mapCtrls keyEnumerator];
PluginMapViewController *mapCtrl;
id mapId;
NSString *clickedDomId;
CGFloat zoomScale = [[UIScreen mainScreen] scale];
offsetY *= zoomScale;
offsetX *= zoomScale;
webviewWidth *= zoomScale;
webviewHeight *= zoomScale;
NSDictionary *domInfo;
@synchronized(self.pluginScrollView.HTMLNodes) {
//NSLog(@"--->browserClickPoint = %f, %f", browserClickPoint.x, browserClickPoint.y);
clickedDomId = [self findClickedDom:@"root" withPoint:browserClickPoint isMapChild:NO overflow:nil];
//NSLog(@"--->clickedDomId = %@", clickedDomId);
while(mapId = [mapIDs nextObject]) {
mapCtrl = [self.pluginScrollView.mapCtrls objectForKey:mapId];
if (!mapCtrl.divId) {
continue;
}
domInfo =[self.pluginScrollView.HTMLNodes objectForKey:mapCtrl.divId];
rect = CGRectFromString([domInfo objectForKey:@"size"]);
// Is the map clickable?
if (mapCtrl.clickable == NO) {
//NSLog(@"--> map (%@) is not clickable.", mapCtrl.overlayId);
continue;
}
// Is the map displayed?
if (rect.origin.y + rect.size.height < 0 ||
rect.origin.x + rect.size.width < 0 ||
rect.origin.y > offsetY + webviewHeight ||
rect.origin.x > offsetX + webviewWidth ||
mapCtrl.view.hidden == YES) {
//NSLog(@"--> map (%@) is not displayed.", mapCtrl.overlayId);
continue;
}
// Is the clicked point is in the map rectangle?
if (CGRectContainsPoint(rect, browserClickPoint)) {
//NSLog(@"--->in map");
//NSLog(@"--->clickedDomId = %@, mapCtrl.divId = %@", clickedDomId, mapCtrl.divId);
if ([mapCtrl.divId isEqualToString:clickedDomId]) {
// If user click on the map, return the mapCtrl.view.
CGPoint mapPoint = CGPointMake(point.x - mapCtrl.view.frame.origin.x - self.webView.frame.origin.x, point.y - mapCtrl.view.frame.origin.y - self.webView.frame.origin.y);
UIView *hitView =[mapCtrl.view hitTest:mapPoint withEvent:event];
//[mapCtrl mapView:mapCtrl.map didTapAtPoint:point2];
return hitView;
}
}
}
}
//NSLog(@"--->in browser!");
return [self.webView hitTest:browserClickPoint withEvent:event];
}
- (NSString *)findClickedDom:(NSString *)domId withPoint:(CGPoint)clickPoint isMapChild:(BOOL)isMapChild overflow:(OverflowCSS *)overflow {
if ([self.CACHE_FIND_DOM objectForKey:domId]) {
NSString *cacheResult = [self.CACHE_FIND_DOM objectForKey:domId];
if ([@"(null)" isEqualToString:cacheResult]) {
cacheResult = nil;
}
return cacheResult;
}
OverflowCSS *overflow1 = nil;
if (overflow) {
overflow1 = [[OverflowCSS alloc] init];
overflow1.cropX = overflow.cropX;
overflow1.cropY = overflow.cropY;
overflow1.rect = CGRectMake(overflow.rect.origin.x, overflow.rect.origin.y, overflow.rect.size.width, overflow.rect.size.height);
}
NSDictionary *domInfo = [self.pluginScrollView.HTMLNodes objectForKey:domId];
NSDictionary *domInfoChild;
NSString *maxDomId = nil;
CGRect rect, tmpRect;
CGDOMRect *rect2, *rect3;
NSDictionary *zIndexProp;
unsigned long containMapCnt = 0;
domInfo = [self.pluginScrollView.HTMLNodes objectForKey:domId];
if ([domInfo objectForKey:@"containMapIDs"]) {
NSDictionary *containMapIDs = [domInfo objectForKey:@"containMapIDs"];
containMapCnt = [[containMapIDs allKeys] count];
}
isMapChild = isMapChild || [[domInfo objectForKey:@"isMap"] boolValue] || false;
//NSLog(@"---- domId = %@, containMapCnt = %ld, isMapChild = %@", domId, containMapCnt, isMapChild ? @"YES":@"NO");
NSString *pointerEvents = [domInfo objectForKey:@"pointerEvents"];
NSString *overflowX = [domInfo objectForKey:@"overflowX"];
NSString *overflowY = [domInfo objectForKey:@"overflowY"];
if ([@"hidden" isEqualToString:overflowX] || [@"scroll" isEqualToString:overflowX] ||
[@"hidden" isEqualToString:overflowY] || [@"scroll" isEqualToString:overflowY]) {
overflow1 = [[OverflowCSS alloc] init];
overflow1.cropX = [@"hidden" isEqualToString:overflowX] || [@"scroll" isEqualToString:overflowX];
overflow1.cropY = [@"hidden" isEqualToString:overflowY] || [@"scroll" isEqualToString:overflowY];
overflow1.rect = CGRectFromString([domInfo objectForKey:@"size"]);
} else if ([@"visible" isEqualToString:overflowX] || [@"visible" isEqualToString:overflowX]) {
if (overflow1 != nil) {
overflow1.cropX = ![@"visible" isEqualToString:overflowX];
overflow1.cropY = ![@"visible" isEqualToString:overflowY];
}
}
zIndexProp = [domInfo objectForKey:@"zIndex"];
NSArray *children = [domInfo objectForKey:@"children"];
int maxZIndex = -1215752192; // -1 * pow(2, 32) + 1;
if ((containMapCnt > 0 || isMapChild || [@"none" isEqualToString:pointerEvents] ||
[[zIndexProp objectForKey:@"isInherit"] boolValue]) && children != nil && children.count > 0) {
int zIndexValue;
NSString *childId, *grandChildId;
NSArray *grandChildren;
for (int i = (int)children.count - 1; i >= 0; i--) {
childId = [children objectAtIndex:i];
domInfo = [self.pluginScrollView.HTMLNodes objectForKey:childId];
if (!domInfo) {
continue;
}
zIndexProp = [domInfo objectForKey:@"zIndex"];
zIndexValue = [[zIndexProp objectForKey:@"z"] intValue];
if (maxZIndex < zIndexValue || [[zIndexProp objectForKey:@"isInherit"] boolValue]) {
grandChildren = [domInfo objectForKey:@"children"];
if (grandChildren == nil || grandChildren.count == 0) {
tmpRect = CGRectFromString([domInfo objectForKey:@"size"]);
rect2 = [[CGDOMRect alloc] init];
rect2.left = tmpRect.origin.x;
rect2.top = tmpRect.origin.y;
rect2.right = rect2.left + tmpRect.size.width;
rect2.bottom = rect2.top + tmpRect.size.height;
if (overflow1 && ![@"root" isEqualToString:domId]) {
overflow1.rect = CGRectFromString([domInfo objectForKey:@"size"]);
rect3 = [[CGDOMRect alloc] init];
rect3.left = overflow1.rect.origin.x;
rect3.top = overflow1.rect.origin.y;
rect3.right = rect3.left + overflow1.rect.size.width;
rect3.bottom = rect3.top + overflow1.rect.size.height;
if (overflow1.cropX) {
rect2.left = MAX(rect2.left, rect3.left);
rect2.right = MIN(rect2.right, rect3.right);
}
if (overflow1.cropY) {
rect2.top = MAX(rect2.top, rect3.top);
rect2.bottom = MIN(rect2.bottom, rect3.bottom);
}
}
if (clickPoint.x < rect2.left ||
clickPoint.y < rect2.top ||
clickPoint.x > rect2.right ||
clickPoint.y > rect2.bottom) {
continue;
}
if ([@"none" isEqualToString:[domInfo objectForKey:@"pointerEvents"]]) {
continue;
}
if (maxZIndex < zIndexValue) {
maxZIndex = zIndexValue;
maxDomId = childId;
}
} else {
if (zIndexValue < maxZIndex) {
continue;
}
grandChildId = [self findClickedDom:childId withPoint:clickPoint isMapChild: isMapChild overflow:overflow1];
//NSLog(@"-----[570] childId = %@ grandChildId = %@", childId, grandChildId);
if (grandChildId == nil) {
grandChildId = childId;
} else {
domInfoChild = [self.pluginScrollView.HTMLNodes objectForKey:grandChildId];
zIndexProp = [domInfoChild objectForKey:@"zIndex"];
zIndexValue = [[zIndexProp objectForKey:@"z"] intValue];
}
domInfoChild = [self.pluginScrollView.HTMLNodes objectForKey:grandChildId];
rect = CGRectFromString([domInfoChild objectForKey:@"size"]);
rect2 = [[CGDOMRect alloc] init];
rect2.left = rect.origin.x;
rect2.top = rect.origin.y;
rect2.right = rect2.left + rect.size.width;
rect2.bottom = rect2.top + rect.size.height;
/*
if (overflow1 && ![@"root" isEqualToString:domId]) {
NSLog(@"-----[519] grandChildId = %@, size = %@", grandChildId, [domInfo objectForKey:@"size"]);
overflow1.rect = CGRectFromString([domInfo objectForKey:@"size"]);
rect3 = [[CGDOMRect alloc] init];
rect3.left = overflow1.rect.origin.x;
rect3.top = overflow1.rect.origin.y;
rect3.right = rect3.left + overflow1.rect.size.width;
rect3.bottom = rect3.top + overflow1.rect.size.height;
if (overflow1.cropX) {
rect2.left = MAX(rect2.left, rect3.left);
rect2.right = MIN(rect2.right, rect3.right);
}
if (overflow1.cropY) {
rect2.top = MAX(rect2.top, rect3.top);
rect2.bottom = MIN(rect2.bottom, rect3.bottom);
}
}
*/
if (clickPoint.x < rect2.left ||
clickPoint.y < rect2.top ||
clickPoint.x > rect2.right ||
clickPoint.y > rect2.bottom) {
continue;
}
if ([@"none" isEqualToString:[domInfoChild objectForKey:@"pointerEvents"]]) {
continue;
}
if (maxZIndex < zIndexValue) {
maxZIndex = zIndexValue;
maxDomId = grandChildId;
}
}
}
}
}
if (maxDomId == nil && ![@"root" isEqualToString:domId]) {
if ([@"none" isEqualToString:pointerEvents]) {
[self.CACHE_FIND_DOM setValue:@"(null)" forKey:domId];
return nil;
}
domInfo = [self.pluginScrollView.HTMLNodes objectForKey:domId];
rect = CGRectFromString([domInfo objectForKey:@"size"]);
rect2 = [[CGDOMRect alloc] init];
rect2.left = rect.origin.x;
rect2.top = rect.origin.y;
rect2.right = rect2.left + rect.size.width;
rect2.bottom = rect2.top + rect.size.height;
// if (overflow1) {
//
// overflow1.rect = CGRectFromString([domInfo objectForKey:@"size"]);
// rect3 = [[CGDOMRect alloc] init];
// rect3.left = overflow1.rect.origin.x;
// rect3.top = overflow1.rect.origin.y;
// rect3.right = rect3.left + overflow1.rect.size.width;
// rect3.bottom = rect3.top + overflow1.rect.size.height;
//
//
// if (overflow1.cropX) {
// rect2.left = MAX(rect2.left, rect3.left);
// rect2.right = MIN(rect2.right, rect3.right);
// }
// if (overflow1.cropY) {
// rect2.top = MAX(rect2.top, rect3.top);
// rect2.bottom = MIN(rect2.bottom, rect3.bottom);
// }
//
// }
if (clickPoint.x < rect2.left ||
clickPoint.y < rect2.top ||
clickPoint.x > rect2.right ||
clickPoint.y > rect2.bottom) {
[self.CACHE_FIND_DOM setValue:@"(null)" forKey:domId];
return nil;
}
maxDomId = domId;
}
if (!maxDomId) {
[self.CACHE_FIND_DOM setValue:@"(null)" forKey:domId];
} else {
[self.CACHE_FIND_DOM setValue:maxDomId forKey:domId];
}
return maxDomId;
}
@end
@@ -0,0 +1,17 @@
//
// MyPluginScrollView.h
// cordova-googlemaps-plugin v2
//
// Created by Masashi Katsumata.
//
//
#import <UIKit/UIKit.h>
@interface MyPluginScrollView : UIScrollView
@property (nonatomic) NSMutableDictionary *mapCtrls;
@property (nonatomic) NSMutableDictionary *HTMLNodes;
- (void)attachView:(UIView *)view depth:(NSInteger)depth;
- (void)detachView:(UIView *)view;
@end
@@ -0,0 +1,57 @@
//
// MyPluginScrollView.m
// cordova-googlemaps-plugin v2
//
// Created by Masashi Katsumata.
//
//
#import "MyPluginScrollView.h"
@implementation MyPluginScrollView
- (id)initWithFrame:(CGRect)aRect
{
self = [super initWithFrame:aRect];
// Avoid the white bar that appears at the top of the map with iPhone iOS 11
// See problem description here: https://github.com/mapsplugin/cordova-plugin-googlemaps/issues/1909
//
// https://github.com/vidinoti/cordova-plugin-googlemaps/commit/0894072be260223f4ee833e422adf011af9740dd
if (@available(iOS 11, *)) {
self.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
self.HTMLNodes = [[NSMutableDictionary alloc] init];
self.mapCtrls = [[NSMutableDictionary alloc] init];
return self;
}
- (void)attachView:(UIView *)view depth:(NSInteger)depth {
NSArray *subviews = [self subviews];
UIView *subview;
NSInteger tag;
int viewCnt = (int)[subviews count];
int index = viewCnt;
for (int i = 0; i < viewCnt; i++) {
subview = [subviews objectAtIndex: i];
tag = subview.tag;
if (tag == 0) {
continue;
}
if (tag > depth) {
index = i;
break;
}
}
[self insertSubview:view atIndex:index];
}
- (void)detachView:(UIView *)view {
[view removeFromSuperview];
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
return [super hitTest:point withEvent:event];
}
@end
@@ -0,0 +1,25 @@
//
// PluginCircle.h
// cordova-googlemaps-plugin v2
//
// Created by Masashi Katsumata.
//
//
#import "CordovaGoogleMaps.h"
#import "IPluginProtocol.h"
@interface PluginCircle : CDVPlugin<IPluginProtocol>
@property (nonatomic, strong) PluginMapViewController* mapCtrl;
@property (nonatomic) BOOL initialized;
- (void)create:(CDVInvokedUrlCommand*)command;
- (void)setCenter:(CDVInvokedUrlCommand*)command;
- (void)setFillColor:(CDVInvokedUrlCommand*)command;
- (void)setStrokeColor:(CDVInvokedUrlCommand*)command;
- (void)setStrokeWidth:(CDVInvokedUrlCommand*)command;
- (void)setRadius:(CDVInvokedUrlCommand*)command;
- (void)setZIndex:(CDVInvokedUrlCommand*)command;
- (void)setVisible:(CDVInvokedUrlCommand*)command;
- (void)remove:(CDVInvokedUrlCommand*)command;
@end
@@ -0,0 +1,345 @@
//
// PluginCircle.m
// cordova-googlemaps-plugin v2
//
// Created by Masashi Katsumata.
//
//
#import "PluginCircle.h"
@implementation PluginCircle
- (void)pluginInitialize
{
if (self.initialized) {
return;
}
self.initialized = YES;
[super pluginInitialize];
}
- (void)pluginUnload
{
// Plugin destroy
NSArray *keys = [self.mapCtrl.objects allKeys];
NSString *key;
for (int i = 0; i < [keys count]; i++) {
key = [keys objectAtIndex:i];
if ([key hasPrefix:@"circle_property"]) {
key = [key stringByReplacingOccurrencesOfString:@"_property" withString:@""];
GMSCircle *circle = (GMSCircle *)[self.mapCtrl.objects objectForKey:key];
circle.map = nil;
circle = nil;
}
[self.mapCtrl.objects removeObjectForKey:key];
}
key = nil;
keys = nil;
NSString *pluginId = [NSString stringWithFormat:@"%@-circle", self.mapCtrl.overlayId];
CDVViewController *cdvViewController = (CDVViewController*)self.viewController;
[cdvViewController.pluginObjects removeObjectForKey:pluginId];
[cdvViewController.pluginsMap setValue:nil forKey:pluginId];
pluginId = nil;
}
-(void)setPluginViewController:(PluginViewController *)viewCtrl
{
self.mapCtrl = (PluginMapViewController *)viewCtrl;
}
-(void)create:(CDVInvokedUrlCommand *)command
{
NSDictionary *json = [command.arguments objectAtIndex:1];
NSString *idBase = [command.arguments objectAtIndex:2];
NSDictionary *latLng = [json objectForKey:@"center"];
double latitude = [[latLng valueForKey:@"lat"] doubleValue];
double longitude = [[latLng valueForKey:@"lng"] doubleValue];
CLLocationCoordinate2D position = CLLocationCoordinate2DMake(latitude, longitude);
float radius = [[json valueForKey:@"radius"] floatValue];
dispatch_async(dispatch_get_main_queue(), ^{
GMSCircle *circle = [GMSCircle circleWithPosition:position radius:radius];
if ([json valueForKey:@"fillColor"] && [json valueForKey:@"fillColor"] != [NSNull null]) {
circle.fillColor = [[json valueForKey:@"fillColor"] parsePluginColor];
}
if ([json valueForKey:@"strokeColor"] && [json valueForKey:@"strokeColor"] != [NSNull null]) {
circle.strokeColor = [[json valueForKey:@"strokeColor"] parsePluginColor];
}
if ([json valueForKey:@"strokeWidth"] && [json valueForKey:@"strokeWidth"] != [NSNull null]) {
circle.strokeWidth = [[json valueForKey:@"strokeWidth"] floatValue];
}
if ([json valueForKey:@"zIndex"] && [json valueForKey:@"zIndex"] != [NSNull null]) {
circle.zIndex = [[json valueForKey:@"zIndex"] floatValue];
}
BOOL isVisible = YES;
// Visible property
NSString *visibleValue = [NSString stringWithFormat:@"%@", json[@"visible"]];
if ([@"0" isEqualToString:visibleValue]) {
// false
isVisible = NO;
circle.map = nil;
} else {
// true or default
circle.map = ((GMSMapView *)(self.mapCtrl.view));
}
BOOL isClickable = NO;
if ([json valueForKey:@"clickable"] != [NSNull null] && [[json valueForKey:@"clickable"] boolValue]) {
isClickable = YES;
}
// Since this plugin uses own touch-detection,
// set NO to the tappable property.
circle.tappable = NO;
// Store the circle instance into self.objects
NSString *circleId = [NSString stringWithFormat:@"circle_%@", idBase];
circle.title = circleId;
[self.mapCtrl.objects setObject:circle forKey: circleId];
[self.mapCtrl.executeQueue addOperationWithBlock:^{
//---------------------------
// Keep the properties
//---------------------------
NSString *propertyId = [NSString stringWithFormat:@"circle_property_%@", idBase];
// points
NSMutableDictionary *properties = [[NSMutableDictionary alloc] init];
GMSMutablePath *mutablePath = [PluginUtil getMutablePathFromCircle:circle.position radius:circle.radius];
//[properties setObject:mutablePath forKey:@"mutablePath"];
// bounds (pre-calculate for click detection)
[properties setObject:[[GMSCoordinateBounds alloc] initWithPath:mutablePath] forKey:@"bounds"];
// isVisible
[properties setObject:[NSNumber numberWithBool:isVisible] forKey:@"isVisible"];
// isClickable
[properties setObject:[NSNumber numberWithBool:isClickable] forKey:@"isClickable"];
// zIndex
[properties setObject:[NSNumber numberWithFloat:circle.zIndex] forKey:@"zIndex"];;
[self.mapCtrl.objects setObject:properties forKey:propertyId];
//---------------------------
// Result for JS
//---------------------------
NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
[result setObject:circleId forKey:@"__pgmId"];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:result];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
});
}
/**
* Set center
* @params key
*/
-(void)setCenter:(CDVInvokedUrlCommand *)command
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *circleId = [command.arguments objectAtIndex:0];
GMSCircle *circle = [self.mapCtrl.objects objectForKey:circleId];
double latitude = [[command.arguments objectAtIndex:1] doubleValue];
double longitude = [[command.arguments objectAtIndex:2] doubleValue];
CLLocationCoordinate2D center = CLLocationCoordinate2DMake(latitude, longitude);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[circle setPosition:center];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}];
}
/**
* Set fill color
* @params key
*/
-(void)setFillColor:(CDVInvokedUrlCommand *)command
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *circleId = [command.arguments objectAtIndex:0];
GMSCircle *circle = [self.mapCtrl.objects objectForKey:circleId];
NSArray *rgbColor = [command.arguments objectAtIndex:1];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[circle setFillColor:[rgbColor parsePluginColor]];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}];
}
/**
* Set stroke color
* @params key
*/
-(void)setStrokeColor:(CDVInvokedUrlCommand *)command
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *circleId = [command.arguments objectAtIndex:0];
GMSCircle *circle = [self.mapCtrl.objects objectForKey:circleId];
NSArray *rgbColor = [command.arguments objectAtIndex:1];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[circle setStrokeColor:[rgbColor parsePluginColor]];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}];
}
/**
* Set stroke width
* @params key
*/
-(void)setStrokeWidth:(CDVInvokedUrlCommand *)command
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *circleId = [command.arguments objectAtIndex:0];
GMSCircle *circle = [self.mapCtrl.objects objectForKey:circleId];
float width = [[command.arguments objectAtIndex:1] floatValue];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[circle setStrokeWidth:width];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}];
}
/**
* Set radius
* @params key
*/
-(void)setRadius:(CDVInvokedUrlCommand *)command
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *circleId = [command.arguments objectAtIndex:0];
GMSCircle *circle = [self.mapCtrl.objects objectForKey:circleId];
float radius = [[command.arguments objectAtIndex:1] floatValue];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[circle setRadius:radius];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}];
}
/**
* Set z-index
* @params key
*/
-(void)setZIndex:(CDVInvokedUrlCommand *)command
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *circleId = [command.arguments objectAtIndex:0];
GMSCircle *circle = [self.mapCtrl.objects objectForKey:circleId];
NSInteger zIndex = [[command.arguments objectAtIndex:1] integerValue];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[circle setZIndex:(int)zIndex];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}];
}
/**
* Set visibility
* @params key
*/
-(void)setVisible:(CDVInvokedUrlCommand *)command
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *key = [command.arguments objectAtIndex:0];
GMSCircle *circle = (GMSCircle *)[self.mapCtrl.objects objectForKey:key];
Boolean isVisible = [[command.arguments objectAtIndex:1] boolValue];
// Update the property
NSString *propertyId = [key stringByReplacingOccurrencesOfString:@"circle_" withString:@"circle_property_"];
NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithDictionary:
[self.mapCtrl.objects objectForKey:propertyId]];
[properties setObject:[NSNumber numberWithBool:isVisible] forKey:@"isVisible"];
[self.mapCtrl.objects setObject:properties forKey:propertyId];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
if (isVisible) {
circle.map = ((GMSMapView *)(self.mapCtrl.view));
} else {
circle.map = nil;
}
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}];
}
/**
* Set clickable
* @params key
*/
-(void)setClickable:(CDVInvokedUrlCommand *)command
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *key = [command.arguments objectAtIndex:0];
//GMSCircle *circle = (GMSCircle *)[self.mapCtrl.objects objectForKey:key];
Boolean isClickable = [[command.arguments objectAtIndex:1] boolValue];
// Update the property
NSString *propertyId = [key stringByReplacingOccurrencesOfString:@"circle_" withString:@"circle_property_"];
NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithDictionary:
[self.mapCtrl.objects objectForKey:propertyId]];
[properties setObject:[NSNumber numberWithBool:isClickable] forKey:@"isClickable"];
[self.mapCtrl.objects setObject:properties forKey:propertyId];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}
/**
* Remove the circle
* @params key
*/
-(void)remove:(CDVInvokedUrlCommand *)command
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSString *circleId = [command.arguments objectAtIndex:0];
GMSCircle *circle = [self.mapCtrl.objects objectForKey:circleId];
NSString *propertyId = [circleId stringByReplacingOccurrencesOfString:@"circle_" withString:@"circle_property_"];
[self.mapCtrl.objects removeObjectForKey:propertyId];
circle.map = nil;
[self.mapCtrl.objects removeObjectForKey:circleId];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}
@end
@@ -0,0 +1,18 @@
//
// PluginEnvironment.h
// cordova-googlemaps-plugin v2
//
// Created by Masashi Katsumata.
//
//
#import <Cordova/CDV.h>
#import "CordovaGoogleMaps.h"
@interface PluginEnvironment : CDVPlugin
- (void)isAvailable:(CDVInvokedUrlCommand*)command;
- (void)setBackGroundColor:(CDVInvokedUrlCommand*)command;
- (void)getLicenseInfo:(CDVInvokedUrlCommand*)command;
- (void)setEnv:(CDVInvokedUrlCommand*)command;
@end
@@ -0,0 +1,121 @@
//
// PluginEnvironment.m
// cordova-googlemaps-plugin v2
//
// Created by Masashi Katsumata.
//
//
#import "PluginEnvironment.h"
@implementation PluginEnvironment
dispatch_queue_t queue;
- (void)pluginInitialize
{
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
}
- (void)pluginUnload
{
queue = nil;
}
- (void)isAvailable:(CDVInvokedUrlCommand *)command {
// Return true always in iOS.
dispatch_async(queue, ^{
//-------------------------------
// Check the Google Maps API key
//-------------------------------
NSString *errorMsg = nil;
NSString *APIKey = [((CDVViewController *)self.viewController).settings objectForKey:@"google_maps_ios_api_key"];
if (APIKey == nil) {
NSString *errorTitle = [PluginUtil PGM_LOCALIZATION:@"APIKEY_IS_UNDEFINED_TITLE"];
NSString *errorMsg = [PluginUtil PGM_LOCALIZATION:@"APIKEY_IS_UNDEFINED_MESSAGE"];
UIAlertController* alert = [UIAlertController alertControllerWithTitle:errorTitle
message:errorMsg
preferredStyle:UIAlertControllerStyleAlert];
NSString *closeBtnLabel = [PluginUtil PGM_LOCALIZATION:@"CLOSE_BUTTON"];
UIAlertAction* ok = [UIAlertAction actionWithTitle:closeBtnLabel
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action)
{
[alert dismissViewControllerAnimated:YES completion:nil];
}];
[alert addAction:ok];
[self.viewController presentViewController:alert
animated:YES
completion:nil];
return;
}
/*---------------------------------------------------------------------------------------
* If CFBundleExecutable is not English, the Google Maps SDK for iOS will crash.
* So must be English.
*
* If you want to use non-english name for your app, you need to change your config.xml like this.
*
* <?xml version='1.0' encoding='utf-8'?>
* <widget id="(package name)" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
* <name short="(non-english app name)">(english app name)</name>
*---------------------------------------------------------------------------------------*/
NSDictionary *info = [[NSBundle mainBundle] infoDictionary];
NSString *CFBundleExecutable = [info objectForKey:@"CFBundleExecutable"];
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^[a-zA-Z0-9$@$!%*?&#^\\-_.\\s+]+$" options:NSRegularExpressionCaseInsensitive error:nil];
if ([regex numberOfMatchesInString:CFBundleExecutable options:0 range:NSMakeRange(0, CFBundleExecutable.length)] == 0) {
errorMsg = [PluginUtil PGM_LOCALIZATION:@"APP_NAME_ERROR_MESSAGE"];
}
CDVPluginResult* pluginResult;
if (errorMsg) {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:errorMsg];
} else {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
}
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
});
}
- (void)setBackGroundColor:(CDVInvokedUrlCommand *)command {
dispatch_async(queue, ^{
// Load the GoogleMap.m
CDVViewController *cdvViewController = (CDVViewController*)self.viewController;
CordovaGoogleMaps *googlemaps = [cdvViewController getCommandInstance:@"CordovaGoogleMaps"];
NSArray *rgbColor = [command.arguments objectAtIndex:0];
dispatch_async(dispatch_get_main_queue(), ^{
googlemaps.pluginLayer.backgroundColor = [rgbColor parsePluginColor];
});
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
});
}
- (void)getLicenseInfo:(CDVInvokedUrlCommand *)command {
dispatch_async(queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
NSString *txt = [GMSServices openSourceLicenseInfo];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:txt];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
});
});
}
- (void)setEnv:(CDVInvokedUrlCommand *)command {
// stub
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
@end
@@ -0,0 +1,19 @@
//
// PluginGeocoder.h
// cordova-googlemaps-plugin v2
//
// Created by Katsumata Masashi.
//
//
#import "CordovaGoogleMaps.h"
#import "PluginUtil.h"
#import <CoreLocation/CoreLocation.h>
@interface PluginGeocoder : CDVPlugin
@property (nonatomic, strong) NSDictionary *codeForCountryDictionary;
@property (nonatomic, strong) NSOperationQueue *executeQueue;
- (void)geocode:(CDVInvokedUrlCommand*)command;
- (NSArray *)geocoder_callback:(NSArray *)placemarks error:(NSError *)error;
@end
@@ -0,0 +1,296 @@
//
// PluginGeocoder.m
// cordova-googlemaps-plugin v2
//
// Created by Katsumata Masashi.
//
//
#import "PluginGeocoder.h"
@implementation PluginGeocoder
- (void)pluginUnload
{
if (self.executeQueue != nil){
self.executeQueue.suspended = YES;
[self.executeQueue cancelAllOperations];
self.executeQueue.suspended = NO;
self.executeQueue = nil;
}
}
- (void)pluginInitialize
{
NSArray *countryCodes = [NSLocale ISOCountryCodes];
NSMutableArray *countries = [NSMutableArray arrayWithCapacity:[countryCodes count]];
NSString *currentLanguage = [[NSLocale preferredLanguages] objectAtIndex:0];
for (NSString *countryCode in countryCodes)
{
NSString *identifier = [NSLocale localeIdentifierFromComponents: [NSDictionary dictionaryWithObject: countryCode forKey: NSLocaleCountryCode]];
NSString *country = [[[NSLocale alloc] initWithLocaleIdentifier:currentLanguage] displayNameForKey: NSLocaleIdentifier value: identifier];
[countries addObject: country];
}
self.codeForCountryDictionary = [[NSDictionary alloc] initWithObjects:countryCodes forKeys:countries];
self.executeQueue = [NSOperationQueue new];
self.executeQueue.maxConcurrentOperationCount = 6;
}
-(void)geocode:(CDVInvokedUrlCommand *)command
{
[self.executeQueue addOperationWithBlock:^{
NSDictionary *json = [command.arguments objectAtIndex:0];
NSDictionary *position = [json objectForKey:@"position"];
NSString *address = [json objectForKey:@"address"];
int idx = 0;
if ([json objectForKey:@"idx"]) {
idx = (int)[[json objectForKey:@"idx"] integerValue];
}
if (address && position == nil) {
__block CLGeocoder *geocoder = [CLGeocoder new];
NSArray *points = [json objectForKey:@"bounds"];
if (points) {
//center
int i = 0;
NSDictionary *latLng;
GMSMutablePath *path = [GMSMutablePath path];
GMSCoordinateBounds *bounds;
for (i = 0; i < points.count; i++) {
latLng = [points objectAtIndex:i];
[path addCoordinate:CLLocationCoordinate2DMake([[latLng objectForKey:@"lat"] floatValue], [[latLng objectForKey:@"lng"] floatValue])];
}
bounds = [[GMSCoordinateBounds alloc] initWithPath:path];
CLLocationCoordinate2D southWest = bounds.southWest;
CLLocationCoordinate2D northEast = bounds.northEast;
float latitude = (southWest.latitude + northEast.latitude) / 2.0;
float longitude = (southWest.longitude + northEast.longitude) / 2.0;
CLLocationCoordinate2D center = CLLocationCoordinate2DMake(latitude, longitude);
//distance
CLLocation *locA = [[CLLocation alloc] initWithLatitude:center.latitude longitude:center.latitude];
CLLocation *locB = [[CLLocation alloc] initWithLatitude:southWest.latitude longitude:southWest.longitude];
CLLocationDistance distance = [locA distanceFromLocation:locB];
CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:center radius:distance/2 identifier:@"geocoder"];
[geocoder geocodeAddressString:address inRegion:region completionHandler:^(NSArray *placemarks, NSError *error) {
CDVPluginResult* pluginResult;
if (error) {
if (placemarks.count == 0) {
NSMutableDictionary *resultJson = [[NSMutableDictionary alloc] init];
[resultJson setObject:[NSNumber numberWithInt:idx] forKey:@"idx"];
[resultJson setObject:[[NSArray alloc] init] forKey:@"results"];
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:resultJson];
} else {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:error.description];
}
} else {
NSArray *results = [self geocoder_callback:placemarks error:error];
NSMutableDictionary *resultJson = [[NSMutableDictionary alloc] init];
[resultJson setObject:[NSNumber numberWithInt:idx] forKey:@"idx"];
[resultJson setObject:results forKey:@"results"];
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:resultJson];
}
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
} else {
//No region specified.
[geocoder geocodeAddressString:address completionHandler:^(NSArray *placemarks, NSError *error) {
CDVPluginResult* pluginResult;
if (error) {
if (placemarks.count == 0) {
NSMutableDictionary *resultJson = [[NSMutableDictionary alloc] init];
[resultJson setObject:[NSNumber numberWithInt:idx] forKey:@"idx"];
[resultJson setObject:[[NSArray alloc] init] forKey:@"results"];
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:resultJson];
} else {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:error.description];
}
} else {
NSArray *results = [self geocoder_callback:placemarks error:error];
NSMutableDictionary *resultJson = [[NSMutableDictionary alloc] init];
[resultJson setObject:[NSNumber numberWithInt:idx] forKey:@"idx"];
[resultJson setObject:results forKey:@"results"];
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:resultJson];
}
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}
return;
}
// Reverse geocoding
if (position && address == nil) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
GMSGeocoder *reverseGeocoder = [GMSGeocoder geocoder];
NSDictionary *latLng = [json objectForKey:@"position"];
CLLocationCoordinate2D position = CLLocationCoordinate2DMake([[latLng objectForKey:@"lat"] floatValue], [[latLng objectForKey:@"lng"] floatValue]);
[reverseGeocoder reverseGeocodeCoordinate:position completionHandler:^(GMSReverseGeocodeResponse *response, NSError *error) {
[self.executeQueue addOperationWithBlock:^{
CDVPluginResult* pluginResult;
if (error) {
if ([response.results count] == 0) {
NSMutableDictionary *resultJson = [[NSMutableDictionary alloc] init];
[resultJson setObject:[json objectForKey:@"idx"] forKey:@"idx"];
[resultJson setObject:[[NSArray alloc] init] forKey:@"results"];
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:resultJson];
} else {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:error.description];
}
} else {
NSMutableArray *results = [NSMutableArray array];
GMSAddress *address;
NSString *countryCode;
int i;
for (i = 0; i < [response.results count]; i++) {
address = [response.results objectAtIndex:i];
NSMutableDictionary *result = [NSMutableDictionary dictionary];
[result setObject:[NSNumber numberWithDouble:address.coordinate.latitude] forKey:@"lat"];
[result setObject:[NSNumber numberWithDouble:address.coordinate.longitude] forKey:@"lng"];
NSMutableDictionary *position = [NSMutableDictionary dictionary];
[position setObject:[NSNumber numberWithDouble:address.coordinate.latitude] forKey:@"lat"];
[position setObject:[NSNumber numberWithDouble:address.coordinate.longitude] forKey:@"lng"];
[result setObject:position forKey:@"position"];
[result setObject:[NSString stringWithFormat:@"%@", address.locality] forKey:@"locality"];
[result setObject:[NSString stringWithFormat:@"%@", address.administrativeArea] forKey:@"adminArea"];
[result setObject:[NSString stringWithFormat:@"%@", address.country] forKey:@"country"];
countryCode = [self.codeForCountryDictionary objectForKey:address.country];
[result setObject:[NSString stringWithFormat:@"%@", countryCode] forKey:@"countryCode"];
[result setObject:@"" forKey:@"locale"];
[result setObject:[NSString stringWithFormat:@"%@", address.postalCode] forKey:@"postalCode"];
[result setObject:@"" forKey:@"subAdminArea"];
[result setObject:[NSString stringWithFormat:@"%@", address.subLocality] forKey:@"subLocality"];
[result setObject:@"" forKey:@"subThoroughfare"];
[result setObject:[NSString stringWithFormat:@"%@", address.thoroughfare] forKey:@"thoroughfare"];
NSMutableDictionary *extra = [NSMutableDictionary dictionary];
[extra setObject:address.lines forKey:@"lines"];
[result setObject:extra forKey:@"extra"];
[results addObject:result];
}
NSMutableDictionary *resultJson = [[NSMutableDictionary alloc] init];
[resultJson setObject:[json objectForKey:@"idx"] forKey:@"idx"];
[resultJson setObject:results forKey:@"results"];
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:resultJson];
}
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}];
}];
}
}];
}
- (NSArray *)geocoder_callback:(NSArray *)placemarks error:(NSError *)error
{
NSMutableArray *results = [[NSMutableArray alloc] init];
if ([placemarks count] > 0) {
CLPlacemark *placemark;
CLLocation *location;
CLLocationCoordinate2D coordinate;
for (int i = 0; i < placemarks.count; i++) {
NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
NSMutableDictionary *position = [[NSMutableDictionary alloc] init];
placemark = [placemarks objectAtIndex:i];
location = placemark.location;
coordinate = location.coordinate;
[position setObject:[NSNumber numberWithDouble:coordinate.latitude] forKey:@"lat"];
[position setObject:[NSNumber numberWithDouble:coordinate.longitude] forKey:@"lng"];
[result setObject:position forKey:@"position"];
if (placemark.administrativeArea) {
[result setObject:placemark.administrativeArea forKey:@"adminArea"];
}
if (placemark.subAdministrativeArea) {
[result setObject:placemark.subAdministrativeArea forKey:@"subAdminArea"];
}
if (placemark.locality) {
[result setObject:placemark.locality forKey:@"locality"];
}
if (placemark.country) {
[result setObject:placemark.country forKey:@"country"];
}
if (placemark.postalCode) {
[result setObject:placemark.postalCode forKey:@"postalCode"];
}
if (placemark.subLocality) {
[result setObject:placemark.subLocality forKey:@"subLocality"];
}
if (placemark.subThoroughfare) {
[result setObject:placemark.subThoroughfare forKey:@"subThoroughfare"];
}
if (placemark.thoroughfare) {
[result setObject:placemark.thoroughfare forKey:@"thoroughfare"];
}
NSMutableDictionary *extra = [[NSMutableDictionary alloc] init];
if (placemark.ocean) {
[extra setObject:placemark.ocean forKey:@"ocean"];
}
if (placemark.addressDictionary) {
[extra setObject:placemark.addressDictionary forKey:@"address"];
}
if (placemark.description) {
[extra setObject:placemark.description forKey:@"description"];
}
if (placemark.inlandWater) {
[extra setObject:placemark.inlandWater forKey:@"inlandWater"];
}
if (placemark.region) {
// iOS8 SDK still uses CLRegion inside CLPlaceMarker though,
// to supress the warning, cast to CLCircularRegion.
// http://www.4byte.cn/question/288833/deprecated-clregion-methods-how-to-get-radius.html
#ifdef __IPHONE_7_0
CLCircularRegion *circularRegion = (CLCircularRegion *)placemark.region;
[extra setObject:[NSString stringWithFormat:@"%f", circularRegion.radius] forKey:@"radius"];
#else
[extra setObject:[NSString stringWithFormat:@"%f", placemark.region.radius] forKey:@"radius"];
#endif
[extra setObject:placemark.region.identifier forKey:@"identifier"];
}
if (placemark.name) {
[extra setObject:placemark.name forKey:@"name"];
}
[result setObject:extra forKey:@"extra"];
[results addObject:result];
}
}
return results;
}
@end
@@ -0,0 +1,30 @@
//
// PluginGroundOverlay.h
// cordova-googlemaps-plugin v2
//
// Created by Katsumata Masashi.
//
//
#import "CordovaGoogleMaps.h"
#import "IPluginProtocol.h"
#import "PluginUtil.h"
@interface PluginGroundOverlay : CDVPlugin<IPluginProtocol>
//@property (nonatomic, strong) NSCache* imgCache;
@property (nonatomic) BOOL initialized;
@property (nonatomic, strong) PluginMapViewController* mapCtrl;
- (void)create:(CDVInvokedUrlCommand*)command;
- (void)remove:(CDVInvokedUrlCommand *)command;
- (void)setClickable:(CDVInvokedUrlCommand*)command;
- (void)setVisible:(CDVInvokedUrlCommand*)command;
- (void)setImage:(CDVInvokedUrlCommand*)command;
- (void)setBounds:(CDVInvokedUrlCommand*)command;
- (void)setOpacity:(CDVInvokedUrlCommand*)command;
- (void)setBearing:(CDVInvokedUrlCommand*)command;
- (void)setZIndex:(CDVInvokedUrlCommand *)command;
- (void)_setImage:(GMSGroundOverlay *)layer urlStr:(NSString *)urlStr completionHandler:(void (^)(BOOL succeeded))completionHandler;
- (void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^)(BOOL succeeded, UIImage *image))completionBlock;
@end
@@ -0,0 +1,602 @@
//
// PluginGroundOverlay.m
// cordova-googlemaps-plugin v2
//
// Created by Katsumata Masashi.
//
//
#import "PluginGroundOverlay.h"
@implementation PluginGroundOverlay
- (void)pluginInitialize
{
if (self.initialized) {
return;
}
[super pluginInitialize];
// Initialize this plugin
//self.imgCache = [[NSCache alloc]init];
//self.imgCache.totalCostLimit = 3 * 1024 * 1024 * 1024; // 3MB = Cache for image
}
- (void)pluginUnload
{
// Plugin destroy
NSArray *keys = [self.mapCtrl.objects allKeys];
NSString *key;
for (int i = 0; i < [keys count]; i++) {
key = [keys objectAtIndex:i];
if ([key hasPrefix:@"groundoverlay_property"]) {
key = [key stringByReplacingOccurrencesOfString:@"_property" withString:@""];
GMSGroundOverlay *groundoverlay = (GMSGroundOverlay *)[self.mapCtrl.objects objectForKey:key];
groundoverlay.map = nil;
groundoverlay = nil;
}
[self.mapCtrl.objects removeObjectForKey:key];
}
//[self.imgCache removeAllObjects];
//self.imgCache = nil;
key = nil;
keys = nil;
NSString *pluginId = [NSString stringWithFormat:@"%@-groundoverlay", self.mapCtrl.overlayId];
CDVViewController *cdvViewController = (CDVViewController*)self.viewController;
[cdvViewController.pluginObjects removeObjectForKey:pluginId];
[cdvViewController.pluginsMap setValue:nil forKey:pluginId];
pluginId = nil;
}
-(void)setPluginViewController:(PluginViewController *)viewCtrl
{
self.mapCtrl = (PluginMapViewController *)viewCtrl;
}
-(void)create:(CDVInvokedUrlCommand *)command
{
PluginGroundOverlay *self_ = self;
NSDictionary *json = [command.arguments objectAtIndex:1];
NSString *idBase = [command.arguments objectAtIndex:2];
NSArray *points = [json objectForKey:@"bounds"];
GMSMutablePath *path = [GMSMutablePath path];
GMSCoordinateBounds *bounds;
if (points) {
//Generate a bounds
int i = 0;
NSDictionary *latLng;
for (i = 0; i < points.count; i++) {
latLng = [points objectAtIndex:i];
[path addCoordinate:CLLocationCoordinate2DMake([[latLng objectForKey:@"lat"] floatValue], [[latLng objectForKey:@"lng"] floatValue])];
}
}
bounds = [[GMSCoordinateBounds alloc] initWithPath:path];
dispatch_async(dispatch_get_main_queue(), ^{
GMSGroundOverlay *groundOverlay = [GMSGroundOverlay groundOverlayWithBounds:bounds icon:nil];
NSString *groundOverlayId = [NSString stringWithFormat:@"groundoverlay_%@", idBase];
[self.mapCtrl.objects setObject:groundOverlay forKey: groundOverlayId];
groundOverlay.title = groundOverlayId;
groundOverlay.anchor = CGPointMake(0.5f, 0.5f);
if ([json valueForKey:@"zIndex"] && [json valueForKey:@"zIndex"] != [NSNull null]) {
groundOverlay.zIndex = [[json valueForKey:@"zIndex"] floatValue];
}
if ([json valueForKey:@"bearing"] && [json valueForKey:@"bearing"] != [NSNull null]) {
groundOverlay.bearing = [[json valueForKey:@"bearing"] floatValue];
}
if ([json valueForKey:@"anchor"] && [json valueForKey:@"anchor"] != [NSNull null]) {
NSArray *anchor = [json valueForKey:@"anchor"];
groundOverlay.anchor = CGPointMake([[anchor objectAtIndex:0] floatValue], [[anchor objectAtIndex:1] floatValue]);
}
BOOL isVisible = YES;
// Visible property
NSString *visibleValue = [NSString stringWithFormat:@"%@", json[@"visible"]];
if ([@"0" isEqualToString:visibleValue]) {
// false
isVisible = NO;
groundOverlay.map = nil;
} else {
// true or default
groundOverlay.map = self.mapCtrl.map;
}
BOOL isClickable = NO;
if ([json valueForKey:@"clickable"] != [NSNull null] && [[json valueForKey:@"clickable"] boolValue]) {
isClickable = YES;
}
// Since this plugin uses own touch-detection,
// set NO to the tappable property.
groundOverlay.tappable = NO;
__block PluginGroundOverlay *me = self;
// Load image
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *urlStr = [json objectForKey:@"url"];
if (urlStr) {
[self _setImage:groundOverlay urlStr:urlStr completionHandler:^(BOOL successed) {
if (!successed) {
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR];
[self_.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
return;
}
//NSString *imgId = [NSString stringWithFormat:@"groundoverlay_image_%lu", (unsigned long)groundOverlay.hash];
//[me.imgCache setObject:groundOverlay.icon forKey:imgId];
if ([json valueForKey:@"opacity"] && [json valueForKey:@"opacity"] != [NSNull null]) {
CGFloat opacity = [[json valueForKey:@"opacity"] floatValue];
groundOverlay.icon = [groundOverlay.icon imageByApplyingAlpha:opacity];
}
//---------------------------
// Keep the properties
//---------------------------
NSString *propertyId = [NSString stringWithFormat:@"groundoverlay_property_%@", idBase];
// points
NSMutableDictionary *properties = [[NSMutableDictionary alloc] init];
// bounds (pre-calculate for click detection)
[properties setObject:groundOverlay.bounds forKey:@"bounds"];
// isVisible
[properties setObject:[NSNumber numberWithBool:isVisible] forKey:@"isVisible"];
// isClickable
[properties setObject:[NSNumber numberWithBool:isClickable] forKey:@"isClickable"];
// zIndex
[properties setObject:[NSNumber numberWithFloat:groundOverlay.zIndex] forKey:@"zIndex"];;
[me.mapCtrl.objects setObject:properties forKey:propertyId];
//---------------------------
// Result for JS
//---------------------------
NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
[result setObject:groundOverlayId forKey:@"__pgmId"];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:result];
[self_.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
} else {
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Error: The url property is not specified."];
[self_.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
}];
});
}
- (void)_setImage:(GMSGroundOverlay *)groundOverlay urlStr:(NSString *)urlStr completionHandler:(void (^)(BOOL succeeded))completionHandler {
NSRange range = [urlStr rangeOfString:@"http"];
if (range.location != 0) {
/**
* Load icon from file or Base64 encoded strings
*/
UIImage *image;
if ([urlStr rangeOfString:@"data:image/"].location != NSNotFound &&
[urlStr rangeOfString:@";base64,"].location != NSNotFound) {
/**
* Base64 icon
*/
NSArray *tmp = [urlStr componentsSeparatedByString:@","];
NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:[tmp objectAtIndex:1] options:0];
image = [[UIImage alloc] initWithData:decodedData];
} else {
/**
* Load the icon from local path
*/
range = [urlStr rangeOfString:@"cdvfile://"];
if (range.location != NSNotFound) {
urlStr = [PluginUtil getAbsolutePathFromCDVFilePath:self.webView cdvFilePath:urlStr];
if (urlStr == nil) {
NSLog(@"(error)Can not convert '%@' to device full path.", urlStr);
completionHandler(NO);
return;
}
}
range = [urlStr rangeOfString:@"://"];
if (range.location == NSNotFound) {
range = [urlStr rangeOfString:@"/"];
if (range.location != 0) {
// Get the current URL, then calculate the relative path.
CDVViewController *cdvViewController = (CDVViewController*)self.viewController;
id webview = cdvViewController.webView;
dispatch_sync(dispatch_get_main_queue(), ^{
NSURL *url = [webview URL];
NSString *currentURL = url.absoluteString;
if (![[url lastPathComponent] isEqualToString:@"/"]) {
currentURL = [currentURL stringByDeletingLastPathComponent];
}
// remove page unchor (i.e index.html#page=test, index.html?key=value)
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"[#\\?].*$" options:NSRegularExpressionCaseInsensitive error:nil];
currentURL = [regex stringByReplacingMatchesInString:currentURL options:0 range:NSMakeRange(0, [currentURL length]) withTemplate:@""];
// remove file name (i.e /index.html)
regex = [NSRegularExpression regularExpressionWithPattern:@"\\/[^\\/]+\\.[^\\/]+$" options:NSRegularExpressionCaseInsensitive error:nil];
currentURL = [regex stringByReplacingMatchesInString:currentURL options:0 range:NSMakeRange(0, [currentURL length]) withTemplate:@""];
NSString *urlStr2 = [NSString stringWithFormat:@"%@/%@", currentURL, urlStr];
urlStr2 = [urlStr2 stringByReplacingOccurrencesOfString:@":/" withString:@"://"];
urlStr2 = [urlStr2 stringByReplacingOccurrencesOfString:@":///" withString:@"://"];
url = [NSURL URLWithString:urlStr2];
[self downloadImageWithURL:url completionBlock:^(BOOL succeeded, UIImage *image) {
if (!succeeded) {
completionHandler(NO);
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
groundOverlay.icon = image;
completionHandler(YES);
});
}];
});
return;
} else {
urlStr = [NSString stringWithFormat:@"file://%@", urlStr];
}
}
range = [urlStr rangeOfString:@"file://"];
if (range.location != NSNotFound) {
urlStr = [urlStr stringByReplacingOccurrencesOfString:@"file://" withString:@""];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:urlStr]) {
NSLog(@"(error)There is no file at '%@'.", urlStr);
completionHandler(NO);
return;
}
}
image = [UIImage imageNamed:urlStr];
}
dispatch_async(dispatch_get_main_queue(), ^{
groundOverlay.icon = [UIImage imageNamed:urlStr];
completionHandler(YES);
});
} else {
NSURL *url = [NSURL URLWithString:urlStr];
[self downloadImageWithURL:url completionBlock:^(BOOL succeeded, UIImage *image) {
if (!succeeded) {
completionHandler(NO);
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
groundOverlay.icon = image;
completionHandler(YES);
});
}];
}
}
/**
* Remove the ground overlay
* @params key
*/
-(void)remove:(CDVInvokedUrlCommand *)command
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSString *groundOverlayId = [command.arguments objectAtIndex:0];
GMSGroundOverlay *groundOverlay = [self.mapCtrl.objects objectForKey:groundOverlayId];
//[self.imgCache removeObjectForKey:groundOverlayId];
NSString *propertyId = [groundOverlayId stringByReplacingOccurrencesOfString:@"groundoverlay_" withString:@"groundoverlay_property"];
[self.mapCtrl.objects removeObjectForKey:propertyId];
[self.mapCtrl.objects removeObjectForKey:groundOverlayId];
groundOverlay.map = nil;
groundOverlay = nil;
[self.mapCtrl.objects removeObjectForKey:groundOverlayId];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}
/**
* Set visibility
* @params key
*/
-(void)setVisible:(CDVInvokedUrlCommand *)command
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *key = [command.arguments objectAtIndex:0];
GMSGroundOverlay *groundOverlay = [self.mapCtrl.objects objectForKey:key];
Boolean isVisible = [[command.arguments objectAtIndex:1] boolValue];
// Update the property
NSString *propertyId = [key stringByReplacingOccurrencesOfString:@"groundoverlay_" withString:@"groundoverlay_property_"];
NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithDictionary:
[self.mapCtrl.objects objectForKey:propertyId]];
[properties setObject:[NSNumber numberWithBool:isVisible] forKey:@"isVisible"];
[self.mapCtrl.objects setObject:properties forKey:propertyId];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
if (isVisible) {
groundOverlay.map = self.mapCtrl.map;
} else {
groundOverlay.map = nil;
}
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}];
}
/**
* set image
* @params key
*/
-(void)setImage:(CDVInvokedUrlCommand *)command
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *groundOverlayId = [command.arguments objectAtIndex:0];
GMSGroundOverlay *groundOverlay = [self.mapCtrl.objects objectForKey:groundOverlayId];
NSString *urlStr = [command.arguments objectAtIndex:1];
if (urlStr) {
__block PluginGroundOverlay *self_ = self;
[self _setImage:groundOverlay urlStr:urlStr completionHandler:^(BOOL successed) {
CDVPluginResult* pluginResult;
if (successed) {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
} else {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR];
}
[self_.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
} else {
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
}];
}
/**
* Set bounds
* @params key
*/
-(void)setBounds:(CDVInvokedUrlCommand *)command
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *groundOverlayId = [command.arguments objectAtIndex:0];
GMSGroundOverlay *groundOverlay = [self.mapCtrl.objects objectForKey:groundOverlayId];
GMSMutablePath *path = [GMSMutablePath path];
NSArray *points = [command.arguments objectAtIndex:1];
int i = 0;
NSDictionary *latLng;
for (i = 0; i < points.count; i++) {
latLng = [points objectAtIndex:i];
[path addCoordinate:CLLocationCoordinate2DMake([[latLng objectForKey:@"lat"] floatValue], [[latLng objectForKey:@"lng"] floatValue])];
}
GMSCoordinateBounds *bounds;
bounds = [[GMSCoordinateBounds alloc] initWithPath:path];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[groundOverlay setBounds:bounds];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}];
}
/**
* Set opacity
* @params key
*/
-(void)setOpacity:(CDVInvokedUrlCommand *)command
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *groundOverlayId = [command.arguments objectAtIndex:0];
GMSGroundOverlay *groundOverlay = [self.mapCtrl.objects objectForKey:groundOverlayId];
CGFloat opacity = [[command.arguments objectAtIndex:1] floatValue];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
groundOverlay.opacity = opacity;
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}];
}
/**
* Set bearing
* @params key
*/
-(void)setBearing:(CDVInvokedUrlCommand *)command
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *groundOverlayId = [command.arguments objectAtIndex:0];
GMSGroundOverlay *groundOverlay = [self.mapCtrl.objects objectForKey:groundOverlayId];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
groundOverlay.bearing = [[command.arguments objectAtIndex:1] floatValue];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}];
}
/**
* Set clickable
* @params key
*/
-(void)setClickable:(CDVInvokedUrlCommand *)command
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *key = [command.arguments objectAtIndex:0];
//GMSGroundOverlay *groundOverlay = (GMSGroundOverlay *)[self.mapCtrl.objects objectForKey:key];
Boolean isClickable = [[command.arguments objectAtIndex:1] boolValue];
// Update the property
NSString *propertyId = [key stringByReplacingOccurrencesOfString:@"groundoverlay_" withString:@"groundoverlay_property_"];
NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithDictionary:
[self.mapCtrl.objects objectForKey:propertyId]];
[properties setObject:[NSNumber numberWithBool:isClickable] forKey:@"isClickable"];
[self.mapCtrl.objects setObject:properties forKey:propertyId];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}
/**
* Set z-index
* @params key
*/
-(void)setZIndex:(CDVInvokedUrlCommand *)command
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *groundOverlayId = [command.arguments objectAtIndex:0];
GMSGroundOverlay *groundOverlay = [self.mapCtrl.objects objectForKey:groundOverlayId];
NSInteger zIndex = [[command.arguments objectAtIndex:1] integerValue];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[groundOverlay setZIndex:(int)zIndex];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}];
}
- (void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^)(BOOL succeeded, UIImage *image))completionBlock
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *iconPath = url.absoluteString;
// Since ionic local server declines HTTP access for some reason,
// replace URL with file path
NSBundle *mainBundle = [NSBundle mainBundle];
NSString *wwwPath = [mainBundle pathForResource:@"www/cordova" ofType:@"js"];
wwwPath = [wwwPath stringByReplacingOccurrencesOfString:@"/cordova.js" withString:@""];
if ([iconPath containsString:@"assets/"]) {
iconPath = [iconPath regReplace:@"^.*assets/" replaceTxt:[NSString stringWithFormat:@"%@/assets/", wwwPath] options:NSRegularExpressionCaseInsensitive];
}
// iconPath = [iconPath stringByReplacingOccurrencesOfString:wwwPath withString: @""];
// ionic 4
iconPath = [iconPath stringByReplacingOccurrencesOfString:@"http://localhost:8080" withString: wwwPath];
// ionic 5
iconPath = [iconPath stringByReplacingOccurrencesOfString:@"ionic://localhost" withString: wwwPath];
if ([iconPath hasPrefix:@"file://"] || [iconPath hasPrefix:@"/"]) {
iconPath = [iconPath stringByReplacingOccurrencesOfString:@"file://" withString:@""];
if (![iconPath hasPrefix:@"/"]) {
iconPath = [NSString stringWithFormat:@"/%@", iconPath];
}
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:iconPath]) {
//if (self.mapCtrl.debuggable) {
NSLog(@"(error)There is no file at '%@'.", iconPath);
//}
//[self.commandDelegate sendPluginResult:pluginResult callbackId:callbackId];
completionBlock(NO, nil);
return;
}
UIImage *image = [UIImage imageNamed:iconPath];
if (image) {
completionBlock(YES, image);
return;
}
}
NSURLRequest *req = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReturnCacheDataElseLoad
timeoutInterval:5];
NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:req];
if (cachedResponse != nil) {
UIImage *image = [[UIImage alloc] initWithData:cachedResponse.data];
if (image) {
completionBlock(YES, image);
return;
}
}
//-------------------------------------------------------------
// Use NSURLSessionDataTask instead of [NSURLConnection sendAsynchronousRequest]
// https://stackoverflow.com/a/20871647
//-------------------------------------------------------------
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];
NSURLSessionDataTask *getTask = [session dataTaskWithRequest:req
completionHandler:^(NSData *data, NSURLResponse *res, NSError *error) {
[session finishTasksAndInvalidate];
UIImage *image = [UIImage imageWithData:data];
if (image) {
completionBlock(YES, image);
return;
}
completionBlock(NO, nil);
}];
[getTask resume];
}];
}
@end
@@ -0,0 +1,27 @@
//
// PluginKmlOverlay.h
// cordova-googlemaps-plugin v2
//
// Created by Katsumata Masashi.
//
//
#import "CordovaGoogleMaps.h"
#import "IPluginProtocol.h"
#import "PluginUtil.h"
#import "TBXML.h"
#import "IPluginProtocol.h"
@interface PluginKmlOverlay : CDVPlugin<IPluginProtocol>
@property (nonatomic) BOOL initialized;
@property (nonatomic, strong) PluginMapViewController* mapCtrl;
- (void)create:(CDVInvokedUrlCommand*)command;
@end
@interface KmlParseClass : NSObject
@property (nonatomic) NSMutableDictionary *styleHolder;
@property (nonatomic) NSMutableDictionary *schemaHolder;
-(NSMutableDictionary *)parseXml:(TBXML *)tbxml rootElement:(TBXMLElement *)rootElement;
@end
@@ -0,0 +1,358 @@
//
// PluginKmlOverlay.m
// cordova-googlemaps-plugin v2
//
// Created by Katsumata Masashi.
//
//
#import "PluginKmlOverlay.h"
@implementation PluginKmlOverlay
-(void)setPluginViewController:(PluginViewController *)viewCtrl
{
self.mapCtrl = (PluginMapViewController *)viewCtrl;
}
- (void)pluginUnload
{
}
-(void)create:(CDVInvokedUrlCommand *)command
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSDictionary *json = [command.arguments objectAtIndex:1];
NSError *error;
NSString *urlStr = [json objectForKey:@"url"];
NSRange range;
if (![urlStr hasPrefix:@"http"]) {
if (![urlStr containsString:@"://"] &&
![urlStr hasPrefix:@"/"] &&
![urlStr hasPrefix:@"www"] &&
![urlStr hasPrefix:@"./"] &&
![urlStr hasPrefix:@"../"]) {
urlStr = [NSString stringWithFormat:@"./%@", urlStr];
}
if ([urlStr hasPrefix:@"./"] || [urlStr hasPrefix:@"../"]) {
NSError *error = nil;
// replace repeated "./" (i.e ./././test.png)
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"(\\.\\/)+" options:NSRegularExpressionCaseInsensitive error:&error];
urlStr = [regex stringByReplacingMatchesInString:urlStr options:0 range:NSMakeRange(0, [urlStr length]) withTemplate:@"./"];
// Get the current URL, then calculate the relative path.
CDVViewController *cdvViewController = (CDVViewController*)self.viewController;
id webview = cdvViewController.webView;
NSString *currentURL;
NSURL *url = [webview URL];
currentURL = url.absoluteString;
if (![[url lastPathComponent] isEqualToString:@"/"]) {
currentURL = [currentURL stringByReplacingOccurrencesOfString:[url lastPathComponent] withString:@""];
}
// remove page unchor (i.e index.html#page=test, index.html?key=value)
regex = [NSRegularExpression regularExpressionWithPattern:@"[#\\?].*$" options:NSRegularExpressionCaseInsensitive error:&error];
currentURL = [regex stringByReplacingMatchesInString:currentURL options:0 range:NSMakeRange(0, [currentURL length]) withTemplate:@""];
// remove file name (i.e /index.html)
regex = [NSRegularExpression regularExpressionWithPattern:@"\\/[^\\/]+\\.[^\\/]+$" options:NSRegularExpressionCaseInsensitive error:&error];
currentURL = [regex stringByReplacingMatchesInString:currentURL options:0 range:NSMakeRange(0, [currentURL length]) withTemplate:@""];
if (![currentURL hasSuffix:@"/"]) {
currentURL = [NSString stringWithFormat:@"%@/", currentURL];
}
urlStr = [NSString stringWithFormat:@"%@%@", currentURL, urlStr];
// remove file name (i.e /index.html)
regex = [NSRegularExpression regularExpressionWithPattern:@"(\\/\\.\\/+)+" options:NSRegularExpressionCaseInsensitive error:&error];
urlStr = [regex stringByReplacingMatchesInString:urlStr options:0 range:NSMakeRange(0, [urlStr length]) withTemplate:@"/"];
urlStr = [urlStr stringByReplacingOccurrencesOfString:@"%20" withString:@" "];
if (self.mapCtrl.debuggable) {
NSLog(@"urlStr = %@", urlStr);
}
}
range = [urlStr rangeOfString:@"cdvfile://"];
if (range.location != NSNotFound) {
urlStr = [PluginUtil getAbsolutePathFromCDVFilePath:self.webView cdvFilePath:urlStr];
if (urlStr == nil) {
NSMutableDictionary* details = [NSMutableDictionary dictionary];
[details setValue:[NSString stringWithFormat:@"Can not convert '%@' to device full path.", urlStr] forKey:NSLocalizedDescriptionKey];
error = [NSError errorWithDomain:@"world" code:200 userInfo:details];
}
}
range = [urlStr rangeOfString:@"file://"];
if (range.location != NSNotFound) {
urlStr = [urlStr stringByReplacingOccurrencesOfString:@"file://" withString:@""];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:urlStr]) {
NSMutableDictionary* details = [NSMutableDictionary dictionary];
[details setValue:[NSString stringWithFormat:@"There is no file at '%@'.", urlStr] forKey:NSLocalizedDescriptionKey];
error = [NSError errorWithDomain:@"world" code:200 userInfo:details];
}
}
}
if (self.mapCtrl.debuggable) {
NSLog(@"urlStr = %@", urlStr);
}
[self.mapCtrl.executeQueue addOperationWithBlock:^{
[self loadKml:urlStr completionBlock:^(BOOL succeeded, id result) {
CDVPluginResult* pluginResult;
if (succeeded) {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:(NSDictionary *)result];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
} else {
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[error localizedDescription]];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
}];
}];
}];
}
- (void)loadKml:(NSString *)urlStr completionBlock:(void (^)(BOOL succeeded, id result))completionBlock {
urlStr = [urlStr stringByReplacingOccurrencesOfString:@" " withString:@"%20"];
NSError *error;
TBXML *tbxml = [TBXML alloc];// initWithXMLFile:urlStr error:&error];
// Since ionic local server declines HTTP access for some reason,
// replace URL with file path
NSBundle *mainBundle = [NSBundle mainBundle];
NSString *wwwPath = [mainBundle pathForResource:@"www/cordova" ofType:@"js"];
wwwPath = [wwwPath stringByReplacingOccurrencesOfString:@"/cordova.js" withString:@""];
// urlStr = [urlStr stringByReplacingOccurrencesOfString:wwwPath withString: @""];
// ionic 4
urlStr = [urlStr stringByReplacingOccurrencesOfString:@"http://localhost:8080" withString: wwwPath];
// ionic 5
urlStr = [urlStr stringByReplacingOccurrencesOfString:@"ionic://localhost" withString: wwwPath];
if ([urlStr hasPrefix:@"http://"] || [urlStr hasPrefix:@"https://"]) {
NSURLRequest *req = [NSURLRequest requestWithURL:[NSURL URLWithString:urlStr]];
bool valid = [NSURLConnection canHandleRequest:req];
if (valid) {
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];
NSURLSessionDataTask *getTask = [session dataTaskWithRequest:req
completionHandler:^(NSData *data, NSURLResponse *res, NSError *error) {
[session finishTasksAndInvalidate];
if (error) {
NSMutableDictionary* details = [NSMutableDictionary dictionary];
[details setValue:[NSString stringWithFormat:@"Cannot load KML data from %@", urlStr] forKey:NSLocalizedDescriptionKey];
error = [NSError errorWithDomain:@"world" code:200 userInfo:details];
completionBlock(NO, error);
return;
}
TBXML *tbxml = [TBXML alloc];
tbxml = [tbxml initWithXMLData:data error:&error];
NSDictionary *result = [self parseXmlWithTbXml:tbxml];
completionBlock(YES, result);
}];
[getTask resume];
} else {
NSMutableDictionary* details = [NSMutableDictionary dictionary];
[details setValue:[NSString stringWithFormat:@"Cannot load KML data from %@", urlStr] forKey:NSLocalizedDescriptionKey];
error = [NSError errorWithDomain:@"world" code:200 userInfo:details];
completionBlock(NO, error);
}
} else {
tbxml = [tbxml initWithXMLFile:urlStr error:&error];
NSDictionary *result = [self parseXmlWithTbXml:tbxml];
completionBlock(YES, result);
}
}
- (NSDictionary *)parseXmlWithTbXml:(TBXML *)tbxml {
KmlParseClass *parser = [[KmlParseClass alloc] init];
NSMutableDictionary *root = [parser parseXml:tbxml rootElement:tbxml.rootXMLElement];
NSMutableDictionary *result = [NSMutableDictionary dictionary];
[result setObject:root forKey:@"root"];
[result setObject:parser.schemaHolder forKey:@"schemas"];
[result setObject:parser.styleHolder forKey:@"styles"];
return result;
}
@end
//------------------------------------------
// @private
// KmlParseClass
//------------------------------------------
@implementation KmlParseClass
-(instancetype)init {
self = [super init];
self.styleHolder = [NSMutableDictionary dictionary];
self.schemaHolder = [NSMutableDictionary dictionary];
return self;
}
-(NSMutableDictionary *)parseXml:(TBXML *)tbxml rootElement:(TBXMLElement *)rootElement
{
if (rootElement == nil) {
return nil;
}
NSMutableDictionary *result = [NSMutableDictionary dictionary];
NSString *tagName = [[TBXML elementName:rootElement] lowercaseString];
NSString *styleId, *schemaId, *txt;
//NSLog(@"---> tagName = %@", tagName);
[result setObject:tagName forKey:@"tagName"];
NSString *attrName, *value;
TBXMLAttribute *attribute = rootElement->firstAttribute;
while (attribute) {
attrName = [TBXML attributeName:attribute];
value = [TBXML attributeValue:attribute];
[result setObject:value forKey:attrName];
attribute = attribute->next;
}
if ([@"styleurl" isEqualToString:tagName]) {
styleId = [TBXML textForElement:rootElement];
[result setObject:styleId forKey:@"styleId"];
return result;
}
if ([@"style" isEqualToString:tagName] || [@"stylemap" isEqualToString:tagName]) {
// Generate a style id for the tag
styleId = [TBXML valueOfAttributeNamed:@"id" forElement:rootElement];
if (styleId == nil || [styleId isEqualToString:@""]) {
styleId = [NSString stringWithFormat:@"__%ld%d__", tagName.hash, rand() * RAND_MAX];
}
[result setObject:styleId forKey:@"styleId"];
// Store style information into the styleHolder.
NSMutableDictionary *styles = [NSMutableDictionary dictionary];
NSMutableArray *children = [NSMutableArray array];
TBXMLElement *childNode = rootElement->firstChild;
while (childNode) {
NSMutableDictionary *node = [self parseXml:tbxml rootElement:childNode];
if (node) {
if ([node objectForKey:@"value"]) {
[result setObject:[node objectForKey:@"value"] forKey:[node objectForKey:@"tagName"]];
} else {
[children addObject:node];
}
}
childNode = childNode->nextSibling;
}
if ([children count] > 0) {
[styles setObject:children forKey:@"children"];
}
[self.styleHolder setObject:styles forKey:styleId];
return result;
}
if ([@"schema" isEqualToString:tagName]) {
// Generate a schema id for the tag
schemaId = [TBXML valueOfAttributeNamed:@"id" forElement:rootElement];
if (schemaId == nil || [schemaId isEqualToString:@""]) {
schemaId = [NSString stringWithFormat:@"__%ld%d__", tagName.hash, rand() * RAND_MAX];
}
// Store schema information into the schemaHolder.
NSMutableDictionary *schema = [NSMutableDictionary dictionary];
[schema setObject:[TBXML valueOfAttributeNamed:@"name" forElement:rootElement] forKey:@"name"];
NSMutableArray *children = [NSMutableArray array];
TBXMLElement *childNode = rootElement->firstChild;
while (childNode) {
NSMutableDictionary *node = [self parseXml:tbxml rootElement:childNode];
if (node) {
[children addObject:node];
}
childNode = childNode->nextSibling;
}
if ([children count] > 0) {
[schema setObject:children forKey:@"children"];
}
[self.schemaHolder setObject:schema forKey:schemaId];
return result;
}
if ([@"coordinates" isEqualToString:tagName]) {
NSMutableArray *latLngList = [NSMutableArray array];
txt = [TBXML textForElement:rootElement];
txt = [txt regReplace:@"\\s+" replaceTxt:@"\n" options:0];
txt = [txt regReplace:@"\\n+" replaceTxt:@"\n" options:0];
NSArray<NSString *> *lines = [txt componentsSeparatedByString:@"\n"];
NSArray<NSString *> *tmpArry;
NSMutableDictionary *latLng;
NSString *tmp;
for (int i = 0; i < lines.count; i++) {
tmp = [lines objectAtIndex:i];
tmp = [tmp regReplace:@"[^0-9,.eE\\-]" replaceTxt:@"" options:0];
if ([tmp isEqualToString:@""] == NO) {
tmpArry = [tmp componentsSeparatedByString:@","];
latLng = [NSMutableDictionary dictionary];
[latLng setObject:[tmpArry objectAtIndex:1] forKey:@"lat"];
[latLng setObject:[tmpArry objectAtIndex:0] forKey:@"lng"];
[latLngList addObject:latLng];
}
}
[result setObject:latLngList forKey:tagName];
return result;
}
TBXMLElement *childNode = rootElement->firstChild;
NSMutableArray<NSString *> *styleIDs;
if (childNode) {
NSMutableArray *children = [NSMutableArray array];
while(childNode) {
NSMutableDictionary *node = [self parseXml:tbxml rootElement:childNode];
if (node) {
if ([node objectForKey:@"styleId"]) {
styleIDs = [result objectForKey:@"styleIDs"];
if (styleIDs == nil) {
styleIDs = [NSMutableArray array];
}
[styleIDs addObject:[node objectForKey:@"styleId"]];
[result setObject:styleIDs forKey:@"styleIDs"];
} else if ([@"schema" isEqualToString:[node objectForKey:@"tagName"]] == NO) {
[children addObject:node];
}
}
childNode = childNode->nextSibling;
}
[result setObject:children forKey:@"children"];
} else {
[result setObject:[TBXML textForElement:rootElement] forKey:@"value"];
}
return result;
}
@end
@@ -0,0 +1,21 @@
//
// PluginLocationService.h
// cordova-googlemaps-plugin v2
//
// Created by Masashi Katsumata.
//
//
#import <Cordova/CDV.h>
#import <GoogleMaps/GoogleMaps.h>
#import "PluginUtil.h"
@interface PluginLocationService : CDVPlugin<CLLocationManagerDelegate>
@property (nonatomic, strong) NSMutableDictionary *lastResult;
@property (nonatomic, strong) CLLocation *lastLocation;
@property (nonatomic, strong) CLLocationManager *locationManager;
@property (nonatomic, strong) NSMutableArray *locationCommandQueue;
- (void)getMyLocation:(CDVInvokedUrlCommand*)command;
- (void)hasPermission:(CDVInvokedUrlCommand*)command;
@end
@@ -0,0 +1,233 @@
//
// PluginLocationService.m
// cordova-googlemaps-plugin v2
//
// Created by Masashi Katsumata.
//
//
#import "PluginLocationService.h"
@implementation PluginLocationService
- (void)pluginInitialize
{
self.locationCommandQueue = [[NSMutableArray alloc] init];
self.lastLocation = nil;
}
/**
* Return 1 if the app has geolocation permission
*/
- (void)hasPermission:(CDVInvokedUrlCommand*)command {
int result = 1;
CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
if (status == kCLAuthorizationStatusDenied ||
status == kCLAuthorizationStatusRestricted) {
result = 0;
}
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:result];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
/**
* Return the current position based on GPS
*/
-(void)getMyLocation:(CDVInvokedUrlCommand *)command
{
dispatch_async(dispatch_get_main_queue(), ^{
// Obtain the authorizationStatus
CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
if (status == kCLAuthorizationStatusDenied ||
status == kCLAuthorizationStatusRestricted) {
//----------------------------------------------------
// kCLAuthorizationStatusDenied
// kCLAuthorizationStatusRestricted
//----------------------------------------------------
NSString *error_code = @"service_denied";
NSString *error_message = [PluginUtil PGM_LOCALIZATION:@"LOCATION_IS_DENIED_MESSAGE"];
NSMutableDictionary *json = [NSMutableDictionary dictionary];
[json setObject:[NSNumber numberWithBool:NO] forKey:@"status"];
[json setObject:[NSString stringWithString:error_message] forKey:@"error_message"];
[json setObject:[NSString stringWithString:error_code] forKey:@"error_code"];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:json];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
//
// NSString *LOCATION_IS_UNAVAILABLE_ERROR_TITLE = [PluginUtil PGM_LOCALIZATION:@"LOCATION_IS_UNAVAILABLE_ERROR_TITLE"];
// NSString *LOCATION_IS_UNAVAILABLE_ERROR_MESSAGE = [PluginUtil PGM_LOCALIZATION:@"LOCATION_IS_UNAVAILABLE_ERROR_MESSAGE"];
// UIAlertController* alert = [UIAlertController alertControllerWithTitle:LOCATION_IS_UNAVAILABLE_ERROR_TITLE
// message:LOCATION_IS_UNAVAILABLE_ERROR_MESSAGE
// preferredStyle:UIAlertControllerStyleAlert];
//
// NSString *closeBtnLabel = [PluginUtil PGM_LOCALIZATION:@"CLOSE_BUTTON"];
// UIAlertAction* ok = [UIAlertAction actionWithTitle:closeBtnLabel
// style:UIAlertActionStyleDefault
// handler:^(UIAlertAction* action)
// {
// NSString *error_code = @"service_denied";
// NSString *error_message = [PluginUtil PGM_LOCALIZATION:@"LOCATION_IS_DENIED_MESSAGE"];
//
// NSMutableDictionary *json = [NSMutableDictionary dictionary];
// [json setObject:[NSNumber numberWithBool:NO] forKey:@"status"];
// [json setObject:[NSString stringWithString:error_message] forKey:@"error_message"];
// [json setObject:[NSString stringWithString:error_code] forKey:@"error_code"];
//
// CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:json];
// [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
// [alert dismissViewControllerAnimated:YES completion:nil];
// }];
//
// [alert addAction:ok];
//
//
// [self.viewController presentViewController:alert
// animated:YES
// completion:nil];
} else {
if (self.locationManager == nil) {
self.locationManager = [[CLLocationManager alloc] init];
}
self.locationManager.delegate = self;
//----------------------------------------------------
// kCLAuthorizationStatusNotDetermined
// kCLAuthorizationStatusAuthorized
// kCLAuthorizationStatusAuthorizedAlways
// kCLAuthorizationStatusAuthorizedWhenInUse
//----------------------------------------------------
CLLocationAccuracy locationAccuracy = kCLLocationAccuracyNearestTenMeters;
NSDictionary *opts = [command.arguments objectAtIndex:0];
BOOL isEnabledHighAccuracy = NO;
if ([opts objectForKey:@"enableHighAccuracy"]) {
isEnabledHighAccuracy = [[opts objectForKey:@"enableHighAccuracy"] boolValue];
}
if (isEnabledHighAccuracy == YES) {
locationAccuracy = kCLLocationAccuracyBestForNavigation;
self.locationManager.distanceFilter = 5;
} else {
self.locationManager.distanceFilter = 10;
}
self.locationManager.desiredAccuracy = locationAccuracy;
//http://stackoverflow.com/questions/24268070/ignore-ios8-code-in-xcode-5-compilation
[self.locationManager requestWhenInUseAuthorization];
if (self.lastLocation && -[self.lastLocation.timestamp timeIntervalSinceNow] < 2) {
//---------------------------------------------------------------------
// If the user requests the location in two seconds from the last time,
// return the last result in order to save battery usage.
// (Don't request the device location too much! Save battery usage!)
//---------------------------------------------------------------------
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:self.lastResult];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
return;
}
if (self.locationCommandQueue.count == 0) {
// Executes getMyLocation() first time
[self.locationManager stopUpdatingLocation];
// Why do I have to still support iOS9?
[NSTimer scheduledTimerWithTimeInterval:6000
target:self
selector:@selector(locationFailed)
userInfo:nil
repeats:NO];
[self.locationManager startUpdatingLocation];
}
[self.locationCommandQueue addObject:command];
//CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
//[pluginResult setKeepCallbackAsBool:YES];
//[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
});
}
-(void)locationFailed
{
if (self.lastLocation != nil) {
return;
}
// Timeout
[self.locationManager stopUpdatingLocation];
NSMutableDictionary *json = [NSMutableDictionary dictionary];
[json setObject:[NSNumber numberWithBool:NO] forKey:@"status"];
NSString *error_code = @"error";
NSString *error_message = [PluginUtil PGM_LOCALIZATION:@"CAN_NOT_GET_LOCATION_MESSAGE"];
[json setObject:[NSString stringWithString:error_message] forKey:@"error_message"];
[json setObject:[NSString stringWithString:error_code] forKey:@"error_code"];
for (CDVInvokedUrlCommand *command in self.locationCommandQueue) {
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:json];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
[self.locationCommandQueue removeAllObjects];
}
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
self.lastLocation = self.locationManager.location;
NSMutableDictionary *latLng = [NSMutableDictionary dictionary];
[latLng setObject:[NSNumber numberWithDouble:self.locationManager.location.coordinate.latitude] forKey:@"lat"];
[latLng setObject:[NSNumber numberWithDouble:self.locationManager.location.coordinate.longitude] forKey:@"lng"];
NSMutableDictionary *json = [NSMutableDictionary dictionary];
[json setObject:[NSNumber numberWithBool:YES] forKey:@"status"];
[json setObject:latLng forKey:@"latLng"];
[json setObject:[NSNumber numberWithFloat:[self.locationManager.location speed]] forKey:@"speed"];
[json setObject:[NSNumber numberWithFloat:[self.locationManager.location altitude]] forKey:@"altitude"];
//todo: calcurate the correct accuracy based on horizontalAccuracy and verticalAccuracy
[json setObject:[NSNumber numberWithFloat:[self.locationManager.location horizontalAccuracy]] forKey:@"accuracy"];
[json setObject:[NSNumber numberWithDouble:[self.locationManager.location.timestamp timeIntervalSince1970]] forKey:@"time"];
[json setObject:[NSNumber numberWithInteger:[self.locationManager.location hash]] forKey:@"hashCode"];
self.lastResult = json;
for (CDVInvokedUrlCommand *command in self.locationCommandQueue) {
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:json];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
[self.locationCommandQueue removeAllObjects];
[self.locationManager stopUpdatingLocation];
//self.locationManager.delegate = nil;
//self.locationManager = nil;
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
self.lastLocation = nil;
self.lastResult = nil;
NSMutableDictionary *json = [NSMutableDictionary dictionary];
[json setObject:[NSNumber numberWithBool:NO] forKey:@"status"];
NSString *error_code = @"error";
NSString *error_message = [PluginUtil PGM_LOCALIZATION:@"CAN_NOT_GET_LOCATION_MESSAGE"];
if (error.code == kCLErrorDenied) {
error_code = @"service_denied";
error_message = [PluginUtil PGM_LOCALIZATION:@"LOCATION_REJECTED_BY_USER_MESSAGE"];
}
[json setObject:[NSString stringWithString:error_message] forKey:@"error_message"];
[json setObject:[NSString stringWithString:error_code] forKey:@"error_code"];
for (CDVInvokedUrlCommand *command in self.locationCommandQueue) {
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:json];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
[self.locationCommandQueue removeAllObjects];
}
@end
+46
View File
@@ -0,0 +1,46 @@
//
// PluginMap.h
// cordova-googlemaps-plugin v2
//
// Created by Masashi Katsumata.
//
//
#import "CordovaGoogleMaps.h"
#import "IPluginProtocol.h"
#import "IPluginView.h"
@interface PluginMap : CDVPlugin<IPluginProtocol, IPluginView>
@property (nonatomic, strong) PluginMapViewController* mapCtrl;
@property (nonatomic) BOOL isRemoved;
@property (nonatomic) BOOL initialized;
- (void)clear:(CDVInvokedUrlCommand*)command;
- (void)setClickable:(CDVInvokedUrlCommand*)command;
- (void)setVisible:(CDVInvokedUrlCommand*)command;
- (void)setCameraTilt:(CDVInvokedUrlCommand*)command;
- (void)setCameraTarget:(CDVInvokedUrlCommand*)command;
- (void)setCameraBearing:(CDVInvokedUrlCommand *)command;
- (void)setCameraZoom:(CDVInvokedUrlCommand*)command;
- (void)setDiv:(CDVInvokedUrlCommand *)command;
- (void)setMapTypeId:(CDVInvokedUrlCommand*)command;
- (void)animateCamera:(CDVInvokedUrlCommand*)command;
- (void)loadPlugin:(CDVInvokedUrlCommand*)command;
- (void)getMap:(CDVInvokedUrlCommand*)command;
- (void)moveCamera:(CDVInvokedUrlCommand*)command;
- (void)setMyLocationEnabled:(CDVInvokedUrlCommand*)command;
- (void)setIndoorEnabled:(CDVInvokedUrlCommand*)command;
- (void)setTrafficEnabled:(CDVInvokedUrlCommand*)command;
- (void)setCompassEnabled:(CDVInvokedUrlCommand*)command;
- (void)attachToWebView:(CDVInvokedUrlCommand*)command;
- (void)detachFromWebView:(CDVInvokedUrlCommand*)command;
- (void)toDataURL:(CDVInvokedUrlCommand*)command;
- (void)setOptions:(CDVInvokedUrlCommand*)command;
- (void)setAllGesturesEnabled:(CDVInvokedUrlCommand*)command;
- (void)setPadding:(CDVInvokedUrlCommand*)command;
- (void)panBy:(CDVInvokedUrlCommand*)command;
- (void)getFocusedBuilding:(CDVInvokedUrlCommand*)command;
- (void)setActiveMarkerId:(CDVInvokedUrlCommand*)command;
- (void)stopAnimation:(CDVInvokedUrlCommand*)command;
@end
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,26 @@
//
// PluginMapViewController.h
// cordova-googlemaps-plugin v2
//
// Created by Masashi Katsumata.
//
//
#import <Cordova/CDV.h>
#import <UIKit/UIKit.h>
#import <math.h>
#import "PluginUtil.h"
#import "IPluginProtocol.h"
#import "PluginObjects.h"
#import "PluginViewController.h"
#import <GoogleMaps/GoogleMaps.h>
@interface PluginMapViewController : PluginViewController<GMSMapViewDelegate, GMSIndoorDisplayDelegate>
@property (nonatomic) GMSMarker* activeMarker;
@property (nonatomic) GMSMapView* map;
- (BOOL)didTapMyLocationButtonForMapView:(GMSMapView *)mapView;
- (void)didChangeActiveBuilding: (GMSIndoorBuilding *)building;
- (void)didChangeActiveLevel: (GMSIndoorLevel *)level;
@end
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,51 @@
//
// PluginMarker.h
// cordova-googlemaps-plugin v2
//
// Created by Masashi Katsumata.
//
//
#import "CordovaGoogleMaps.h"
#import "IPluginProtocol.h"
#import "PluginUtil.h"
#import "UIImageCache.h"
@interface PluginMarker : CDVPlugin<IPluginProtocol>
@property (nonatomic) BOOL initialized;
@property (nonatomic, strong) PluginMapViewController* mapCtrl;
- (void)create:(CDVInvokedUrlCommand*)command;
- (void)showInfoWindow:(CDVInvokedUrlCommand*)command;
- (void)hideInfoWindow:(CDVInvokedUrlCommand*)command;
- (void)getPosition:(CDVInvokedUrlCommand*)command;
- (void)setSnippet:(CDVInvokedUrlCommand*)command;
- (void)setTitle:(CDVInvokedUrlCommand*)command;
- (void)setFlat:(CDVInvokedUrlCommand*)command;
- (void)setOpacity:(CDVInvokedUrlCommand*)command;
- (void)setDraggable:(CDVInvokedUrlCommand*)command;
- (void)setDisableAutoPan:(CDVInvokedUrlCommand*)command;
- (void)setVisible:(CDVInvokedUrlCommand*)command;
- (void)setIcon:(CDVInvokedUrlCommand*)command;
- (void)setIconAnchor:(CDVInvokedUrlCommand*)command;
- (void)setInfoWindowAnchor:(CDVInvokedUrlCommand*)command;
- (void)remove:(CDVInvokedUrlCommand*)command;
- (void)setPosition:(CDVInvokedUrlCommand*)command;
- (void)setRotation:(CDVInvokedUrlCommand*)command;
- (void)setAnimation:(CDVInvokedUrlCommand*)command;
- (void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^)(BOOL succeeded, UIImage *image))completionBlock;
// Internal use
/*
-(void)setMarkerAnimation_:(NSString *)animation marker:(GMSMarker *)marker pluginResult:(CDVPluginResult *)pluginResult callbackId:(NSString*)callbackId;
-(void)setDropAnimation_:(GMSMarker *)marker pluginResult:(CDVPluginResult *)pluginResult callbackId:(NSString*)callbackId;
-(void)setBounceAnimation_:(GMSMarker *)marker pluginResult:(CDVPluginResult *)pluginResult callbackId:(NSString*)callbackId;
*/
- (void)_create:(NSString *)markerId markerOptions:(NSDictionary *)json callbackBlock:(void (^)(BOOL successed, id resultObj)) callbackBlock;
-(void)setIcon_:(GMSMarker *)marker iconProperty:(NSDictionary *)iconProperty callbackBlock:(void (^)(BOOL successed, id resultObj)) callbackBlock;
-(void)_removeMarker:(GMSMarker *)marker;
@end
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,27 @@
//
// PluginMarkerCluster.h
// cordova-googlemaps-plugin
//
// Created by masashi.
//
//
#import "CordovaGoogleMaps.h"
#import "IPluginProtocol.h"
#import "PluginUtil.h"
#import "PluginMarker.h"
@interface PluginMarkerCluster : PluginMarker<IPluginProtocol>
@property (atomic, strong) NSMutableDictionary *debugFlags;
@property (atomic, strong) NSMutableDictionary *pluginMarkers;
@property (atomic, strong) NSMutableDictionary *waitCntManager;
@property (atomic, strong) NSMutableDictionary *allResults;
@property (atomic, strong) NSMutableArray *deleteMarkers;
@property (atomic, strong) dispatch_semaphore_t semaphore;
@property (atomic, strong) dispatch_semaphore_t deleteThreadLock;
@property (nonatomic) BOOL stopFlag;
- (void)create:(CDVInvokedUrlCommand*)command;
- (void)redrawClusters:(CDVInvokedUrlCommand*)command;
- (void)remove:(CDVInvokedUrlCommand*)command;
@end
@@ -0,0 +1,618 @@
//
// PluginMarkerCluster.m
// cordova-googlemaps-plugin
//
// Created by masashi.
//
//
#import "PluginMarkerCluster.h"
@implementation PluginMarkerCluster
const NSString *GEOCELL_ALPHABET = @"0123456789abcdef";
const int GEOCELL_GRID_SIZE = 4;
- (void)pluginInitialize
{
if (self.initialized) {
return;
}
self.initialized = YES;
[super pluginInitialize];
// Initialize this plugin
self.waitCntManager = [NSMutableDictionary dictionary];
self.allResults = [NSMutableDictionary dictionary];
self.pluginMarkers = [NSMutableDictionary dictionary];
self.debugFlags = [NSMutableDictionary dictionary];
self.deleteMarkers = [NSMutableArray array];
self.semaphore = dispatch_semaphore_create(0);
self.deleteThreadLock = dispatch_semaphore_create(0);
self.stopFlag = NO;
int waitTimeOut = 1000 * 1000 * 1000; // 1sec
//---------------------
// Delete thread
//---------------------
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while(!self.stopFlag) {
@synchronized (self.deleteThreadLock) {
dispatch_semaphore_wait(self.deleteThreadLock, dispatch_time(DISPATCH_TIME_NOW, waitTimeOut));
}
if ([self.deleteMarkers count] == 0) {
continue;
}
dispatch_async(dispatch_get_main_queue(), ^(void) {
@synchronized (self.deleteMarkers) {
NSString *markerId;
GMSMarker *marker = nil;
//---------
// delete
//---------
int deleteCnt = (int)[self.deleteMarkers count];
for (int i = (deleteCnt - 1); i > -1; i--) {
markerId = [self.deleteMarkers objectAtIndex:i];
@synchronized (self.mapCtrl.objects) {
marker = [self.mapCtrl.objects objectForKey: markerId];
}
@synchronized (self.pluginMarkers) {
if ([[self.pluginMarkers objectForKey:markerId] isEqualToString:@"WORKING"]) {
[self.pluginMarkers setObject:@"DELETED" forKey:markerId];
} else {
@synchronized (self.mapCtrl.objects) {
[self _removeMarker:marker];
marker = nil;
[self.mapCtrl.objects removeObjectForKey:markerId];
if ([self.mapCtrl.objects objectForKey:[NSString stringWithFormat:@"marker_property_%@", markerId]]) {
[self.mapCtrl.objects removeObjectForKey:[NSString stringWithFormat:@"marker_property_%@", markerId]];
}
if ([self.mapCtrl.objects objectForKey:[NSString stringWithFormat:@"marker_icon_%@", markerId]]) {
[self.mapCtrl.objects removeObjectForKey:[NSString stringWithFormat:@"marker_icon_%@", markerId]];
}
}
[self.pluginMarkers removeObjectForKey:markerId];
[self.deleteMarkers removeObjectAtIndex:i];
}
}
}
}
});
}
});
}
- (void)pluginUnload
{
self.stopFlag = true;
[super pluginUnload];
@synchronized (self.pluginMarkers) {
NSString *key;
NSArray *keys = self.pluginMarkers.allKeys;
for (int i = 0; i < keys.count; i++) {
key = [keys objectAtIndex:i];
[self.pluginMarkers setObject:@"DELETED" forKey:key];
[self.deleteMarkers addObject:key];
}
}
}
- (void)remove:(CDVInvokedUrlCommand*)command {
NSString *clusterId = [command.arguments objectAtIndex: 0];
@synchronized (self.debugFlags) {
[self.debugFlags removeObjectForKey:clusterId];
[self.waitCntManager removeObjectForKey:clusterId];
}
@synchronized (self.pluginMarkers) {
NSString *key;
NSArray *keys = self.pluginMarkers.allKeys;
for (int i = 0; i < keys.count; i++) {
key = [keys objectAtIndex:i];
if ([key hasPrefix:clusterId]) {
[self.pluginMarkers setObject:@"DELETED" forKey:key];
[self.deleteMarkers addObject:key];
}
}
}
dispatch_semaphore_signal(self.deleteThreadLock);
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK ];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
- (void)create:(CDVInvokedUrlCommand*)command {
NSDictionary *params = [command.arguments objectAtIndex:1];
NSArray *positionList = [params objectForKey:@"positionList"];
NSMutableArray *geocellList = [NSMutableArray array];
NSMutableDictionary *position;
double lat, lng;
for (int i = 0; i < [positionList count]; i++) {
position = [positionList objectAtIndex:i];
lat = [[position objectForKey:@"lat"] doubleValue];
lng = [[position objectForKey:@"lng"] doubleValue];
[geocellList addObject:[self getGeocell:lat lng:lng resolution:12]];
}
NSString *clusterId = [NSString stringWithFormat:@"markercluster_%lu%d", command.hash, arc4random() % 100000];
NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
[result setObject:geocellList forKey:@"geocellList"];
[result setObject:clusterId forKey:@"__pgmId"];
[result setObject:[NSNumber numberWithFloat:[[UIScreen mainScreen] scale]] forKey:@"scale"];
[self.debugFlags setObject:[NSNumber numberWithBool:[[params objectForKey:@"debug"] boolValue]] forKey:clusterId];
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:result];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
- (void)redrawClusters:(CDVInvokedUrlCommand*)command {
__block NSMutableArray *updateClusterIDs = [NSMutableArray array];
__block NSMutableDictionary *changeProperties = [NSMutableDictionary dictionary];
__block NSString *clusterId = [command.arguments objectAtIndex: 0];
[self.mapCtrl.executeQueue addOperationWithBlock:^{
[self.allResults removeAllObjects];
BOOL isDebug = [[self.debugFlags objectForKey:clusterId] boolValue];
__block NSDictionary *params = [command.arguments objectAtIndex:1];
NSString *clusterId_markerId, *markerId;
NSMutableArray *new_or_update = nil;
if ([params objectForKey:@"new_or_update"]) {
new_or_update = [params objectForKey:@"new_or_update"];
}
//---------------------------
// Determine new or update
//---------------------------
int new_or_updateCnt = 0;
if (new_or_update != nil) {
new_or_updateCnt = (int)[new_or_update count];
}
NSDictionary *clusterData;
NSMutableDictionary *properties;
for (int i = 0; i < new_or_updateCnt; i++) {
clusterData = [new_or_update objectAtIndex:i];
markerId = [clusterData objectForKey:@"__pgmId"];
clusterId_markerId = [NSString stringWithFormat:@"%@-%@", clusterId, markerId];
// Save the marker properties
[self.mapCtrl.objects setObject:clusterData forKey:[NSString stringWithFormat:@"marker_property_%@", clusterId_markerId]];
// Set the WORKING status flag
[updateClusterIDs addObject:clusterId_markerId];
@synchronized (self.pluginMarkers) {
[self.pluginMarkers setObject:@"WORKING" forKey:clusterId_markerId];
}
// Prepare the marker properties for addMarker()
properties = [NSMutableDictionary dictionary];
[properties setObject:[clusterData objectForKey:@"position"] forKey:@"position"];
if ([clusterData objectForKey:@"title"]) {
[properties setObject:[clusterData objectForKey:@"title"] forKey:@"title"];
}
if (clusterData[@"visible"]) {
[properties setObject:[clusterData objectForKey:@"visible"] forKey:@"visible"];
} else {
[properties setObject:[NSNumber numberWithBool:true] forKey:@"visible"];
}
[properties setObject:clusterId_markerId forKey:@"__pgmId"];
if ([clusterData objectForKey:@"icon"]) {
id iconObj = [clusterData objectForKey:@"icon"];
if ([[iconObj class] isSubclassOfClass:[NSString class]]) {
NSMutableDictionary *iconProperties = [NSMutableDictionary dictionary];
[iconProperties setObject:iconObj forKey:@"url"];
[properties setObject:iconProperties forKey:@"icon"];
} else if ([[iconObj class] isSubclassOfClass:[NSDictionary class]]) {
NSMutableDictionary *iconProperties = [NSMutableDictionary dictionaryWithDictionary:iconObj];
if ([[clusterData objectForKey:@"isClusterIcon"] boolValue]) {
if ([iconProperties objectForKey:@"label"]) {
NSMutableDictionary *label = [NSMutableDictionary dictionaryWithDictionary:[iconProperties objectForKey:@"label"]];
if (isDebug == YES) {
[label setObject:[clusterId_markerId stringByReplacingOccurrencesOfString:clusterId withString:@""] forKey:@"text"];
} else {
[label setObject:[clusterData objectForKey:@"count"] forKey:@"text"];
}
[iconProperties setObject:label forKey:@"label"];
} else {
NSMutableDictionary *label = [NSMutableDictionary dictionary];
if (isDebug == YES) {
[label setObject:[clusterId_markerId stringByReplacingOccurrencesOfString:clusterId withString:@""] forKey:@"text"];
} else {
[label setObject:[NSNumber numberWithInt:15] forKey:@"fontSize"];
[label setObject:[NSNumber numberWithBool:TRUE] forKey:@"bold"];
[label setObject:[clusterData objectForKey:@"count"] forKey:@"text"];
}
[iconProperties setObject:label forKey:@"label"];
}
}
if ([iconProperties objectForKey:@"anchor"]) {
[iconProperties setObject:[iconProperties objectForKey:@"anchor"] forKey:@"anchor"];
}
if ([iconProperties objectForKey:@"infoWindowAnchor"]) {
[iconProperties setObject:[iconProperties objectForKey:@"infoWindowAnchor"] forKey:@"infoWindowAnchor"];
}
[properties setObject:iconProperties forKey:@"icon"];
}
} // if ([clusterData objectForKey:@"icon"]) {..}
[changeProperties setObject:properties forKey:clusterId_markerId];
} // for (int i = 0; i < new_or_updateCnt; i++) { .. }
if ([updateClusterIDs count] == 0) {
[self deleteProcess:params clusterId:clusterId];
[self endRedraw:command];
return;
}
//---------------------------
// mapping markers on the map
//---------------------------
[[NSOperationQueue mainQueue] addOperationWithBlock: ^{
//self.mapCtrl.map.selectedMarker = nil;
NSString *clusterId_markerId;
NSMutableDictionary *markerProperties;
GMSMarker *marker;
CLLocationCoordinate2D position;
double latitude, longitude;
BOOL isNew;
NSDictionary *positionJSON;
//---------------------
// new or update
//---------------------
[self.waitCntManager setObject:[NSNumber numberWithInteger:[updateClusterIDs count]] forKey:clusterId];
for (int i = 0; i < [updateClusterIDs count]; i++) {
clusterId_markerId = [updateClusterIDs objectAtIndex:i];
@synchronized(self.pluginMarkers) {
[self.pluginMarkers setObject:@"WORKING" forKey:clusterId_markerId];
}
// Get the marker properties
markerProperties = [self.mapCtrl.objects objectForKey:[NSString stringWithFormat:@"marker_property_%@", clusterId_markerId]];
isNew = [self.mapCtrl.objects objectForKey:clusterId_markerId] == nil;
//--------------------------
// regular marker
//--------------------------
if ([clusterId_markerId containsString:@"-marker_"]) {
if (isNew) {
markerProperties = [self.mapCtrl.objects objectForKey:[NSString stringWithFormat:@"marker_property_%@", clusterId_markerId]];
[super _create:clusterId_markerId markerOptions:markerProperties callbackBlock:^(BOOL successed, id resultObj) {
@synchronized (self.pluginMarkers) {
if (successed) {
//((GMSMarker *)resultObj).map = self.mapCtrl.map;
[self.pluginMarkers setObject:@"CREATED" forKey:clusterId_markerId];
NSArray *tmp = [clusterId_markerId componentsSeparatedByString:@"-"];
NSString *markerId = [tmp objectAtIndex:1];
NSMutableDictionary *createResult = [NSMutableDictionary dictionary];
GMSMarker *marker = resultObj;
UIImage *image;
NSString *iconKey = [NSString stringWithFormat:@"marker_icon_%@", marker.userData];
// retrieve key mapping set by the PluginMarker.setIcon_
NSString *iconCacheKey = [self.mapCtrl.objects objectForKey:iconKey];
if (iconCacheKey != nil) {
// use it to retrieve cached icon
image = [[UIImageCache sharedInstance] getCachedImageForKey:iconCacheKey];
}
if (image == nil) {
// fallback to old behaviour
image = [[UIImageCache sharedInstance] getCachedImageForKey:iconKey];
}
if (image != nil) {
[createResult setObject:[NSNumber numberWithInt: (int)image.size.width] forKey:@"width"];
[createResult setObject:[NSNumber numberWithInt: (int)image.size.height] forKey:@"height"];
} else {
[createResult setObject:[NSNumber numberWithInt: 24] forKey:@"width"];
[createResult setObject:[NSNumber numberWithInt: 40] forKey:@"height"];
}
[self.allResults setObject:createResult forKey:markerId];
} else {
//--------------------------------------
// Could not read icon for some reason
//--------------------------------------
[self.pluginMarkers setObject:@"DELETED" forKey:clusterId_markerId];
NSLog(@"(error) %@", resultObj);
@synchronized (self.deleteMarkers) {
[self.deleteMarkers addObject:clusterId_markerId];
}
}
}
[self decreaseWaitWithClusterId:clusterId command:command];
}];
} else {
marker = [self.mapCtrl.objects objectForKey:clusterId_markerId];
//----------------------------------------
// Set the title and snippet properties
//----------------------------------------
if ([markerProperties objectForKey:@"title"]) {
marker.title = [markerProperties objectForKey:@"title"];
}
if ([markerProperties objectForKey:@"snippet"]) {
marker.snippet = [markerProperties objectForKey:@"snippet"];
}
@synchronized (self.pluginMarkers) {
[self.pluginMarkers setObject:@"CREATED" forKey:clusterId_markerId];
}
[self decreaseWaitWithClusterId:clusterId command:command];
}
continue;
}
//--------------------------
// cluster icon
//--------------------------
markerProperties = [changeProperties objectForKey:clusterId_markerId];
if (isNew) {
// If the requested id is new location, create a marker
positionJSON = [markerProperties objectForKey:@"position"];
latitude = [[positionJSON objectForKey:@"lat"] doubleValue];
longitude = [[positionJSON objectForKey:@"lng"] doubleValue];
position = CLLocationCoordinate2DMake(latitude, longitude);
marker = [GMSMarker markerWithPosition:position];
marker.userData = clusterId_markerId;
// Store the marker instance with markerId
@synchronized (self.mapCtrl.objects) {
[self.mapCtrl.objects setObject:marker forKey:clusterId_markerId];
}
} else {
@synchronized (self.mapCtrl.objects) {
marker = [self.mapCtrl.objects objectForKey:clusterId_markerId];
}
}
//----------------------------------------
// Set the title and snippet properties
//----------------------------------------
if ([markerProperties objectForKey:@"title"]) {
marker.title = [markerProperties objectForKey:@"title"];
}
if ([markerProperties objectForKey:@"snippet"]) {
marker.snippet = [markerProperties objectForKey:@"snippet"];
}
if ([markerProperties objectForKey:@"icon"]) {
PluginMarkerCluster *self_ = self;
NSDictionary *icon = [markerProperties objectForKey:@"icon"];
[self setIconToClusterMarker:clusterId_markerId marker:marker iconProperty:icon callbackBlock:^(BOOL successed, id resultObj) {
if (successed == NO) {
//--------------------------------------
// Could not read icon for some reason
//--------------------------------------
NSLog(@"(error) %@", resultObj);
@synchronized (self_.deleteMarkers) {
[self_.deleteMarkers addObject:clusterId_markerId];
}
@synchronized (self_.pluginMarkers) {
[self_.pluginMarkers setObject:@"DELETED" forKey:clusterId_markerId];
}
} else {
//--------------------------------------
// Marker was updated
//--------------------------------------
marker.map = self_.mapCtrl.map;
@synchronized (self_.pluginMarkers) {
[self_.pluginMarkers setObject:@"CREATED" forKey:clusterId_markerId];
}
}
[self_ decreaseWaitWithClusterId:clusterId command:command];
}];
} else {
marker.map = self.mapCtrl.map;
@synchronized (self.pluginMarkers) {
[self.pluginMarkers setObject:@"CREATED" forKey:clusterId_markerId];
}
[self decreaseWaitWithClusterId:clusterId command:command];
}
} // for (int i = 0; i < [updateClusterIDs count]; i++) {..}
[self deleteProcess:params clusterId:clusterId];
}]; // [[NSOperationQueue mainQueue] addOperationWithBlock: ^{..}
}]; // dispatch_async
}
- (void) endRedraw:(CDVInvokedUrlCommand*)command {
NSLog(@"--->allResults = %@", self.allResults);
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:self.allResults];
[(CDVCommandDelegateImpl *)self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
- (void) deleteProcess:(NSDictionary *) params clusterId:(NSString *)clusterId{
NSMutableArray *deleteClusters = nil;
if ([params objectForKey:@"delete"]) {
deleteClusters = [params objectForKey:@"delete"];
}
if (deleteClusters != nil) {
//-------------------------------------
// delete markers on the delete thread
//-------------------------------------
int deleteCnt = 0;
deleteCnt = (int)[deleteClusters count];
NSString *clusterId_markerId;
@synchronized (self.deleteMarkers) {
for (int i = 0; i < deleteCnt; i++) {
clusterId_markerId = [NSString stringWithFormat:@"%@-%@",
clusterId, [deleteClusters objectAtIndex:i]];
[self.deleteMarkers addObject:clusterId_markerId];
}
}
dispatch_semaphore_signal(self.deleteThreadLock);
}
}
- (void) setIconToClusterMarker:(NSString *) markerId marker:(GMSMarker *)marker iconProperty:(NSDictionary *)iconProperty callbackBlock:(void (^)(BOOL successed, id resultObj)) callbackBlock {
PluginMarkerCluster *self_ = self;
@synchronized (_pluginMarkers) {
if ([[_pluginMarkers objectForKey:markerId] isEqualToString:@"DELETED"]) {
[self _removeMarker:marker];
if ([self.pluginMarkers objectForKey:markerId]) {
[_pluginMarkers removeObjectForKey:markerId];
}
callbackBlock(NO, @"marker has been removed");
return;
}
[_pluginMarkers setObject:@"WORKING" forKey:markerId];
}
[self setIcon_:marker iconProperty:iconProperty callbackBlock:^(BOOL successed, id resultObj) {
if (successed) {
//----------------------------------------------------------------------
// If marker has been already marked as DELETED, remove the marker.
//----------------------------------------------------------------------
GMSMarker *marker = resultObj;
@synchronized (self_.pluginMarkers) {
if ([[self_.pluginMarkers objectForKey:markerId] isEqualToString:@"DELETED"]) {
[self_ _removeMarker:marker];
[self_.pluginMarkers removeObjectForKey:markerId];
callbackBlock(YES, nil);
return;
}
[self_.pluginMarkers setObject:@"CREATED" forKey:markerId];
callbackBlock(YES, marker);
return;
}
} else {
if (marker != nil && marker.userData != nil) {
[self_ _removeMarker:marker];
}
@synchronized (self_.pluginMarkers) {
[self_.pluginMarkers removeObjectForKey:markerId];
}
callbackBlock(NO, resultObj);
}
}];
}
- (void) decreaseWaitWithClusterId:(NSString *) clusterId command:(CDVInvokedUrlCommand*)command {
@synchronized (_waitCntManager) {
int waitCnt = [[_waitCntManager objectForKey:clusterId] intValue];
waitCnt = waitCnt - 1;
[self.waitCntManager setObject:[NSNumber numberWithInt:waitCnt] forKey:clusterId];
if (waitCnt == 0) {
[self endRedraw: command];
}
}
}
- (NSString *)getGeocell:(double) lat lng:(double) lng resolution:(int)resolution {
NSMutableString *cell = [NSMutableString string];
double north = 90.0;
double south = -90.0;
double east = 180.0;
double west = -180.0;
double subcell_lng_span, subcell_lat_span;
char x, y;
while ([cell length] < resolution) {
subcell_lng_span = (east - west) / GEOCELL_GRID_SIZE;
subcell_lat_span = (north - south) / GEOCELL_GRID_SIZE;
x = (char)MIN(floor(GEOCELL_GRID_SIZE * (lng - west) / (east - west)), GEOCELL_GRID_SIZE - 1);
y = (char)MIN(floor(GEOCELL_GRID_SIZE * (lat - south) / (north - south)), GEOCELL_GRID_SIZE - 1);
[cell appendString:[NSString stringWithFormat:@"%c", [self _subdiv_char:x y:y]]];
south += subcell_lat_span * y;
north = south + subcell_lat_span;
west += subcell_lng_span * x;
east = west + subcell_lng_span;
}
return cell;
}
- (char) _subdiv_char:(int) posX y:(int)posY {
return [GEOCELL_ALPHABET characterAtIndex:(
(posY & 2) << 2 |
(posX & 2) << 1 |
(posY & 1) << 1 |
(posX & 1))];
}
- (GMSCoordinateBounds *)computeBox:(NSString *) geocell {
NSString *geoChar;
double north = 90.0;
double south = -90.0;
double east = 180.0;
double west = -180.0;
double subcell_lng_span, subcell_lat_span;
int x, y, pos;
NSRange range;
for (int i = 0; i < [geocell length]; i++) {
geoChar = [geocell substringWithRange:NSMakeRange(i, 1)];
range = [GEOCELL_ALPHABET rangeOfString:geoChar];
pos = (int)range.location;
subcell_lng_span = (east - west) / GEOCELL_GRID_SIZE;
subcell_lat_span = (north - south) / GEOCELL_GRID_SIZE;
x = (int) ((int)floor(pos / 4) % 2 * 2 + pos % 2);
y = (int) (pos - floor(pos / 4) * 4);
y = y >> 1;
y += floor(pos / 4) > 1 ? 2 : 0;
south += subcell_lat_span * y;
north = south + subcell_lat_span;
west += subcell_lng_span * x;
east = west + subcell_lng_span;
}
GMSMutablePath *mutablePath = [[GMSMutablePath alloc] init];
[mutablePath addCoordinate:CLLocationCoordinate2DMake(south, west)];
[mutablePath addCoordinate:CLLocationCoordinate2DMake(north, east)];
return [[GMSCoordinateBounds alloc] initWithPath:mutablePath];
}
@end
@@ -0,0 +1,35 @@
//
// PluginObjects
// cordova-googlemaps-plugin v2
//
// Created by Masashi Katsumata.
//
//
#ifndef PluginObjects_h
#define PluginObjects_h
#import <Foundation/Foundation.h>
@interface PluginObjects : NSObject
@property (atomic, strong) NSMutableDictionary *objects;
// set
- (void)setObject:(id)objId forKey:(NSString*)key;
// get
- (id)objectForKey:(NSString*)key;
// remove
- (void)removeObjectForKey:(NSString*)key;
// remove all
- (void)removeAllObjects;
// return all keys
- (NSArray<NSString *>*)allKeys;
@end
#endif
@@ -0,0 +1,55 @@
//
// PluginObjects
// cordova-googlemaps-plugin v2
//
// Created by Masashi Katsumata.
//
//
#import "PluginObjects.h"
@implementation PluginObjects
static NSObject *_lock;
- (instancetype)init {
self = [super init];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_lock = [NSObject alloc];
});
self.objects = [NSMutableDictionary dictionary];
return self;
}
- (void)setObject:(id)objId forKey:(NSString*)key {
@synchronized(_lock) {
[self.objects setObject:objId forKey:key];
}
}
- (id)objectForKey:(NSString*)key {
@synchronized(_lock) {
return [self.objects objectForKey:key];
}
}
- (void)removeObjectForKey:(NSString*)key {
@synchronized(_lock) {
[self.objects removeObjectForKey:key];
}
}
- (void)removeAllObjects {
@synchronized(_lock) {
[self.objects removeAllObjects];
}
}
- (NSArray<NSString *>*)allKeys {
@synchronized(_lock) {
return [self.objects allKeys];
}
}
@end
@@ -0,0 +1,32 @@
//
// PluginPolygon.h
// cordova-googlemaps-plugin v2
//
// Created by Masashi Katsumata.
//
//
#import "CordovaGoogleMaps.h"
#import "IPluginProtocol.h"
@interface PluginPolygon : CDVPlugin<IPluginProtocol>
@property (nonatomic, strong) PluginMapViewController* mapCtrl;
@property (nonatomic) BOOL initialized;
- (void)create:(CDVInvokedUrlCommand*)command;
- (void)setFillColor:(CDVInvokedUrlCommand*)command;
- (void)setStrokeColor:(CDVInvokedUrlCommand*)command;
- (void)setStrokeWidth:(CDVInvokedUrlCommand*)command;
- (void)removePointAt:(CDVInvokedUrlCommand*)command;
- (void)setPointAt:(CDVInvokedUrlCommand*)command;
- (void)setPoints:(CDVInvokedUrlCommand*)command;
- (void)insertPointAt:(CDVInvokedUrlCommand*)command;
- (void)setPointOfHoleAt:(CDVInvokedUrlCommand*)command;
- (void)removePointOfHoleAt:(CDVInvokedUrlCommand*)command;
- (void)insertPointOfHoleAt:(CDVInvokedUrlCommand*)command;
- (void)setHoles:(CDVInvokedUrlCommand*)command;
- (void)setZIndex:(CDVInvokedUrlCommand*)command;
- (void)setClickable:(CDVInvokedUrlCommand*)command;
- (void)setVisible:(CDVInvokedUrlCommand*)command;
- (void)remove:(CDVInvokedUrlCommand*)command;
- (void)setGeodesic:(CDVInvokedUrlCommand*)command;
@end
@@ -0,0 +1,746 @@
//
// PluginPolygon.m
// cordova-googlemaps-plugin v2
//
// Created by Masashi Katsumata.
//
//
#import "PluginPolygon.h"
@implementation PluginPolygon
-(void)setPluginViewController:(PluginViewController *)viewCtrl
{
self.mapCtrl = (PluginMapViewController *)viewCtrl;
}
- (void)pluginUnload
{
// Plugin destroy
NSArray *keys = [self.mapCtrl.objects allKeys];
NSString *key;
for (int i = 0; i < [keys count]; i++) {
key = [keys objectAtIndex:i];
if ([key hasPrefix:@"polygon_property"]) {
key = [key stringByReplacingOccurrencesOfString:@"_property" withString:@""];
GMSPolygon *polygon = (GMSPolygon *)[self.mapCtrl.objects objectForKey:key];
polygon.map = nil;
polygon = nil;
}
[self.mapCtrl.objects removeObjectForKey:key];
}
key = nil;
keys = nil;
NSString *pluginId = [NSString stringWithFormat:@"%@-polygon", self.mapCtrl.overlayId];
CDVViewController *cdvViewController = (CDVViewController*)self.viewController;
[cdvViewController.pluginObjects removeObjectForKey:pluginId];
[cdvViewController.pluginsMap setValue:nil forKey:pluginId];
pluginId = nil;
}
- (void)pluginInitialize
{
if (self.initialized) {
return;
}
self.initialized = YES;
[super pluginInitialize];
}
-(void)create:(CDVInvokedUrlCommand *)command
{
// Parse the polygonOptions
NSDictionary *json = [command.arguments objectAtIndex:1];
NSString *idBase = [command.arguments objectAtIndex:2];
GMSMutablePath *mutablePath = [GMSMutablePath path];
NSArray *points = [json objectForKey:@"points"];
int i = 0;
NSDictionary *latLng;
for (i = 0; i < points.count; i++) {
latLng = [points objectAtIndex:i];
[mutablePath addCoordinate:CLLocationCoordinate2DMake([[latLng objectForKey:@"lat"] doubleValue], [[latLng objectForKey:@"lng"] doubleValue])];
}
// Create paths of the hole property if specified.
NSMutableArray *holePaths = [NSMutableArray array];
if ([json valueForKey:@"holes"] && [json valueForKey:@"holes"] != [NSNull null]) {
NSArray *holes = [json objectForKey:@"holes"];
NSArray *latLngArray;
NSDictionary *latLng;
int j;
for (i = 0; i < holes.count; i++) {
latLngArray = [holes objectAtIndex:i];
GMSMutablePath *holePath = [GMSMutablePath path];
for (j = 0; j < latLngArray.count; j++) {
latLng = [latLngArray objectAtIndex:j];
[holePath addLatitude:[[latLng objectForKey:@"lat"] doubleValue] longitude:[[latLng objectForKey:@"lng"] doubleValue]];
}
[holePaths addObject:holePath];
}
}
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// Create the polygon, and assign it to the map on UI thread.
GMSPolygon *polygon = [GMSPolygon polygonWithPath:mutablePath];
if (holePaths != nil) {
polygon.holes = holePaths;
}
BOOL isVisible = YES;
// Visible property
NSString *visibleValue = [NSString stringWithFormat:@"%@", json[@"visible"]];
if ([@"0" isEqualToString:visibleValue]) {
// false
isVisible = NO;
polygon.map = nil;
} else {
// true or default
polygon.map = self.mapCtrl.map;
}
BOOL isClickable = NO;
if ([[json valueForKey:@"clickable"] boolValue]) {
isClickable = YES;
}
if ([[json valueForKey:@"geodesic"] boolValue]) {
polygon.geodesic = YES;
}
NSArray *rgbColor = [json valueForKey:@"fillColor"];
polygon.fillColor = [rgbColor parsePluginColor];
rgbColor = [json valueForKey:@"strokeColor"];
polygon.strokeColor = [rgbColor parsePluginColor];
polygon.strokeWidth = [[json valueForKey:@"strokeWidth"] floatValue];
polygon.zIndex = [[json valueForKey:@"zIndex"] floatValue];
// Since this plugin provide own click detection,
// disable default clickable feature.
polygon.tappable = NO;
// Register polygon to the overlayManager.
NSString *id = [NSString stringWithFormat:@"polygon_%@", idBase];
[self.mapCtrl.objects setObject:polygon forKey: id];
polygon.title = id;
// Run the below code on background thread.
[self.mapCtrl.executeQueue addOperationWithBlock:^{
//---------------------------
// Keep the properties
//---------------------------
NSString *propertyId = [NSString stringWithFormat:@"polygon_property_%@", idBase];
// points
NSMutableDictionary *properties = [[NSMutableDictionary alloc] init];
[properties setObject:mutablePath forKey:@"mutablePath"];
// holes
[properties setObject:holePaths forKey:@"holePaths"];
// bounds (pre-calculate for click detection)
[properties setObject:[[GMSCoordinateBounds alloc] initWithPath:mutablePath] forKey:@"bounds"];
// isVisible
[properties setObject:[NSNumber numberWithBool:isVisible] forKey:@"isVisible"];
// isClickable
[properties setObject:[NSNumber numberWithBool:isClickable] forKey:@"isClickable"];
// geodesic
[properties setObject:[NSNumber numberWithBool:polygon.geodesic] forKey:@"geodesic"];
// zIndex
[properties setObject:[NSNumber numberWithDouble:polygon.zIndex] forKey:@"zIndex"];;
[self.mapCtrl.objects setObject:properties forKey:propertyId];
//---------------------------
// Result for JS
//---------------------------
NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
[result setObject:id forKey:@"__pgmId"];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:result];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}];
}
-(void)insertHoleAt:(CDVInvokedUrlCommand *)command
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *polygonKey = [command.arguments objectAtIndex:0];
NSInteger index = [[command.arguments objectAtIndex:1] integerValue];
GMSPolygon *polygon = (GMSPolygon *)[self.mapCtrl.objects objectForKey:polygonKey];
NSArray *holes = [command.arguments objectAtIndex:2];
// Get properties
NSString *propertyId = [polygonKey stringByReplacingOccurrencesOfString:@"polygon_" withString:@"polygon_property_"];
NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithDictionary:
[self.mapCtrl.objects objectForKey:propertyId]];
NSMutableArray *holePaths = (NSMutableArray *)[properties objectForKey:@"holePaths"];
// Insert a hole into the holes property
NSArray *latLngArray;
NSDictionary *latLng;
int i;
GMSMutablePath *path = [GMSMutablePath path];
for (i = 0; i < [holes count]; i++) {
latLng = [latLngArray objectAtIndex:i];
[path addLatitude:[[latLng objectForKey:@"lat"] doubleValue] longitude:[[latLng objectForKey:@"lng"] doubleValue]];
}
[holePaths insertObject:path atIndex:index];
// Save the property
[properties setObject:holePaths forKey:@"holePaths"];
// Apply to the polygon on UI thread.
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
polygon.holes = holePaths;
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}];
}
-(void)removePointOfHoleAt:(CDVInvokedUrlCommand *)command
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *polygonKey = [command.arguments objectAtIndex:0];
NSInteger holeIndex = [[command.arguments objectAtIndex:1] integerValue];
NSInteger pointIndex = [[command.arguments objectAtIndex:2] integerValue];
//GMSPolygon *polygon = (GMSPolygon *)[self.mapCtrl.objects objectForKey:polygonKey];
// Get properties
NSString *propertyId = [polygonKey stringByReplacingOccurrencesOfString:@"polygon_" withString:@"polygon_property_"];
NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithDictionary:
[self.mapCtrl.objects objectForKey:propertyId]];
NSMutableArray *holePaths = (NSMutableArray *)[properties objectForKey:@"holePaths"];
// Insert a point into the specified hole
GMSMutablePath *path = [holePaths objectAtIndex:holeIndex];
[path removeCoordinateAtIndex:pointIndex];
[holePaths setObject:path atIndexedSubscript:holeIndex];
// Save the property
[properties setObject:holePaths forKey:@"holePaths"];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}
-(void)setPointOfHoleAt:(CDVInvokedUrlCommand *)command
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *polygonKey = [command.arguments objectAtIndex:0];
NSInteger holeIndex = [[command.arguments objectAtIndex:1] integerValue];
NSInteger pointIndex = [[command.arguments objectAtIndex:2] integerValue];
GMSPolygon *polygon = (GMSPolygon *)[self.mapCtrl.objects objectForKey:polygonKey];
NSDictionary *latLng = [command.arguments objectAtIndex:3];
// Get properties
NSString *propertyId = [polygonKey stringByReplacingOccurrencesOfString:@"polygon_" withString:@"polygon_property_"];
NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithDictionary:
[self.mapCtrl.objects objectForKey:propertyId]];
NSMutableArray *holePaths = (NSMutableArray *)[properties objectForKey:@"holePaths"];
// Insert a point into the specified hole
GMSMutablePath *path = [holePaths objectAtIndex:holeIndex];
CLLocationCoordinate2D position = CLLocationCoordinate2DMake([[latLng objectForKey:@"lat"] doubleValue], [[latLng objectForKey:@"lng"] doubleValue]);
[path replaceCoordinateAtIndex:pointIndex withCoordinate:position];
[holePaths setObject:path atIndexedSubscript:holeIndex];
// Save the property
[properties setObject:holePaths forKey:@"holePaths"];
// Apply to the polygon on UI thread.
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
polygon.holes = holePaths;
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}];
}
-(void)setHoles:(CDVInvokedUrlCommand *)command
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *polygonKey = [command.arguments objectAtIndex:0];
NSArray *holeList = [command.arguments objectAtIndex:1];
GMSMutablePath *hole;
GMSPolygon *polygon = (GMSPolygon *)[self.mapCtrl.objects objectForKey:polygonKey];
NSDictionary *latLng;
// Get properties
NSString *propertyId = [polygonKey stringByReplacingOccurrencesOfString:@"polygon_" withString:@"polygon_property_"];
NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithDictionary:
[self.mapCtrl.objects objectForKey:propertyId]];
// Remove current holes
NSMutableArray *holePaths = (NSMutableArray *)[properties objectForKey:@"holePaths"];
for (int i = 0; i < holePaths.count; i++) {
hole = [holePaths objectAtIndex:i];
[hole removeAllCoordinates];
}
[holePaths removeAllObjects];
// update all holes
GMSMutablePath *path;
NSArray *holePositions;
for (int i = 0; i < holeList.count; i++) {
path = [GMSMutablePath path];
holePositions = [holeList objectAtIndex:i];
for (int j = 0; j < holePositions.count; j++) {
latLng = [holePositions objectAtIndex:j];
[path addCoordinate:CLLocationCoordinate2DMake([[latLng objectForKey:@"lat"] doubleValue], [[latLng objectForKey:@"lng"] doubleValue])];
}
[holePaths addObject:path];
}
// Save the property
[properties setObject:holePaths forKey:@"holePaths"];
// Apply to the polygon on UI thread.
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
polygon.holes = holePaths;
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}];
}
-(void)insertPointOfHoleAt:(CDVInvokedUrlCommand *)command
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *polygonKey = [command.arguments objectAtIndex:0];
NSInteger holeIndex = [[command.arguments objectAtIndex:1] integerValue];
NSInteger pointIndex = [[command.arguments objectAtIndex:2] integerValue];
GMSPolygon *polygon = (GMSPolygon *)[self.mapCtrl.objects objectForKey:polygonKey];
NSDictionary *latLng = [command.arguments objectAtIndex:3];
// Get properties
NSString *propertyId = [polygonKey stringByReplacingOccurrencesOfString:@"polygon_" withString:@"polygon_property_"];
NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithDictionary:
[self.mapCtrl.objects objectForKey:propertyId]];
NSMutableArray *holePaths = (NSMutableArray *)[properties objectForKey:@"holePaths"];
// Insert a point into the specified hole
GMSMutablePath *path = [holePaths objectAtIndex:holeIndex];
CLLocationCoordinate2D position = CLLocationCoordinate2DMake([[latLng objectForKey:@"lat"] doubleValue], [[latLng objectForKey:@"lng"] doubleValue]);
[path insertCoordinate:position atIndex:pointIndex];
[holePaths setObject:path atIndexedSubscript:holeIndex];
// Save the property
[properties setObject:holePaths forKey:@"holePaths"];
// Apply to the polygon on UI thread.
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
polygon.holes = holePaths;
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}];
}
-(void)removePointAt:(CDVInvokedUrlCommand *)command
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *polygonKey = [command.arguments objectAtIndex:0];
NSInteger index = [[command.arguments objectAtIndex:1] integerValue];
//GMSPolygon *polygon = (GMSPolygon *)[self.mapCtrl.objects objectForKey:polygonKey];
// Get properties
NSString *propertyId = [polygonKey stringByReplacingOccurrencesOfString:@"polygon_" withString:@"polygon_property_"];
NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithDictionary:
[self.mapCtrl.objects objectForKey:propertyId]];
GMSMutablePath *mutablePath = (GMSMutablePath *)[properties objectForKey:@"mutablePath"];
[mutablePath removeCoordinateAtIndex:index];
// update the property
[properties setObject:mutablePath forKey:@"mutablePath"];
if ([mutablePath count] == 0) {
[properties removeObjectForKey:@"bounds"];
} else {
[properties setObject:[[GMSCoordinateBounds alloc] initWithPath:mutablePath] forKey:@"bounds"];
}
[self.mapCtrl.objects setObject:properties forKey:propertyId];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}
-(void)setPoints:(CDVInvokedUrlCommand *)command
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *polygonKey = [command.arguments objectAtIndex:0];
NSArray *positionList = [command.arguments objectAtIndex:1];
NSDictionary *latLng;
GMSPolygon *polygon = (GMSPolygon *)[self.mapCtrl.objects objectForKey:polygonKey];
// Get properties
NSString *propertyId = [polygonKey stringByReplacingOccurrencesOfString:@"polygon_" withString:@"polygon_property_"];
NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithDictionary:
[self.mapCtrl.objects objectForKey:propertyId]];
GMSMutablePath *mutablePath = (GMSMutablePath *)[properties objectForKey:@"mutablePath"];
[mutablePath removeAllCoordinates];
for (int i = 0; i < positionList.count; i++) {
latLng = [positionList objectAtIndex:i];
[mutablePath addCoordinate:CLLocationCoordinate2DMake([[latLng objectForKey:@"lat"] doubleValue], [[latLng objectForKey:@"lng"] doubleValue])];
}
// update the property
[properties setObject:mutablePath forKey:@"mutablePath"];
[properties setObject:[[GMSCoordinateBounds alloc] initWithPath:mutablePath] forKey:@"bounds"];
[self.mapCtrl.objects setObject:properties forKey:propertyId];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[polygon setPath:mutablePath];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}];
}
-(void)insertPointAt:(CDVInvokedUrlCommand *)command
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *polygonKey = [command.arguments objectAtIndex:0];
NSInteger index = [[command.arguments objectAtIndex:1] integerValue];
NSDictionary *latLng = [command.arguments objectAtIndex:2];
GMSPolygon *polygon = (GMSPolygon *)[self.mapCtrl.objects objectForKey:polygonKey];
// Get properties
NSString *propertyId = [polygonKey stringByReplacingOccurrencesOfString:@"polygon_" withString:@"polygon_property_"];
NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithDictionary:
[self.mapCtrl.objects objectForKey:propertyId]];
GMSMutablePath *mutablePath = (GMSMutablePath *)[properties objectForKey:@"mutablePath"];
CLLocationCoordinate2D position = CLLocationCoordinate2DMake([[latLng objectForKey:@"lat"] doubleValue], [[latLng objectForKey:@"lng"] doubleValue]);
[mutablePath insertCoordinate:position atIndex:index];
// update the property
[properties setObject:mutablePath forKey:@"mutablePath"];
[properties setObject:[[GMSCoordinateBounds alloc] initWithPath:mutablePath] forKey:@"bounds"];
[self.mapCtrl.objects setObject:properties forKey:propertyId];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[polygon setPath:mutablePath];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}];
}
-(void)setPointAt:(CDVInvokedUrlCommand *)command
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *polygonKey = [command.arguments objectAtIndex:0];
NSInteger index = [[command.arguments objectAtIndex:1] integerValue];
NSDictionary *latLng = [command.arguments objectAtIndex:2];
GMSPolygon *polygon = (GMSPolygon *)[self.mapCtrl.objects objectForKey:polygonKey];
// Get properties
NSString *propertyId = [polygonKey stringByReplacingOccurrencesOfString:@"polygon_" withString:@"polygon_property_"];
NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithDictionary:
[self.mapCtrl.objects objectForKey:propertyId]];
GMSMutablePath *mutablePath = (GMSMutablePath *)[properties objectForKey:@"mutablePath"];
CLLocationCoordinate2D position = CLLocationCoordinate2DMake([[latLng objectForKey:@"lat"] doubleValue], [[latLng objectForKey:@"lng"] doubleValue]);
[mutablePath replaceCoordinateAtIndex:index withCoordinate:position];
// update the property
[properties setObject:mutablePath forKey:@"mutablePath"];
[properties setObject:[[GMSCoordinateBounds alloc] initWithPath:mutablePath] forKey:@"bounds"];
[self.mapCtrl.objects setObject:properties forKey:propertyId];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[polygon setPath:mutablePath];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}];
}
/**
* Set fill color
* @params key
*/
-(void)setFillColor:(CDVInvokedUrlCommand *)command
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *polygonKey = [command.arguments objectAtIndex:0];
GMSPolygon *polygon = [self.mapCtrl.objects objectForKey:polygonKey];
NSArray *rgbColor = [command.arguments objectAtIndex:1];
// Apply to the polygon on UI thread.
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[polygon setFillColor:[rgbColor parsePluginColor]];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}];
}
/**
* Set stroke color
* @params key
*/
-(void)setStrokeColor:(CDVInvokedUrlCommand *)command
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *polygonKey = [command.arguments objectAtIndex:0];
GMSPolygon *polygon = [self.mapCtrl.objects objectForKey:polygonKey];
NSArray *rgbColor = [command.arguments objectAtIndex:1];
// Apply to the polygon on UI thread.
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[polygon setStrokeColor:[rgbColor parsePluginColor]];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}];
}
/**
* Set stroke width
* @params key
*/
-(void)setStrokeWidth:(CDVInvokedUrlCommand *)command
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *polygonKey = [command.arguments objectAtIndex:0];
GMSPolygon *polygon = [self.mapCtrl.objects objectForKey:polygonKey];
float width = [[command.arguments objectAtIndex:1] floatValue];
// Apply to the polygon on UI thread.
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[polygon setStrokeWidth:width];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}];
}
/**
* Set z-index
* @params key
*/
-(void)setZIndex:(CDVInvokedUrlCommand *)command
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *polygonKey = [command.arguments objectAtIndex:0];
GMSPolygon *polygon = [self.mapCtrl.objects objectForKey:polygonKey];
int zIndex = [[command.arguments objectAtIndex:1] intValue];
// Get properties
NSString *propertyId = [polygonKey stringByReplacingOccurrencesOfString:@"polygon_" withString:@"polygon_property_"];
NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithDictionary:
[self.mapCtrl.objects objectForKey:propertyId]];
// update the property
[properties setObject:[NSNumber numberWithInt:zIndex] forKey:@"zIndex"];
[self.mapCtrl.objects setObject:properties forKey:propertyId];
// Apply to the polygon on UI thread.
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[polygon setZIndex:(int)zIndex];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}];
}
/**
* Set clickable
* @params key
*/
-(void)setClickable:(CDVInvokedUrlCommand *)command
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *polygonKey = [command.arguments objectAtIndex:0];
//GMSPolygon *polygon = [self.mapCtrl.objects objectForKey:polygonKey];
Boolean isClickable = [[command.arguments objectAtIndex:1] boolValue];
// Get properties
NSString *propertyId = [polygonKey stringByReplacingOccurrencesOfString:@"polygon_" withString:@"polygon_property_"];
NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithDictionary:
[self.mapCtrl.objects objectForKey:propertyId]];
// update the property
[properties setObject:[NSNumber numberWithBool:isClickable] forKey:@"isClickable"];
[self.mapCtrl.objects setObject:properties forKey:propertyId];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}
/**
* Set visibility
* @params key
*/
-(void)setVisible:(CDVInvokedUrlCommand *)command
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *polygonKey = [command.arguments objectAtIndex:0];
GMSPolygon *polygon = [self.mapCtrl.objects objectForKey:polygonKey];
Boolean isVisible = [[command.arguments objectAtIndex:1] boolValue];
// Get properties
NSString *propertyId = [polygonKey stringByReplacingOccurrencesOfString:@"polygon_" withString:@"polygon_property_"];
NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithDictionary:
[self.mapCtrl.objects objectForKey:propertyId]];
// update the property
[properties setObject:[NSNumber numberWithBool:isVisible] forKey:@"isVisible"];
[self.mapCtrl.objects setObject:properties forKey:propertyId];
// Apply to the polygon on UI thread.
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
if (isVisible) {
polygon.map = self.mapCtrl.map;
} else {
polygon.map = nil;
}
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}];
}
/**
* Set geodesic
* @params key
*/
-(void)setGeodesic:(CDVInvokedUrlCommand *)command
{
[self.mapCtrl.executeQueue addOperationWithBlock:^{
NSString *polygonKey = [command.arguments objectAtIndex:0];
GMSPolygon *polygon = [self.mapCtrl.objects objectForKey:polygonKey];
Boolean isGeodisic = [[command.arguments objectAtIndex:1] boolValue];
// Apply to the polygon on UI thread.
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[polygon setGeodesic:isGeodisic];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}];
}
/**
* Remove the polygon
* @params key
*/
-(void)remove:(CDVInvokedUrlCommand *)command
{
// Apply to the polygon on UI thread.
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSString *polygonKey = [command.arguments objectAtIndex:0];
GMSPolygon *polygon = [self.mapCtrl.objects objectForKey:polygonKey];
[self.mapCtrl.objects removeObjectForKey:[polygonKey stringByReplacingOccurrencesOfString:@"polygon_" withString:@"polygon_property_"]];
[self.mapCtrl.objects removeObjectForKey:polygonKey];
polygon.map = nil;
polygon = nil;
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}];
}
@end

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