change format of tile/ enable downloading from internet tiles (for android)

git-svn-id: https://osmand.googlecode.com/svn/trunk@27 e29c36b1-1cfa-d876-8d93-3434fc2bb7b8
This commit is contained in:
Victor Shcherb 2010-05-01 23:35:00 +00:00
parent 57475433ec
commit d786dcdd60
11 changed files with 283 additions and 80 deletions

View file

@ -1,4 +1,4 @@
.level = ERROR
.level = SEVERE
handlers=java.util.logging.ConsoleHandler
# Set the default logging level for the logger named com.mycompany

View file

@ -1,6 +1,7 @@
package com.osmand;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@ -46,4 +47,17 @@ public class Algoritms {
log.warn("Closing stream warn", e);
}
}
public static void updateAllExistingImgTilesToOsmandFormat(File f){
if(f.isDirectory()){
for(File c : f.listFiles()){
updateAllExistingImgTilesToOsmandFormat(c);
}
} else if(f.getName().endsWith(".png") || f.getName().endsWith(".jpg")){
f.renameTo(new File(f.getAbsolutePath() + ".tile"));
} else if(f.getName().endsWith(".andnav2")) {
f.renameTo(new File(f.getAbsolutePath().substring(0, f.getAbsolutePath().length() - ".andnav2".length()) + ".tile"));
}
}
}

View file

