package org.fieldpapers.model;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Color;
import com.mapbox.mapboxsdk.api.ILatLng;
import com.mapbox.mapboxsdk.events.MapListener;
import com.mapbox.mapboxsdk.events.RotateEvent;
import com.mapbox.mapboxsdk.events.ScrollEvent;
import com.mapbox.mapboxsdk.events.ZoomEvent;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.overlay.Marker;
import com.mapbox.mapboxsdk.overlay.Overlay;
import com.mapbox.mapboxsdk.overlay.PathOverlay;
import com.mapbox.mapboxsdk.views.MapView;
import com.mapbox.mapboxsdk.views.MapViewListener;
import com.spatialdev.osm.marker.OSMItemizedIconOverlay;
import com.spatialdev.osm.renderer.OSMLine;
import com.spatialdev.osm.renderer.OSMOverlay;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.index.strtree.STRtree;
import org.apache.commons.io.FileUtils;
import org.fieldpapers.listeners.FPListener;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class FPAtlas implements MapViewListener, MapListener {
private static final String PREVIOUS_FP_FILE_PATH = "org.redcross.openmapkit.PREVIOUS_FP_FILE_PATH";
public static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory();
private static File fpGeoJson;
private static FPAtlas atlas;
private JSONObject geoJson;
private String title;
private Activity activity;
private MapView mapView;
private STRtree spatialIndex = new STRtree();
private Map<String, FPPage> pages = new HashMap<>();
private PathOverlay selectedPathOverlay;
public static void load(File fpGeoJSON) throws IOException, JSONException {
/**
* Only load if the file specified is a file not currently loaded.
*/
if (fpGeoJSON.equals(FPAtlas.fpGeoJson)) return;
FPAtlas.fpGeoJson = fpGeoJSON;
atlas = new FPAtlas(fpGeoJSON);
}
public static void addToMap(Activity activity, MapView mapView) throws IOException, JSONException {
/**
* Deal with SharedPreferences. Use it if we haven't explicitly loaded. Set it if we have.
*/
SharedPreferences preferences = activity.getPreferences(Context.MODE_PRIVATE);
if (fpGeoJson == null) {
String previousFpGeoJsonPath = preferences.getString(PREVIOUS_FP_FILE_PATH, null);
if (previousFpGeoJsonPath == null) return;
load(new File(previousFpGeoJsonPath));
} else {
SharedPreferences.Editor editor = preferences.edit();
editor.putString(PREVIOUS_FP_FILE_PATH, fpGeoJson.getAbsolutePath());
editor.apply();
}
if (atlas == null) return;
atlas.setActivity(activity);
atlas.setupMapView(mapView);
}
public static FPAtlas singleton() {
return atlas;
}
/**
* Singleton Constructor
*
* @param fpGeoJSON
* @throws IOException
* @throws JSONException
*/
private FPAtlas(File fpGeoJSON) throws IOException, JSONException {
String geoJsonStr = FileUtils.readFileToString(fpGeoJSON, "UTF-8");
geoJson = new JSONObject(geoJsonStr);
parseTitle();
parsePages();
}
private void parsePages() {
JSONArray features = geoJson.optJSONArray("features");
if (features == null) return;
int len = features.length();
// the 3rd feature is the first page
for (int i = 2; i < len; ++i) {
JSONObject o = features.optJSONObject(i);
if (o != null) {
FPPage p = new FPPage(o);
pages.put(p.pageNumber(), p);
spatialIndex.insert(p.envelope(), p);
}
}
}
private void parseTitle() {
try {
title = geoJson.getJSONArray("features")
.getJSONObject(0)
.getJSONObject("properties")
.getString("title");
} catch (JSONException e) {
e.printStackTrace();
}
}
public String title() {
return title;
}
public void setActivity(Activity activity) {
this.activity = activity;
}
public void setupMapView(MapView mapView) {
this.mapView = mapView;
/**
* There can only be one mapViewListener, so we have to reserve
* that privilege to OSMMap. This is ridiculous, but it's a weird
* oversight in the design of the Mapbox Android SDK Legacy.
*
* There can be multiple "listeners", which is a different interface
* that handles things like scroll. Map view listeners handle things like
* tap. Not sure why they are two different things...
*
* We look for the singleton of FPAtlas in OSMMap, and then notify
* onTapMap from there. Sorry.
*/
// mapView.setMapViewListener(this);
mapView.addListener(this);
addPathOverlaysToMapView();
}
private void findMapCenterPage() {
LatLng center = mapView.getCenter();
findPage(center);
}
private void findPage(ILatLng latLng) {
Coordinate coord = new Coordinate(latLng.getLongitude(), latLng.getLatitude());
Envelope env = new Envelope(coord);
List fpPages = spatialIndex.query(env);
for (Object p : fpPages) {
FPPage page = (FPPage)p;
Geometry pageGeom = page.geometry();
if (pageGeom.contains(GEOMETRY_FACTORY.createPoint(coord))) {
foundPage(page);
return;
}
}
noPageFound();
}
private void noPageFound() {
clearSelectedPathOverlay();
if (activity != null && activity instanceof FPListener) {
((FPListener)activity).onMapCenterPageChangeMessage(null);
}
}
private void foundPage(FPPage page) {
if (activity != null && activity instanceof FPListener) {
String msg = pageMessage(page);
((FPListener)activity).onMapCenterPageChangeMessage(msg);
setSelectedPathOverlay(page);
}
}
private String pageMessage(FPPage page) {
return title() + " " + page.pageNumber();
}
private void clearSelectedPathOverlay() {
if (selectedPathOverlay != null) {
selectedPathOverlay.getPaint().setColor(Color.BLACK);
selectedPathOverlay = null;
}
}
private void setSelectedPathOverlay(FPPage page) {
clearSelectedPathOverlay();
PathOverlay pathOverlay = page.pathOverlay();
List<Overlay> overlays = mapView.getOverlays();
// Remove overlay to select and then put it in the right place in the list
// so that it is in front of the other PathOverlays but behind the OSM
// overlays.
overlays.remove(pathOverlay);
int len = overlays.size();
boolean overlayMoved = false;
for (int i = 0; i < len; ++i) {
Overlay o = overlays.get(i);
if (o instanceof OSMOverlay || o instanceof OSMItemizedIconOverlay) {
overlays.add(i-1, pathOverlay);
overlayMoved = true;
break;
}
}
if (!overlayMoved) {
overlays.add(pathOverlay);
}
pathOverlay.getPaint().setARGB(255, OSMLine.DEFAULT_R, OSMLine.DEFAULT_G, OSMLine.DEFAULT_B);
selectedPathOverlay = pathOverlay;
}
private void addPathOverlaysToMapView() {
List<Overlay> overlays = mapView.getOverlays();
for (Overlay o : overlays) {
if (o instanceof PathOverlay) {
overlays.remove(o);
}
}
Collection<FPPage> pagesCollection = pages.values();
for (FPPage p : pagesCollection) {
overlays.add(p.pathOverlay());
}
mapView.invalidate();
}
/**
* LISTENERS
*/
@Override
public void onScroll(ScrollEvent event) {
findMapCenterPage();
}
@Override
public void onZoom(ZoomEvent event) {
findMapCenterPage();
}
@Override
public void onRotate(RotateEvent event) {
findMapCenterPage();
}
@Override
public void onShowMarker(MapView pMapView, Marker pMarker) {
}
@Override
public void onHideMarker(MapView pMapView, Marker pMarker) {
}
@Override
public void onTapMarker(MapView pMapView, Marker pMarker) {
}
@Override
public void onLongPressMarker(MapView pMapView, Marker pMarker) {
}
/**
* This is not called by an actual map listener. It's called by:
*
* OSMMap#onTapMap
*
* @param pMapView
* @param pPosition
*/
@Override
public void onTapMap(MapView pMapView, ILatLng pPosition) {
findPage(pPosition);
}
@Override
public void onLongPressMap(MapView pMapView, ILatLng pPosition) {
}
}