diff --git a/android/manifest b/android/manifest index 1380c543..675925fe 100644 --- a/android/manifest +++ b/android/manifest @@ -2,7 +2,7 @@ # this is your module manifest and used by Titanium # during compilation, packaging, distribution, etc. # -version: 5.5.0 +version: 5.6.0 apiversion: 4 architectures: arm64-v8a armeabi-v7a x86 x86_64 description: External version of Map module using native Google Maps library diff --git a/android/src/ti/map/MapBoxOfflineTileProvider.java b/android/src/ti/map/MapBoxOfflineTileProvider.java new file mode 100644 index 00000000..74a8c42a --- /dev/null +++ b/android/src/ti/map/MapBoxOfflineTileProvider.java @@ -0,0 +1,225 @@ +package ti.map; + +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.LatLngBounds; +import com.google.android.gms.maps.model.Tile; +import com.google.android.gms.maps.model.TileProvider; +import java.io.Closeable; +import java.io.File; +import org.appcelerator.kroll.common.Log; + +public class MapBoxOfflineTileProvider implements TileProvider, Closeable +{ + + // ------------------------------------------------------------------------ + // Instance Variables + // ------------------------------------------------------------------------ + + private int mMinimumZoom = Integer.MIN_VALUE; + + private int mMaximumZoom = Integer.MAX_VALUE; + + private LatLngBounds mBounds; + + private SQLiteDatabase mDatabase; + + private final Object mDatabaseLock = new Object(); + + // ------------------------------------------------------------------------ + // Constructors + // ------------------------------------------------------------------------ + + public MapBoxOfflineTileProvider(File file) + { + this(file.getAbsolutePath()); + } + + public MapBoxOfflineTileProvider(String pathToFile) + { + int flags = SQLiteDatabase.OPEN_READONLY | SQLiteDatabase.NO_LOCALIZED_COLLATORS; + try { + this.mDatabase = SQLiteDatabase.openDatabase(pathToFile, null, flags); + this.calculateZoomConstraints(); + this.calculateBounds(); + } catch (Error e) { + Log.e("TiUIMapView", "Error loading mbtiles file"); + } + } + + // ------------------------------------------------------------------------ + // TileProvider Interface + // ------------------------------------------------------------------------ + + @Override + public Tile getTile(int x, int y, int z) + { + Tile tile = NO_TILE; + synchronized (mDatabaseLock) { + if (this.isZoomLevelAvailable(z) && this.isDatabaseAvailable()) { + String[] projection = { "tile_data" }; + int row = ((int) (Math.pow(2, z) - y) - 1); + String predicate = "tile_row = ? AND tile_column = ? AND zoom_level = ?"; + String[] values = { String.valueOf(row), String.valueOf(x), String.valueOf(z) }; + Cursor c = this.mDatabase.query("tiles", projection, predicate, values, null, null, null); + if (c != null) { + c.moveToFirst(); + if (!c.isAfterLast()) { + tile = new Tile(256, 256, c.getBlob(0)); + } + c.close(); + } + } + } + return tile; + } + + // ------------------------------------------------------------------------ + // Closeable Interface + // ------------------------------------------------------------------------ + + /** + * Closes the provider, cleaning up any background resources. + * + *
+ * You must call {@link #close()} when you are finished using an instance of + * this provider. Failing to do so may leak resources, such as the backing + * SQLiteDatabase. + *
+ */ + @Override + public void close() + { + synchronized (mDatabaseLock) { + if (this.mDatabase != null) { + this.mDatabase.close(); + this.mDatabase = null; + } + } + } + + // ------------------------------------------------------------------------ + // Public Methods + // ------------------------------------------------------------------------ + + public void swapDatabase(File newDatabaseFile) + { + synchronized (mDatabaseLock) { + mDatabase.close(); + int flags = SQLiteDatabase.OPEN_READONLY | SQLiteDatabase.NO_LOCALIZED_COLLATORS; + this.mDatabase = SQLiteDatabase.openDatabase(newDatabaseFile.getAbsolutePath(), null, flags); + } + } + /** + * The minimum zoom level supported by this provider. + * + * @return the minimum zoom level supported or {@link Integer.MIN_VALUE} if + * it could not be determined. + */ + public int getMinimumZoom() + { + return this.mMinimumZoom; + } + + /** + * The maximum zoom level supported by this provider. + * + * @return the maximum zoom level supported or {@link Integer.MAX_VALUE} if + * it could not be determined. + */ + public int getMaximumZoom() + { + return this.mMaximumZoom; + } + + /** + * The geographic bounds available from this provider. + * + * @return the geographic bounds available or {@link null} if it could not + * be determined. + */ + public LatLngBounds getBounds() + { + return this.mBounds; + } + + /** + * Determines if the requested zoom level is supported by this provider. + * + * @param zoom The requested zoom level. + * @return {@code true} if the requested zoom level is supported by this + * provider. + */ + public boolean isZoomLevelAvailable(int zoom) + { + return (zoom >= this.mMinimumZoom) && (zoom <= this.mMaximumZoom); + } + + // ------------------------------------------------------------------------ + // Private Methods + // ------------------------------------------------------------------------ + + private void calculateZoomConstraints() + { + if (this.isDatabaseAvailable()) { + String[] projection = new String[] { "value" }; + + String[] minArgs = new String[] { "minzoom" }; + + String[] maxArgs = new String[] { "maxzoom" }; + + Cursor c; + + c = this.mDatabase.query("metadata", projection, "name = ?", minArgs, null, null, null); + + c.moveToFirst(); + if (!c.isAfterLast()) { + this.mMinimumZoom = c.getInt(0); + } + c.close(); + + c = this.mDatabase.query("metadata", projection, "name = ?", maxArgs, null, null, null); + + c.moveToFirst(); + if (!c.isAfterLast()) { + this.mMaximumZoom = c.getInt(0); + } + c.close(); + } + } + + private void calculateBounds() + { + if (this.isDatabaseAvailable()) { + String[] projection = new String[] { "value" }; + + String[] subArgs = new String[] { "bounds" }; + + Cursor c = this.mDatabase.query("metadata", projection, "name = ?", subArgs, null, null, null); + + c.moveToFirst(); + if (!c.isAfterLast()) { + String[] parts = c.getString(0).split(",\\s*"); + + double w = Double.parseDouble(parts[0]); + double s = Double.parseDouble(parts[1]); + double e = Double.parseDouble(parts[2]); + double n = Double.parseDouble(parts[3]); + + LatLng ne = new LatLng(n, e); + LatLng sw = new LatLng(s, w); + + this.mBounds = new LatLngBounds(sw, ne); + } + c.close(); + } + } + + private boolean isDatabaseAvailable() + { + synchronized (mDatabaseLock) { + return (this.mDatabase != null) && (this.mDatabase.isOpen()); + } + } +} diff --git a/android/src/ti/map/MapModule.java b/android/src/ti/map/MapModule.java index ff21d374..770c1cd3 100644 --- a/android/src/ti/map/MapModule.java +++ b/android/src/ti/map/MapModule.java @@ -78,6 +78,8 @@ public class MapModule extends KrollModule implements OnMapsSdkInitializedCallba public static final String PROPERTY_HEADING = "heading"; public static final String PROPERTY_PITCH = "pitch"; + @Kroll.constant + public static final int TYPE_NONE = GoogleMap.MAP_TYPE_NONE; @Kroll.constant public static final int NORMAL_TYPE = GoogleMap.MAP_TYPE_NORMAL; @Kroll.constant diff --git a/android/src/ti/map/TiUIMapView.java b/android/src/ti/map/TiUIMapView.java index f52b68c5..c48de1b6 100644 --- a/android/src/ti/map/TiUIMapView.java +++ b/android/src/ti/map/TiUIMapView.java @@ -88,6 +88,8 @@ public class TiUIMapView extends TiUIFragment private DefaultClusterRenderer clusterRender; private MarkerManager mMarkerManager; private MarkerManager.Collection collection; + private float minZoom = -1; + private float maxZoom = -1; public TiUIMapView(final TiViewProxy proxy, Activity activity) { @@ -239,7 +241,12 @@ public void onMapReady(GoogleMap gMap) markerCollection.setInfoWindowAdapter(this); markerCollection.setOnInfoWindowClickListener(this); markerCollection.setOnMarkerDragListener(this); - + if (minZoom != -1) { + map.setMinZoomPreference(minZoom); + } + if (maxZoom != -1) { + map.setMaxZoomPreference(maxZoom); + } ((ViewProxy) proxy).clearPreloadObjects(); } @@ -322,6 +329,12 @@ public void processMapProperties(KrollDict d) if (clusterRender != null) clusterRender.setMinClusterSize(d.getInt(MapModule.PROPERTY_MIN_CLUSTER_SIZE)); } + if (d.containsKey("maxZoomLevel")) { + maxZoom = Float.parseFloat(d.getString("maxZoomLevel")); + } + if (d.containsKey("minZoomLevel")) { + minZoom = Float.parseFloat(d.getString("minZoomLevel")); + } } @Override diff --git a/android/src/ti/map/ViewProxy.java b/android/src/ti/map/ViewProxy.java index a09b9faf..62c698d2 100644 --- a/android/src/ti/map/ViewProxy.java +++ b/android/src/ti/map/ViewProxy.java @@ -15,6 +15,7 @@ import com.google.android.gms.maps.model.TileOverlay; import com.google.android.gms.maps.model.TileOverlayOptions; import com.google.maps.android.heatmaps.HeatmapTileProvider; +import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -27,11 +28,15 @@ import org.appcelerator.kroll.common.Log; import org.appcelerator.kroll.common.TiMessenger; import org.appcelerator.titanium.TiApplication; +import org.appcelerator.titanium.TiBlob; import org.appcelerator.titanium.TiC; +import org.appcelerator.titanium.TiFileProxy; +import org.appcelerator.titanium.io.TiBaseFile; import org.appcelerator.titanium.proxy.TiViewProxy; import org.appcelerator.titanium.util.TiConvert; import org.appcelerator.titanium.view.TiUIView; import ti.map.AnnotationProxy.AnnotationDelegate; +import ti.modules.titanium.filesystem.FileProxy; @Kroll. proxy(creatableInModule = MapModule.class, @@ -83,6 +88,7 @@ public class ViewProxy extends TiViewProxy implements AnnotationDelegate private static final int MSG_REMOVE_ALL_IMAGE_OVERLAYS = MSG_FIRST_ID + 933; private static final int MSG_ADD_HEAT_MAP = MSG_FIRST_ID + 941; + private static final int MSG_ADD_MBILES_MAP = MSG_FIRST_ID + 942; private final ArrayList