package com.geeksville.maps;
import java.io.File;
import java.io.InputStream;
import org.andnav.osm.tileprovider.CloudmadeException;
import org.andnav.osm.tileprovider.IOpenStreetMapTileProviderCallback;
import org.andnav.osm.tileprovider.IRegisterReceiver;
import org.andnav.osm.tileprovider.OpenStreetMapTile;
import org.andnav.osm.tileprovider.OpenStreetMapTileFilesystemProvider;
import org.andnav.osm.tileprovider.constants.OpenStreetMapTileProviderConstants;
import org.andnav.osm.tileprovider.util.CloudmadeUtil;
import org.andnav.osm.util.GeoPoint;
import org.andnav.osm.views.util.IOpenStreetMapRendererInfo;
import org.andnav.osm.views.util.OpenStreetMapRendererFactory;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ProgressBar;
import com.geeksville.gaggle.R;
import com.geeksville.location.LocationUtils;
public class PrefetchMapActivity extends Activity {
private static final String EXTRA_LATITUDE = "latitude";
private static final String EXTRA_LONGITUDE = "longitude";
private static final String EXTRA_ZOOMLEVEL = "zoom";
private static final String EXTRA_TILESOURCE = "tiles";
private static final String TAG = "PrefetchMapActivity";
private GeoPoint center;
private int zoomLevel;
private String source;
private double distMeters = 32186 / 4; // 5 miles for initial testing
private int bytesPerTile = 2591; // FIXME - generate this better
private View editOptions;
private Button startButton, clearButton, cancelButton;
private ProgressBar progress;
private AsyncTask<Void, Integer, Void> background;
private IOpenStreetMapRendererInfo renderer;
/*
* The various tile counts generated from our current zoom level by
* prepareTileCounts()
*/
private int tileStartX;
private int tileStopX;
private int tileStartY;
private int tileStopY;
private int numTilesX;
private int numTilesY;
private int numTiles;
private int numTilesFullWidth;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.prefetch_map);
Bundle extras = getIntent().getExtras();
center = new GeoPoint(extras.getInt(EXTRA_LATITUDE), extras.getInt(EXTRA_LONGITUDE));
zoomLevel = extras.getInt(EXTRA_ZOOMLEVEL);
source = extras.getString(EXTRA_TILESOURCE);
renderer = OpenStreetMapRendererFactory.getRenderer(source);
startButton = (Button) findViewById(R.id.start);
startButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
background = new DownloadTilesTask().execute();
}
});
clearButton = (Button) findViewById(R.id.clear);
clearButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
background = new ClearCacheTask().execute();
}
});
editOptions = findViewById(R.id.editoptions);
progress = (ProgressBar) findViewById(R.id.progress);
progress.setVisibility(View.GONE);
cancelButton = (Button) findViewById(R.id.cancel);
cancelButton.setVisibility(View.GONE);
cancelButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
background.cancel(false);
}
});
}
public static Intent createIntent(Activity context, GeoPoint center, int zoomLevel,
String tilesource) {
Intent i = new Intent(context, PrefetchMapActivity.class);
i.putExtra(EXTRA_LATITUDE, center.getLatitudeE6());
i.putExtra(EXTRA_LONGITUDE, center.getLongitudeE6());
i.putExtra(EXTRA_ZOOMLEVEL, zoomLevel);
i.putExtra(EXTRA_TILESOURCE, tilesource);
return i;
}
private void prepareTileCounts(int zoom) {
numTilesFullWidth = 1 << zoom; // The total # of tiles in X
// & Y for the world
double[] nwPoint = LocationUtils.addDistance(center.getLatitudeE6() * 1e-6, center
.getLongitudeE6() * 1e-6, distMeters, Math.toRadians(45));
double[] sePoint = LocationUtils.addDistance(center.getLatitudeE6() * 1e-6, center
.getLongitudeE6() * 1e-6, distMeters, Math.toRadians(180 + 45));
OpenStreetMapTile start = getMapTileFromCoordinates(renderer, zoom, nwPoint[0],
nwPoint[1]);
OpenStreetMapTile stop = getMapTileFromCoordinates(renderer, zoom, sePoint[0],
sePoint[1]);
tileStartX = start.getX();
tileStopX = (stop.getX() + 1) % numTilesFullWidth;
tileStartY = start.getY();
tileStopY = (stop.getY() + 1) % numTilesFullWidth;
numTilesX = (tileStopX - tileStartX)
+ ((tileStopX <= tileStartX) ? numTilesFullWidth : 0);
numTilesY = (tileStopY - tileStartY)
+ ((tileStopY <= tileStartY) ? numTilesFullWidth : 0);
numTiles = numTilesX * numTilesY;
}
/**
* Count all files in a directory
*
* @param dir
*/
private static int countAll(File dir) {
int count = 1;
if (dir.isDirectory()) {
File[] children = dir.listFiles();
for (File f : children)
count += countAll(f);
}
return count;
}
/**
* For a description see:
*
* @see http://wiki.openstreetmap.org/index.php/Slippy_map_tilenames For a
* code-description see:
* @see http://wiki.openstreetmap.org/index.php/Slippy_map_tilenames#
* compute_bounding_box_for_tile_number
* @param aLat
* latitude to get the {@link OSMTileInfo} for.
* @param aLon
* longitude to get the {@link OSMTileInfo} for.
* @return The {@link OSMTileInfo} providing 'x' 'y' and 'z'(oom) for the
* coordinates passed.
*/
public static OpenStreetMapTile getMapTileFromCoordinates(IOpenStreetMapRendererInfo renderer,
final int zoom, final double aLat, final double aLon) {
final int y = (int) Math.floor((1 - Math.log(Math.tan(aLat * Math.PI / 180) + 1
/ Math.cos(aLat * Math.PI / 180))
/ Math.PI)
/ 2 * (1 << zoom));
final int x = (int) Math.floor((aLon + 180) / 360 * (1 << zoom));
return new OpenStreetMapTile(renderer, zoom, x, y);
}
private abstract class ProgressTask extends AsyncTask<Void, Integer, Void> {
@Override
protected abstract Void doInBackground(Void... params);
/*
* (non-Javadoc)
*
* @see android.os.AsyncTask#onPostExecute(java.lang.Object)
*/
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
progress.setVisibility(View.GONE);
editOptions.setVisibility(View.VISIBLE);
cancelButton.setVisibility(View.GONE);
}
/*
* (non-Javadoc)
*
* @see android.os.AsyncTask#onPreExecute()
*/
@Override
protected void onPreExecute() {
super.onPreExecute();
progress.setVisibility(View.VISIBLE);
editOptions.setVisibility(View.GONE);
cancelButton.setVisibility(View.VISIBLE);
}
/*
* (non-Javadoc)
*
* @see android.os.AsyncTask#onProgressUpdate(Progress[])
*/
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
progress.setIndeterminate(false); // We now know where we are
progress.setProgress(values[0]);
}
}
private class ClearCacheTask extends ProgressTask {
@Override
protected Void doInBackground(Void... params) {
File dir = OpenStreetMapTileProviderConstants.TILE_PATH_BASE;
deleteAll(dir);
return null;
}
/**
* Recursively delete all files in a directory
*
* @param dir
*/
private void deleteAll(File dir) {
if (isCancelled())
return;
if (dir.isDirectory()) {
File[] children = dir.listFiles();
for (File f : children)
deleteAll(f);
}
if (dir.exists()) {
Log.d(TAG, "Deleting " + dir);
dir.delete();
}
}
}
private class DownloadTilesTask extends ProgressTask implements
IOpenStreetMapTileProviderCallback {
// Handler arrivalHandler = new TileArrivalHandler();
IOpenStreetMapRendererInfo renderer;
// OpenStreetMapTileProvider tilesource;
OpenStreetMapTileFilesystemProvider tilesource;
int numReceived = 0, numFailed = 0;
public DownloadTilesTask() {
final IRegisterReceiver registerReceiver = new IRegisterReceiver() {
@Override
public Intent registerReceiver(final BroadcastReceiver aReceiver,
final IntentFilter aFilter) {
return PrefetchMapActivity.this.registerReceiver(aReceiver, aFilter);
}
@Override
public void unregisterReceiver(BroadcastReceiver receiver) {
PrefetchMapActivity.this.unregisterReceiver(receiver);
}
};
tilesource = new OpenStreetMapTileFilesystemProvider(this, registerReceiver);
// tilesource = new OpenStreetMapTileProviderDirect(arrivalHandler,
// CloudmadeUtil.getCloudmadeKey(PrefetchMapActivity.this));
}
/**
* This will be invoked in the GUI thread
*
* @author kevinh
*
*/
private class TileArrivalHandler extends Handler {
@Override
public void handleMessage(final Message msg) {
switch (msg.what) {
case OpenStreetMapTile.MAPTILE_SUCCESS_ID:
break;
}
}
}
@Override
protected Void doInBackground(Void... params) {
// Request a zillion tiles
int numRequested = 0;
int minZoom = zoomLevel, maxZoom = zoomLevel;
for (int zoom = minZoom; zoom <= maxZoom && !isCancelled(); zoom++) {
prepareTileCounts(zoom);
for (int tileY = tileStartY; tileY != tileStopY && !isCancelled(); tileY = (tileY + 1)
% numTilesFullWidth)
for (int tileX = tileStartX; tileX != tileStopX && !isCancelled(); tileX = (tileX + 1)
% numTilesFullWidth) {
final OpenStreetMapTile tile = new OpenStreetMapTile(renderer, zoom,
tileX, tileY);
tilesource.loadMapTileAsync(tile);
numRequested++;
publishProgress(numRequested * 100 / numTiles);
}
}
return null;
}
@Override
public String getCloudmadeKey() throws CloudmadeException {
return CloudmadeUtil.getCloudmadeKey(PrefetchMapActivity.this);
}
@Override
public void mapTileRequestCompleted(OpenStreetMapTile pTile, String aTilePath) {
numReceived++;
}
@Override
public void mapTileRequestCompleted(OpenStreetMapTile aTile) {
numFailed++;
}
@Override
public void mapTileRequestCompleted(OpenStreetMapTile aTile, InputStream aTileInputStream) {
}
@Override
public boolean useDataConnection() {
return true;
}
}
}