package cgeo.geocaching;
import cgeo.geocaching.compatibility.Compatibility;
import cgeo.geocaching.concurrent.BlockingThreadPool;
import cgeo.geocaching.files.LocalStorage;
import cgeo.geocaching.geopoint.GeopointFormatter.Format;
import cgeo.geocaching.network.Network;
import cgeo.geocaching.network.Parameters;
import cgeo.geocaching.utils.Log;
import ch.boye.httpclientandroidlib.HttpResponse;
import org.apache.commons.lang3.StringUtils;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.WindowManager;
import java.io.File;
import java.util.concurrent.TimeUnit;
public class StaticMapsProvider {
private static final String PREFIX_PREVIEW = "preview";
private static final String GOOGLE_STATICMAP_URL = "http://maps.google.com/maps/api/staticmap";
private static final String SATELLITE = "satellite";
private static final String ROADMAP = "roadmap";
private static final String WAYPOINT_PREFIX = "wp";
private static final String MAP_FILENAME_PREFIX = "map_";
private static final String MARKERS_URL = "http://status.cgeo.org/assets/markers/";
/** We assume there is no real usable image with less than 1k */
private static final int MIN_MAP_IMAGE_BYTES = 1000;
/** ThreadPool restricting this to 1 Thread. **/
private static final BlockingThreadPool pool = new BlockingThreadPool(1, Thread.MIN_PRIORITY);
private static File getMapFile(final String geocode, String prefix, final boolean createDirs) {
return LocalStorage.getStorageFile(geocode, MAP_FILENAME_PREFIX + prefix, false, createDirs);
}
private static void downloadDifferentZooms(final String geocode, String markerUrl, String prefix, String latlonMap, int edge, final Parameters waypoints) {
downloadMap(geocode, 20, SATELLITE, markerUrl, prefix + '1', "", latlonMap, edge, edge, waypoints);
downloadMap(geocode, 18, SATELLITE, markerUrl, prefix + '2', "", latlonMap, edge, edge, waypoints);
downloadMap(geocode, 16, ROADMAP, markerUrl, prefix + '3', "", latlonMap, edge, edge, waypoints);
downloadMap(geocode, 14, ROADMAP, markerUrl, prefix + '4', "", latlonMap, edge, edge, waypoints);
downloadMap(geocode, 11, ROADMAP, markerUrl, prefix + '5', "", latlonMap, edge, edge, waypoints);
}
private static void downloadMap(String geocode, int zoom, String mapType, String markerUrl, String prefix, String shadow, String latlonMap, int width, int height, final Parameters waypoints) {
final Parameters params = new Parameters(
"center", latlonMap,
"zoom", String.valueOf(zoom),
"size", String.valueOf(width) + 'x' + String.valueOf(height),
"maptype", mapType,
"markers", "icon:" + markerUrl + '|' + shadow + latlonMap,
"sensor", "false");
if (waypoints != null) {
params.addAll(waypoints);
}
final HttpResponse httpResponse = Network.getRequest(GOOGLE_STATICMAP_URL, params);
if (httpResponse == null) {
Log.e("StaticMapsProvider.downloadMap: httpResponse is null");
return;
}
if (httpResponse.getStatusLine().getStatusCode() != 200) {
Log.d("StaticMapsProvider.downloadMap: httpResponseCode = " + httpResponse.getStatusLine().getStatusCode());
return;
}
final File file = getMapFile(geocode, prefix, true);
if (LocalStorage.saveEntityToFile(httpResponse, file)) {
// Delete image if it has no contents
final long fileSize = file.length();
if (fileSize < MIN_MAP_IMAGE_BYTES) {
file.delete();
}
}
}
public static void downloadMaps(Geocache cache) {
if ((!Settings.isStoreOfflineMaps() && !Settings.isStoreOfflineWpMaps()) || StringUtils.isBlank(cache.getGeocode())) {
return;
}
int edge = guessMaxDisplaySide();
if (Settings.isStoreOfflineMaps() && cache.getCoords() != null) {
storeCachePreviewMap(cache);
storeCacheStaticMap(cache, edge, false);
}
// clean old and download static maps for waypoints if one is missing
if (Settings.isStoreOfflineWpMaps()) {
for (final Waypoint waypoint : cache.getWaypoints()) {
if (!hasAllStaticMapsForWaypoint(cache.getGeocode(), waypoint)) {
refreshAllWpStaticMaps(cache, edge);
}
}
}
}
/**
* Deletes and download all Waypoints static maps.
*
* @param cache
* The cache instance
* @param edge
* The boundings
*/
private static void refreshAllWpStaticMaps(Geocache cache, int edge) {
LocalStorage.deleteFilesWithPrefix(cache.getGeocode(), MAP_FILENAME_PREFIX + WAYPOINT_PREFIX);
for (Waypoint waypoint : cache.getWaypoints()) {
storeWaypointStaticMap(cache.getGeocode(), edge, waypoint, false);
}
}
public static void storeWaypointStaticMap(Geocache cache, Waypoint waypoint, boolean waitForResult) {
int edge = StaticMapsProvider.guessMaxDisplaySide();
storeWaypointStaticMap(cache.getGeocode(), edge, waypoint, waitForResult);
}
private static void storeWaypointStaticMap(final String geocode, int edge, Waypoint waypoint, final boolean waitForResult) {
if (geocode == null) {
Log.e("storeWaypointStaticMap - missing input parameter geocode");
return;
}
if (waypoint == null) {
Log.e("storeWaypointStaticMap - missing input parameter waypoint");
return;
}
if (waypoint.getCoords() == null) {
return;
}
String wpLatlonMap = waypoint.getCoords().format(Format.LAT_LON_DECDEGREE_COMMA);
String wpMarkerUrl = getWpMarkerUrl(waypoint);
if (!hasAllStaticMapsForWaypoint(geocode, waypoint)) {
// download map images in separate background thread for higher performance
downloadMaps(geocode, wpMarkerUrl, WAYPOINT_PREFIX + waypoint.getId() + '_' + waypoint.getStaticMapsHashcode() + "_", wpLatlonMap, edge, null, waitForResult);
}
}
public static void storeCacheStaticMap(Geocache cache, final boolean waitForResult) {
int edge = guessMaxDisplaySide();
storeCacheStaticMap(cache, edge, waitForResult);
}
private static void storeCacheStaticMap(final Geocache cache, final int edge, final boolean waitForResult) {
final String latlonMap = cache.getCoords().format(Format.LAT_LON_DECDEGREE_COMMA);
final Parameters waypoints = new Parameters();
for (final Waypoint waypoint : cache.getWaypoints()) {
if (waypoint.getCoords() == null) {
continue;
}
final String wpMarkerUrl = getWpMarkerUrl(waypoint);
waypoints.put("markers", "icon:" + wpMarkerUrl + '|' + waypoint.getCoords().format(Format.LAT_LON_DECDEGREE_COMMA));
}
// download map images in separate background thread for higher performance
final String cacheMarkerUrl = getCacheMarkerUrl(cache);
downloadMaps(cache.getGeocode(), cacheMarkerUrl, "", latlonMap, edge, waypoints, waitForResult);
}
public static void storeCachePreviewMap(final Geocache cache) {
final String latlonMap = cache.getCoords().format(Format.LAT_LON_DECDEGREE_COMMA);
final Display display = ((WindowManager) cgeoapplication.getInstance().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
DisplayMetrics metrics = new DisplayMetrics();
display.getMetrics(metrics);
final int width = metrics.widthPixels;
final int height = (int) (110 * metrics.density);
final String markerUrl = MARKERS_URL + "my_location_mdpi.png";
downloadMap(cache.getGeocode(), 15, ROADMAP, markerUrl, PREFIX_PREVIEW, "shadow:false|", latlonMap, width, height, null);
}
private static int guessMaxDisplaySide() {
Point displaySize = Compatibility.getDisplaySize();
return Math.max(displaySize.x, displaySize.y) - 25;
}
private static void downloadMaps(final String geocode, final String markerUrl, final String prefix, final String latlonMap, final int edge,
final Parameters waypoints, boolean waitForResult) {
if (waitForResult) {
downloadDifferentZooms(geocode, markerUrl, prefix, latlonMap, edge, waypoints);
}
else {
final Runnable currentTask = new Runnable() {
@Override
public void run() {
downloadDifferentZooms(geocode, markerUrl, prefix, latlonMap, edge, waypoints);
}
};
try {
pool.add(currentTask, 20, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Log.e("StaticMapsProvider.downloadMaps error adding task", e);
}
}
}
private static String getCacheMarkerUrl(final Geocache cache) {
StringBuilder url = new StringBuilder(MARKERS_URL);
url.append("marker_cache_").append(cache.getType().id);
if (cache.isFound()) {
url.append("_found");
} else if (cache.isDisabled()) {
url.append("_disabled");
}
url.append(".png");
return url.toString();
}
private static String getWpMarkerUrl(final Waypoint waypoint) {
String type = waypoint.getWaypointType() != null ? waypoint.getWaypointType().id : null;
return MARKERS_URL + "marker_waypoint_" + type + ".png";
}
public static void removeWpStaticMaps(Waypoint waypoint, final String geocode) {
if (waypoint == null) {
return;
}
int waypointId = waypoint.getId();
int waypointMapHash = waypoint.getStaticMapsHashcode();
for (int level = 1; level <= 5; level++) {
try {
StaticMapsProvider.getMapFile(geocode, WAYPOINT_PREFIX + waypointId + "_" + waypointMapHash + '_' + level, false).delete();
} catch (Exception e) {
Log.e("StaticMapsProvider.removeWpStaticMaps", e);
}
}
}
/**
* Check if at least one map file exists for the given cache.
*
* @param cache
* @return true if at least one map file exists; false otherwise
*/
public static boolean hasStaticMap(final Geocache cache) {
if (cache == null) {
return false;
}
final String geocode = cache.getGeocode();
if (StringUtils.isBlank(geocode)) {
return false;
}
for (int level = 1; level <= 5; level++) {
File mapFile = StaticMapsProvider.getMapFile(geocode, String.valueOf(level), false);
if (mapFile.exists()) {
return true;
}
}
return false;
}
/**
* Checks if at least one map file exists for the given geocode and waypoint ID.
*
* @param geocode
* @param waypoint
* @return true if at least one map file exists; false otherwise
*/
public static boolean hasStaticMapForWaypoint(String geocode, Waypoint waypoint) {
int waypointId = waypoint.getId();
int waypointMapHash = waypoint.getStaticMapsHashcode();
for (int level = 1; level <= 5; level++) {
File mapFile = StaticMapsProvider.getMapFile(geocode, WAYPOINT_PREFIX + waypointId + "_" + waypointMapHash + "_" + level, false);
if (mapFile.exists()) {
return true;
}
}
return false;
}
/**
* Checks if all map files exist for the given geocode and waypoint ID.
*
* @param geocode
* @param waypoint
* @return true if all map files exist; false otherwise
*/
public static boolean hasAllStaticMapsForWaypoint(String geocode, Waypoint waypoint) {
int waypointId = waypoint.getId();
int waypointMapHash = waypoint.getStaticMapsHashcode();
for (int level = 1; level <= 5; level++) {
File mapFile = StaticMapsProvider.getMapFile(geocode, WAYPOINT_PREFIX + waypointId + "_" + waypointMapHash + "_" + level, false);
boolean mapExists = mapFile.exists();
if (!mapExists) {
return false;
}
}
return true;
}
public static Bitmap getPreviewMap(final String geocode) {
return decodeFile(StaticMapsProvider.getMapFile(geocode, PREFIX_PREVIEW, false));
}
public static Bitmap getWaypointMap(final String geocode, Waypoint waypoint, int level) {
int waypointId = waypoint.getId();
int waypointMapHash = waypoint.getStaticMapsHashcode();
return decodeFile(StaticMapsProvider.getMapFile(geocode, WAYPOINT_PREFIX + waypointId + "_" + waypointMapHash + "_" + level, false));
}
public static Bitmap getCacheMap(final String geocode, int level) {
return decodeFile(StaticMapsProvider.getMapFile(geocode, String.valueOf(level), false));
}
private static Bitmap decodeFile(final File mapFile) {
// avoid exception in system log, if we got nothing back from Google.
if (mapFile.exists()) {
return BitmapFactory.decodeFile(mapFile.getPath());
}
return null;
}
}