package mil.nga.dice.map;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
import android.util.TimeUtils;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Polygon;
import com.google.android.gms.maps.model.PolygonOptions;
import com.google.android.gms.maps.model.TileOverlay;
import com.google.android.gms.maps.model.TileOverlayOptions;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.MultiPolygon;
import mil.nga.dice.jackson.deserializer.FeatureDeserializer;
public class OfflineMap {
private static final String TAG = "OfflineMap";
private static final int FILL_COLOR = 0xFFDDDDDD;
private static final List<OfflineMap> waitingForPolygons = new ArrayList<>();
private static boolean initialized = false;
private static Collection<PolygonOptions> polygonOptions;
public static synchronized void initialize(Context context) {
if (initialized) {
throw new Error("attempted to initialize " + OfflineMap.class.getName() + " more than once");
}
initialized = true;
new LoadOfflineMapTask(context).execute();
}
private final GoogleMap map;
private final TileOverlay backgroundTileOverlay;
private Collection<Polygon> polygons;
public OfflineMap(GoogleMap map) {
this.map = map;
backgroundTileOverlay = map.addTileOverlay(new TileOverlayOptions()
.tileProvider(BackgroundTileProvider.getInstance())
.visible(false)
.zIndex(-4));
}
public void setVisible(boolean visible) {
Log.d(TAG, "set visible " + visible);
backgroundTileOverlay.setVisible(visible);
if (polygonOptions == null) {
Log.d(TAG, "waiting for polygons");
waitingForPolygons.add(this);
return;
}
else if (polygons == null) {
polygonsReady();
}
else {
for (Polygon polygon : polygons) {
polygon.setVisible(visible);
}
}
}
public boolean isVisible() {
return backgroundTileOverlay.isVisible();
}
public void clear() {
Log.d(TAG, "clearing offline map");
backgroundTileOverlay.clearTileCache();
backgroundTileOverlay.remove();
for (Polygon polygon : polygons) {
polygon.remove();
}
polygons.clear();
}
private void polygonsReady() {
Log.d(TAG, "adding " + polygonOptions.size() + " polygons to map");
polygons = new ArrayList<Polygon>(polygonOptions.size());
for (PolygonOptions polygon : polygonOptions) {
polygon.visible(isVisible());
polygons.add(map.addPolygon(polygon));
}
Log.d(TAG, "polygons added to map");
}
private static class LoadOfflineMapTask extends AsyncTask<Void, Void, List<Geometry>> {
private static final String OFFLINE_MAP_FILENAME = "ne_110m_land.geojson";
private final Context context;
public LoadOfflineMapTask(Context context) {
this.context = context;
}
@Override
protected List<Geometry> doInBackground(Void... params) {
// TODO: parse geojson directly into PolygonOptions
List<Geometry> geometries = new ArrayList<>();
InputStream is = null;
try {
is = context.getAssets().open(OFFLINE_MAP_FILENAME);
geometries = new FeatureDeserializer().parseFeatures(is);
}
catch (IOException e) {
e.printStackTrace();
}
finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
}
}
}
return geometries;
}
@Override
protected void onPostExecute(List<Geometry> features) {
new TransformOfflinePolygonsTask(features).execute();
}
}
private static class TransformOfflinePolygonsTask extends AsyncTask<Void, Void, List<PolygonOptions>> {
private final List<Geometry> features;
private long startTime;
public TransformOfflinePolygonsTask(List<Geometry> features) {
this.features = features;
}
private PolygonOptions transformPolygon(com.vividsolutions.jts.geom.Polygon polygon) {
PolygonOptions options = new PolygonOptions()
.zIndex(-3)
.visible(false)
.fillColor(FILL_COLOR)
.strokeWidth(0);
for (Coordinate coordinate : polygon.getExteriorRing().getCoordinates()) {
options.add(new LatLng(coordinate.y, coordinate.x));
}
for (int i = 0; i < polygon.getNumInteriorRing(); i++) {
Coordinate[] coordinates = polygon.getInteriorRingN(0).getCoordinates();
Collection<LatLng> hole = new ArrayList<>(coordinates.length);
for (Coordinate coordinate : coordinates) {
hole.add(new LatLng(coordinate.y, coordinate.x));
}
options.addHole(hole);
}
return options;
}
@Override
protected List<PolygonOptions> doInBackground(Void... voids) {
startTime = System.currentTimeMillis();
Log.d(TAG, "transforming geometries to polygons");
List<PolygonOptions> polygons = new ArrayList<>(features.size());
for (Geometry feature : features) {
// For now all offline map features are polygons
if ("Polygon".equals(feature.getGeometryType())) {
polygons.add(transformPolygon((com.vividsolutions.jts.geom.Polygon) feature));
}
else if ("MultiPolygon".equals(feature.getGeometryType())) {
MultiPolygon mp = (MultiPolygon) feature;
for (int i = 0; i < mp.getNumGeometries(); i++) {
com.vividsolutions.jts.geom.Polygon polygon = (com.vividsolutions.jts.geom.Polygon) mp.getGeometryN(i);
polygons.add(transformPolygon(polygon));
}
}
}
return polygons;
}
@Override
protected void onPostExecute(List<PolygonOptions> polygons) {
long stopTime = System.currentTimeMillis();
Log.d(TAG, "finished transforming geometries to polygons after " + (stopTime - startTime) + "ms");
polygonOptions = polygons;
Iterator<OfflineMap> waitingMaps = waitingForPolygons.iterator();
while (waitingMaps.hasNext()) {
Log.d(TAG, "notify polygons ready");
waitingMaps.next().polygonsReady();
waitingMaps.remove();
}
}
}
}