@ -352,8 +352,10 @@ public class DataExtraction implements IMapLocationListener {
final JTextField textField = new JTextField();
final JButton button = new JButton();
button.setText("Set town");
panel.add(textField, BorderLayout.CENTER);
panel.add(button, BorderLayout.EAST);
panel.add(button, BorderLayout.WEST);
content.add(panel, BorderLayout.NORTH);

View file

@ -24,6 +24,7 @@ public abstract class DefaultLauncherConstants {
public static int MAP_divNonLoadedImage = 8;
public static boolean loadMissingImages = true;
public static ITileSource MAP_defaultTileSource = TileSourceManager.getMapnikSource();
public static boolean showGPSCoordinates = true;
// Application constants
@ -34,7 +35,7 @@ public abstract class DefaultLauncherConstants {
// Download manager tile settings
public static int TILE_DOWNLOAD_THREADS = 4;
public static int TILE_DOWNLOAD_SECONTS_TO_WORK = 25;
public static final int TILE_DOWNLOAD_MAX_ERRORS = -1;

View file

@ -196,16 +196,27 @@ public class MapPanel extends JPanel implements IMapDownloaderCallback {
public String getFileForImage (int x, int y, int zoom){
return map.getName() +"/"+zoom+"/"+(x) +"/"+y+".png";
public File getTilesLocation() {
return tilesLocation;
}
public Image getImageFor(int x, int y, int zoom) throws IOException{
public void setTilesLocation(File tilesLocation) {
this.tilesLocation = tilesLocation;
prepareImage();
}
public String getFileForImage (int x, int y, int zoom, String ext){
return map.getName() +"/"+zoom+"/"+(x) +"/"+y+ext+".tile";
}
public Image getImageFor(int x, int y, int zoom, boolean loadIfNeeded) throws IOException{
if(map == null){
return null;
}
String file = getFileForImage(x, y, zoom);
if(!cache.containsKey(file)){
String file = getFileForImage(x, y, zoom, map.getTileFormat());
if(cache.get(file) == null){
File en = new File(tilesLocation, file);
if(cache.size() > 1000){
ArrayList<String> list = new ArrayList<String>(cache.keySet());
@ -213,15 +224,23 @@ public class MapPanel extends JPanel implements IMapDownloaderCallback {
cache.remove(list.get(i));
}
}
if(en.exists() && !downloader.isFileCurrentlyDownloaded(en)){
if (!downloader.isFileCurrentlyDownloaded(en)) {
if (en.exists()) {
long time = System.currentTimeMillis();
try {
cache.put(file, ImageIO.read(en));
if(log.isDebugEnabled()){
log.debug("Loaded file : " + file + " " + -(time -System.currentTimeMillis())+" ms");
if (log.isDebugEnabled()) {
log.debug("Loaded file : " + file + " " + -(time - System.currentTimeMillis()) + " ms");
}
} catch (IIOException e) {
log.error("Eror reading png " + x +" " + y + " zoom : " + zoom, e);
log.error("Eror reading png " + x + " " + y + " zoom : " + zoom, e);
}
}
if(loadIfNeeded && cache.get(file) == null){
String urlToLoad = map.getUrlToLoad(x, y, zoom);
if (urlToLoad != null) {
downloader.requestToDownload(urlToLoad, new DownloadRequest(en, x, y, zoom));
}
}
}
}
@ -239,7 +258,7 @@ public class MapPanel extends JPanel implements IMapDownloaderCallback {
if(request.zoom == this.zoom &&
(i >=0 && i<images.length) && (j>=0 && j< images[i].length)){
try {
images[i][j] = getImageFor(request.xTile, request.yTile, zoom);
images[i][j] = getImageFor(request.xTile, request.yTile, zoom, false);
repaint();
} catch (IOException e) {
log.error("Eror reading png " + request.xTile +" " + request.yTile + " zoom : " + zoom, e);
@ -281,15 +300,7 @@ public class MapPanel extends JPanel implements IMapDownloaderCallback {
for (int j = 0; j < images[i].length; j++) {
int x= (int) xTileLeft + i;
int y = (int) yTileUp + j;
images[i][j] = getImageFor(x, y, zoom);
if(loadNecessaryImages && images[i][j] == null && map != null){
String urlToLoad = map.getUrlToLoad(x, y, zoom);
if(urlToLoad != null){
downloader.requestToDownload(urlToLoad,
new DownloadRequest(new File(tilesLocation, getFileForImage(x, y, zoom)),
x, y, zoom));
}
}
images[i][j] = getImageFor(x, y, zoom, loadNecessaryImages);
}
}
@ -347,6 +358,12 @@ public class MapPanel extends JPanel implements IMapDownloaderCallback {
public void setMapName(ITileSource map){
this.map = map;
if(map.getMaximumZoomSupported() < this.zoom){
zoom = map.getMaximumZoomSupported();
}
if(map.getMinimumZoomSupported() > this.zoom){
zoom = map.getMinimumZoomSupported();
}
prepareImage();
}

View file

@ -22,11 +22,16 @@ public class MapTileDownloader {
private static MapTileDownloader downloader = null;
private static Log log = LogFactory.getLog(MapTileDownloader.class);
private ThreadPoolExecutor threadPoolExecutor;
private IMapDownloaderCallback callback;
private Set<File> currentlyDownloaded;
private int currentErrors = 0;
public static MapTileDownloader getInstance(){
if(downloader == null){
@ -97,6 +102,11 @@ public class MapTileDownloader {
}
public void requestToDownload(String url, DownloadRequest request){
if(DefaultLauncherConstants.TILE_DOWNLOAD_MAX_ERRORS > 0 &&
currentErrors > DefaultLauncherConstants.TILE_DOWNLOAD_MAX_ERRORS){
return;
}
if (!isFileCurrentlyDownloaded(request.fileToSave)) {
threadPoolExecutor.execute(new DownloadMapWorker(url, request));
}
@ -147,8 +157,10 @@ public class MapTileDownloader {
callback.tileDownloaded(downloadUrl, request);
}
} catch (UnknownHostException e) {
currentErrors++;
log.error("UnknownHostException, cannot download tile " + downloadUrl, e);
} catch (IOException e) {
currentErrors++;
log.warn("Cannot download tile : " + downloadUrl, e);
}
}

View file

@ -0,0 +1,14 @@
package com.osmand;
/**
* This class is designed to put all to do's and link them with code.
* The whole methods could be paste or just constants.
* Do not worry to put ugly code here (just a little piece)
*
*/
public class ToDoConstants {
public int CONFIG_COMMONS_LOGGING_IN_ANDROID = 1;
}

View file

@ -72,9 +72,12 @@ public class TileSourceManager {
list.add(getCloudMadeSource());
list.add(getOpenPisteMapSource());
list.add(getGoogleMapsSource());
// TODO ?
// list.add(getGoogleMapsSatelliteSource());
// list.add(getGoogleMapsTerrainSource());
list.add(getGoogleMapsSatelliteSource());
list.add(getGoogleMapsTerrainSource());
list.add(getMicrosoftMapsSource());
list.add(getMicrosoftEarthSource());
list.add(getMicrosoftHybridSource());
}
return list;
@ -112,11 +115,74 @@ public class TileSourceManager {
}
public static TileSourceTemplate getGoogleMapsSatelliteSource(){
return new TileSourceTemplate("GoogleMaps Satellite", "http://khm1.google.com/kh/v=37&x={1}&y={2}&z={0}", ".png", 19, 0, 256, 18000);
return new TileSourceTemplate("GoogleMaps Satellite", "http://khm1.google.com/kh/v=59&x={1}&y={2}&z={0}", ".jpg", 20, 0, 256, 18000);
}
public static TileSourceTemplate getGoogleMapsTerrainSource(){
return new TileSourceTemplate("GoogleMaps Terrain", "http://mt3.google.com/mt/v=w2.87&x={1}&y={2}&z={0}", ".png", 15, 0, 256, 18000);
return new TileSourceTemplate("GoogleMaps Terrain", "http://mt3.google.com/vt/v=w2p.111&hl=en&x={1}&y={2}&z={0}", ".jpg", 15, 0, 256, 18000);
}
public static TileSourceTemplate getMicrosoftMapsSource(){
return new MicrosoftTileSourceTemplate("Microsoft Maps", 'r', "png", ".png", 19, 1, 256, 18000);
}
public static TileSourceTemplate getMicrosoftEarthSource(){
return new MicrosoftTileSourceTemplate("Microsoft Earth", 'a', "jpg", ".jpg", 19, 1, 256, 18000);
}
public static TileSourceTemplate getMicrosoftHybridSource(){
return new MicrosoftTileSourceTemplate("Microsoft Hybrid", 'h', "jpg", ".jpg", 19, 1, 256, 18000);
}
protected static final char[] NUM_CHAR = { '0', '1', '2', '3' };
/**
* See: http://msdn.microsoft.com/en-us/library/bb259689.aspx
* @param zoom
* @param tilex
* @param tiley
* @return quadtree encoded tile number
*
*/
public static String encodeQuadTree(int zoom, int tilex, int tiley) {
char[] tileNum = new char[zoom];
for (int i = zoom - 1; i >= 0; i--) {
// Binary encoding using ones for tilex and twos for tiley. if a bit
// is set in tilex and tiley we get a three.
int num = (tilex % 2) | ((tiley % 2) << 1);
tileNum[i] = NUM_CHAR[num];
tilex >>= 1;
tiley >>= 1;
}
return new String(tileNum);
}
public static class MicrosoftTileSourceTemplate extends TileSourceTemplate {
private final char mapTypeChar;
int serverNum = 0; // 0..3
protected String urlBase = ".ortho.tiles.virtualearth.net/tiles/";
protected String urlAppend = "?g=45";
private final String tileType;
public MicrosoftTileSourceTemplate(String name, char mapTypeChar , String type,
String ext, int maxZoom, int minZoom, int tileSize, int avgSize) {
super(name, null, ext, maxZoom, minZoom, tileSize, avgSize);
this.mapTypeChar = mapTypeChar;
this.tileType = type;
}
@Override
public String getUrlToLoad(int x, int y, int zoom) {
String tileNum = encodeQuadTree(zoom, x, y);
// serverNum = (serverNum + 1) % serverNumMax;
return "http://" + mapTypeChar + serverNum + urlBase + mapTypeChar + tileNum + "."
+ tileType + urlAppend;
}
}
}

View file

@ -20,4 +20,6 @@
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"></uses-permission>
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
</manifest>

View file

@ -40,7 +40,7 @@ public class MapActivity extends Activity implements LocationListener, IMapLocat
setContentView(R.layout.main);
mapView = (OsmandMapTileView) findViewById(R.id.View01);
mapView.setFileWithTiles(new File(Environment.getExternalStorageDirectory(), "osmand/tiles/Mapnik"));
mapView.setFileWithTiles(new File(Environment.getExternalStorageDirectory(), "osmand/tiles/"));
mapView.addMapLocationListener(this);
ZoomControls zoomControls = (ZoomControls) findViewById(R.id.ZoomControls01);

View file

@ -2,11 +2,15 @@ package com.osmand;
import java.io.File;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@ -15,23 +19,22 @@ import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Paint.Style;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import com.osmand.MapTileDownloader.DownloadRequest;
import com.osmand.MapTileDownloader.IMapDownloaderCallback;
import com.osmand.map.ITileSource;
import com.osmand.osm.MapUtils;
public class OsmandMapTileView extends View {
public class OsmandMapTileView extends View implements IMapDownloaderCallback {
protected final int emptyTileDivisor = 4;
protected final int emptyTileDivisor = DefaultLauncherConstants.MAP_divNonLoadedImage;
protected final int maxImgCacheSize = 512;
/**
* tile size of map
*/
private int tileSize = 256;
protected static final Log log = LogFactory.getLog(OsmandMapTileView.class);
/**
* file or directory with tiles
*/
@ -40,20 +43,22 @@ public class OsmandMapTileView extends View {
/**
* zoom level
*/
private int zoom = 15;
private int zoom = DefaultLauncherConstants.MAP_startMapZoom;
// degree measurements (-180, 180)
// долгота
private double longitude = 27.56;
// широта
// degree measurements (90, -90)
private double latitude = 53.9;
private double longitude = DefaultLauncherConstants.MAP_startMapLongitude;
private double latitude = DefaultLauncherConstants.MAP_startMapLatitude;
// name of source map
private ITileSource map = DefaultLauncherConstants.MAP_defaultTileSource;
/**
* listeners
*/
private List<IMapLocationListener> listeners = new ArrayList<IMapLocationListener>();
private MapTileDownloader downloader = MapTileDownloader.getInstance();
// cached data to draw images
private Bitmap[][] images;
@ -63,10 +68,19 @@ public class OsmandMapTileView extends View {
private int yStartingImage = 0;
Map<String, Bitmap> cacheOfImages = new WeakHashMap<String, Bitmap>();
private PointF startDragging = null;
Paint paintGrayFill;
Paint paintWhiteFill;
Paint paintBlack;
final Handler mHandler = new Handler();
// Create runnable for posting
final Runnable invalidateView = new Runnable() {
public void run() {
invalidate();
}
};
public OsmandMapTileView(Context context, AttributeSet attrs) {
@ -94,14 +108,18 @@ public class OsmandMapTileView extends View {
prepareImage();
setClickable(true);
downloader.setDownloaderCallback(this);
}
private PointF startDragging = null;
public int getTileSize() {
return map == null ? 256 : map.getTileSize();
}
public void dragTo(PointF p){
double dx = (startDragging.x - (double)p.x)/tileSize;
double dy = (startDragging.y - (double)p.y)/tileSize;
double dx = (startDragging.x - (double)p.x)/getTileSize();
double dy = (startDragging.y - (double)p.y)/getTileSize();
this.latitude = MapUtils.getLatitudeFromTile(zoom, getYTile() + dy);
this.longitude = MapUtils.getLongitudeFromTile(zoom, getXTile() + dx);
prepareImage();
@ -138,7 +156,7 @@ public class OsmandMapTileView extends View {
}
protected void drawEmptyTile(Canvas cvs, int x, int y){
int tileDiv = tileSize / emptyTileDivisor;
int tileDiv = getTileSize() / emptyTileDivisor;
for (int k1 = 0; k1 < emptyTileDivisor; k1++) {
for (int k2 = 0; k2 < emptyTileDivisor; k2++) {
@ -160,21 +178,41 @@ public class OsmandMapTileView extends View {
for (int i = 0; i < images.length; i++) {
for (int j = 0; j < images[i].length; j++) {
if (images[i][j] == null) {
drawEmptyTile(canvas, i*tileSize+xStartingImage, j * tileSize + yStartingImage);
drawEmptyTile(canvas, i*getTileSize()+xStartingImage, j * getTileSize() + yStartingImage);
} else {
canvas.drawBitmap(images[i][j], i * tileSize + xStartingImage, j * tileSize + yStartingImage, null);
canvas.drawBitmap(images[i][j], i * getTileSize() + xStartingImage, j * getTileSize() + yStartingImage, null);
}
}
}
}
canvas.drawCircle(getWidth()/2, getHeight()/2, 3, paintBlack);
canvas.drawCircle(getWidth()/2, getHeight()/2, 6, paintBlack);
if (DefaultLauncherConstants.showGPSCoordinates) {
canvas.drawText(MessageFormat.format("Lat : {0}, lon : {1}, zoom : {2}", latitude, longitude, zoom), 0, 15, paintBlack);
}
}
public Bitmap getImageFor(int x, int y) {
String file = "/" + zoom + "/" + (x) + "/" + y + ".png";
if (!cacheOfImages.containsKey(file) && fileWithTiles != null) {
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
prepareImage();
super.onSizeChanged(w, h, oldw, oldh);
}
public String getFileForImage (int x, int y, int zoom, String ext){
return map.getName() +"/"+zoom+"/"+(x) +"/"+y+ext+".tile";
}
public Bitmap getImageFor(int x, int y, int zoom, boolean loadIfNeeded) {
if (map == null || fileWithTiles == null || !fileWithTiles.canRead()) {
return null;
}
String file = getFileForImage(x, y, zoom, map.getTileFormat());
if (cacheOfImages.get(file) == null) {
File en = new File(fileWithTiles, file);
if (cacheOfImages.size() > maxImgCacheSize) {
ArrayList<String> list = new ArrayList<String>(cacheOfImages.keySet());
@ -184,39 +222,62 @@ public class OsmandMapTileView extends View {
}
System.gc();
}
if (en.exists() && en.canRead()) {
if (!downloader.isFileCurrentlyDownloaded(en)) {
if (en.exists()) {
long time = System.currentTimeMillis();
cacheOfImages.put(file, BitmapFactory.decodeFile(en.getAbsolutePath()));
} else {
cacheOfImages.put(file, null);
if (log.isDebugEnabled()) {
log.debug("Loaded file : " + file + " " + -(time - System.currentTimeMillis()) + " ms");
}
}
if(loadIfNeeded && cacheOfImages.get(file) == null){
String urlToLoad = map.getUrlToLoad(x, y, zoom);
if (urlToLoad != null) {
downloader.requestToDownload(urlToLoad, new DownloadRequest(en, x, y, zoom));
}
}
}
}
return cacheOfImages.get(file);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
prepareImage();
super.onSizeChanged(w, h, oldw, oldh);
public void tileDownloaded(String dowloadedUrl, DownloadRequest request) {
int tileSize = getTileSize();
double xTileLeft = getXTile() - getWidth() / (2d * tileSize);
double yTileUp = getYTile() - getHeight() / (2d * tileSize);
int i = request.xTile - (int)xTileLeft;
int j = request.yTile - (int)yTileUp;
if(request.zoom == this.zoom &&
(i >= 0 && i < images.length) && (j >= 0 && j < images[i].length)) {
images[i][j] = getImageFor(request.xTile, request.yTile, zoom, false);
mHandler.post(invalidateView);
}
}
// TODO async loading images (show busy cursor while it is loaded)
public void prepareImage() {
double xTileLeft = getXTile() - getWidth() / (2d * tileSize);
double xTileRight = getXTile() + getWidth() / (2d * tileSize);
double yTileUp = getYTile() - getHeight() / (2d * tileSize);
double yTileDown = getYTile() + getHeight() / (2d * tileSize);
public void prepareImage(){
prepareImage(DefaultLauncherConstants.loadMissingImages);
}
xStartingImage = -(int) ((xTileLeft - Math.floor(xTileLeft)) * tileSize);
yStartingImage = -(int) ((yTileUp - Math.floor(yTileUp)) * tileSize);
public void prepareImage(boolean loadNecessaryImages) {
if (loadNecessaryImages) {
downloader.refuseAllPreviousRequests();
}
double xTileLeft = getXTile() - getWidth() / (2d * getTileSize());
double xTileRight = getXTile() + getWidth() / (2d * getTileSize());
double yTileUp = getYTile() - getHeight() / (2d * getTileSize());
double yTileDown = getYTile() + getHeight() / (2d * getTileSize());
xStartingImage = -(int) ((xTileLeft - Math.floor(xTileLeft)) * getTileSize());
yStartingImage = -(int) ((yTileUp - Math.floor(yTileUp)) * getTileSize());
int tileXCount = ((int) xTileRight - (int) xTileLeft + 1);
int tileYCount = ((int) yTileDown - (int) yTileUp + 1);
images = new Bitmap[tileXCount][tileYCount];
for (int i = 0; i < images.length; i++) {
for (int j = 0; j < images[i].length; j++) {
images[i][j] = getImageFor((int) xTileLeft + i, (int) yTileUp + j);
images[i][j] = getImageFor((int) xTileLeft + i, (int) yTileUp + j, zoom, loadNecessaryImages);
}
}
invalidate();
@ -225,14 +286,31 @@ public class OsmandMapTileView extends View {
public void setZoom(int zoom){
if (map == null || (map.getMaximumZoomSupported() >= zoom && map.getMinimumZoomSupported() <= zoom)) {
this.zoom = zoom;
prepareImage();
}
}
public File getFileWithTiles() {
return fileWithTiles;
}
public ITileSource getMap() {
return map;
}
public void setMap(ITileSource map) {
this.map = map;
if(map.getMaximumZoomSupported() < this.zoom){
zoom = map.getMaximumZoomSupported();
}
if(map.getMinimumZoomSupported() > this.zoom){
zoom = map.getMinimumZoomSupported();
}
prepareImage();
}
public void setFileWithTiles(File fileWithTiles) {
this.fileWithTiles = fileWithTiles;
prepareImage();
@ -257,9 +335,6 @@ public class OsmandMapTileView extends View {
return zoom;
}
public int getTileSize() {
return tileSize;
}
public void addMapLocationListener(IMapLocationListener l){