Refactor yandex traffic layer as map tile layer

This commit is contained in:
Victor Shcherb 2011-05-27 23:43:00 +02:00
parent 4b93b76341
commit 0b68af3e7d
11 changed files with 118 additions and 274 deletions

View file

@ -17,6 +17,8 @@ public interface ITileSource {
public int getBitDensity();
public boolean isEllipticYTile();
public boolean couldBeDownloadedFromInternet();
}

View file

@ -29,6 +29,7 @@ public class TileSourceManager {
protected String ext;
private int avgSize;
private int bitDensity;
private boolean ellipticYTile;
public TileSourceTemplate(File dir, String name, String urlToLoad) {
this(name, urlToLoad, determineExt(dir,".jpg"), 18, 1, 256, 16, 20000); //$NON-NLS-1$
@ -77,6 +78,16 @@ public class TileSourceManager {
this.avgSize = avgSize;
this.bitDensity = bitDensity;
}
public void setEllipticYTile(boolean ellipticYTile) {
this.ellipticYTile = ellipticYTile;
}
@Override
public boolean isEllipticYTile() {
return ellipticYTile;
}
@Override
public int getBitDensity() {

View file

@ -547,7 +547,7 @@ public class OsmandSettings {
if(dir.exists()){
if(tileName.endsWith(SQLiteTileSource.EXT)){
return new SQLiteTileSource(dir);
} else if (dir.isDirectory()) {
} else if (dir.isDirectory() && !dir.getName().startsWith(".")) {
String url = null;
File readUrl = new File(dir, "url"); //$NON-NLS-1$
try {

View file

@ -646,19 +646,6 @@ public class ResourceManager {
}
public synchronized void updateMapSource(boolean useVectorMap, ITileSource source){
log.info("Clear cache with new source " + cacheOfImages.size()); //$NON-NLS-1$
cacheOfImages.clear();
renderer.clearCache();
if(source == null || source.getBitDensity() == 0){
maxImgCacheSize = 32;
} else {
maxImgCacheSize = Math.max(384 / source.getBitDensity() , 32);
}
}
protected synchronized void clearTiles(){
log.info("Cleaning tiles - size = " + cacheOfImages.size()); //$NON-NLS-1$
ArrayList<String> list = new ArrayList<String>(cacheOfImages.keySet());

View file

@ -250,6 +250,11 @@ public class SQLiteTileSource implements ITileSource {
}
return urlTemplate != null;
}
@Override
public boolean isEllipticYTile() {
return false;
}

View file

@ -532,7 +532,7 @@ public class MapActivity extends Activity implements IMapLocationListener, Senso
if(locationLayer.getLastKnownLocation() != null){
Location lastKnownLocation = locationLayer.getLastKnownLocation();
AnimateDraggingMapThread thread = mapView.getAnimatedDraggingThread();
int fZoom = mapView.getZoom() < 15 ? 15 : mapView.getZoom();
int fZoom = mapView.getZoom() < 14 ? 14 : mapView.getZoom();
thread.startMoving( lastKnownLocation.getLatitude(), lastKnownLocation.getLongitude(), fZoom, false);
}
}
@ -853,7 +853,6 @@ public class MapActivity extends Activity implements IMapLocationListener, Senso
if(oldMap instanceof SQLiteTileSource){
((SQLiteTileSource)oldMap).closeDB();
}
rm.updateMapSource(vectorData, newSource);
mapTileLayer.setMap(newSource);
mapTileLayer.setVisible(!vectorData);
mapVectorLayer.setVisible(vectorData);

View file

@ -88,6 +88,7 @@ public class MapVectorLayer extends BaseMapLayer {
}
if(!isVectorDataVisible() && tileLayer != null){
tileLayer.drawTileMap(canvas, tilesRect);
resourceManager.getRenderer().interruptLoadingMap();
} else {
if (!view.isZooming()){
pixRect.set(0, 0, view.getWidth(), view.getHeight());

View file

@ -8,7 +8,6 @@ import org.apache.commons.logging.Log;
import android.os.SystemClock;
import android.util.FloatMath;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.LinearInterpolator;
@ -23,7 +22,7 @@ public class AnimateDraggingMapThread {
private final static float DRAGGING_ANIMATION_TIME = 1900f;
private final static float ZOOM_ANIMATION_TIME = 800f;
private final static float ZOOM_MOVE_ANIMATION_TIME = 650f;
private final static float MOVE_MOVE_ANIMATION_TIME = 1300f;
private final static float MOVE_MOVE_ANIMATION_TIME = 2000f;
private final static int DEFAULT_SLEEP_TO_REDRAW = 55;
private volatile boolean stopped;
@ -143,6 +142,8 @@ public class AnimateDraggingMapThread {
final float mMoveX = FloatMath.cos(rad) * mStX - FloatMath.sin(rad) * mStY;
final float mMoveY = FloatMath.sin(rad) * mStX + FloatMath.cos(rad) * mStY;
final float animationTime = Math.max(450, (Math.abs(mStX) + Math.abs(mStY)) / 1200f * MOVE_MOVE_ANIMATION_TIME);
startThreadAnimating(new Runnable() {
@Override
@ -153,7 +154,7 @@ public class AnimateDraggingMapThread {
}
if(!stopped){
animatingMoveInThread(mMoveX, mMoveY, MOVE_MOVE_ANIMATION_TIME, notifyListener);
animatingMoveInThread(mMoveX, mMoveY, animationTime, notifyListener);
}
if(!stopped){
tileView.setLatLonAnimate(finalLat, finalLon, notifyListener);
@ -202,6 +203,7 @@ public class AnimateDraggingMapThread {
private void animatingZoomInThread(int zoomStart, int zoomEnd, float animationTime, boolean notifyListener){
float curZoom = zoomStart;
animationTime *= Math.abs(zoomEnd - zoomStart);
// AccelerateInterpolator interpolator = new AccelerateInterpolator(1);
LinearInterpolator interpolator = new LinearInterpolator();

View file

@ -17,7 +17,7 @@ public class MapTileLayer extends BaseMapLayer {
protected final int emptyTileDivisor = 16;
public static final int OVERZOOM_IN = 2;
private ITileSource map = null;
protected ITileSource map = null;
Paint paintBitmap;
@ -26,8 +26,8 @@ public class MapTileLayer extends BaseMapLayer {
protected RectF bitmapToDraw = new RectF();
protected Rect bitmapToZoom = new Rect();
private OsmandMapTileView view;
private ResourceManager resourceManager;
protected OsmandMapTileView view;
protected ResourceManager resourceManager;
private OsmandSettings settings;
private boolean visible = true;
@ -63,7 +63,7 @@ public class MapTileLayer extends BaseMapLayer {
ResourceManager mgr = resourceManager;
int nzoom = view.getZoom();
float tileX = view.getXTile();
float tileY = view.getYTile();
float tileY = map.isEllipticYTile() ? view.getEllipticYTile() : view.getYTile();
float w = view.getCenterPointX();
float h = view.getCenterPointY();
float ftileSize = view.getTileSize();
@ -82,7 +82,13 @@ public class MapTileLayer extends BaseMapLayer {
for (int j = 0; j < height; j++) {
int leftPlusI = (int) FloatMath.floor((float) MapUtils
.getTileNumberX(nzoom, MapUtils.getLongitudeFromTile(nzoom, left + i)));
int topPlusJ = (int) FloatMath.floor((float) MapUtils.getTileNumberY(nzoom, MapUtils.getLatitudeFromTile(nzoom, top + j)));
float topTileY;
if(map.isEllipticYTile()){
topTileY = (float) MapUtils.getTileEllipsoidNumberY(nzoom, MapUtils.getLatitudeFromTile(nzoom, top + j));
} else {
topTileY = (float) MapUtils.getTileNumberY(nzoom, MapUtils.getLatitudeFromTile(nzoom, top + j));
}
int topPlusJ = (int) FloatMath.floor(topTileY);
float x1 = (left + i - tileX) * ftileSize + w;
float y1 = (top + j - tileY) * ftileSize + h;
String ordImgTile = mgr.calculateTileId(map, leftPlusI, topPlusJ, nzoom);

View file

@ -247,6 +247,13 @@ public class OsmandMapTileView extends SurfaceView implements IMapDownloaderCall
return (float) MapUtils.getTileNumberY(getZoom(), latitude);
}
/**
* @return y tile based on (int) zoom
*/
public float getEllipticYTile() {
return (float) MapUtils.getTileEllipsoidNumberY(getZoom(), latitude);
}
public void setZoom(float zoom) {
if (mainLayer != null && zoom <= mainLayer.getMaximumShownMapZoom() && zoom >= mainLayer.getMinimumShownMapZoom()) {
animatedDraggingThread.stopAnimating();
@ -436,19 +443,19 @@ public class OsmandMapTileView extends SurfaceView implements IMapDownloaderCall
try {
boundsRect.set(0, 0, getWidth(), getHeight());
calculateTileRectangle(boundsRect, w, h, tileX, tileY, tilesRect);
int left = (int) FloatMath.floor(tilesRect.left);
int top = (int) FloatMath.floor(tilesRect.top);
latlonRect.top = (float) MapUtils.getLatitudeFromTile(nzoom, tilesRect.top);
latlonRect.left = (float) MapUtils.getLongitudeFromTile(nzoom, tilesRect.left);
latlonRect.bottom = (float) MapUtils.getLatitudeFromTile(nzoom, tilesRect.bottom);
latlonRect.right = (float) MapUtils.getLongitudeFromTile(nzoom, tilesRect.right);
if(nightMode){
canvas.drawARGB(255, 220, 220, 220);
canvas.drawARGB(255, 100, 100, 100);
} else {
canvas.drawARGB(255, 240, 240, 240);
canvas.drawARGB(255, 225, 225, 225);
}
// TODO map
// float ftileSize = getTileSize();
// int left = (int) FloatMath.floor(tilesRect.left);
// int top = (int) FloatMath.floor(tilesRect.top);
// int width = (int) FloatMath.ceil(tilesRect.right - left);
// int height = (int) FloatMath.ceil(tilesRect.bottom - top);
// for (int i = 0; i < width; i++) {

View file

@ -3,290 +3,114 @@ package net.osmand.plus.views;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import net.osmand.Algoritms;
import net.osmand.LogUtil;
import net.osmand.osm.MapUtils;
import net.osmand.map.TileSourceManager.TileSourceTemplate;
import net.osmand.plus.R;
import net.osmand.plus.ResourceManager;
import org.apache.commons.logging.Log;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.BitmapFactory.Options;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.FloatMath;
import android.widget.Toast;
public class YandexTrafficLayer implements OsmandMapLayer {
public class YandexTrafficLayer extends MapTileLayer {
private static final long DELTA = 60000;
private OsmandMapTileView view;
private static final long DELTA = 10 * 60 * 1000;
private long lastTimestampUpdated;
private String mTimestamp = null;
private Rect pixRect;
private RectF tileRect;
private boolean visible = false;
private Handler handler = null;
private static final Log log = LogUtil.getLog(YandexTrafficLayer.class);
private Map<String, Bitmap> tiles = new LinkedHashMap<String, Bitmap>();
private Rect srcImageRect = new Rect(0, 0, 256, 256);
private RectF dstImageRect = new RectF();
protected int cMinX;
protected int cMaxX;
protected int cMinY;
protected int cMaxY;
protected int cZoom;
private Paint paint;
@Override
public void initLayer(OsmandMapTileView view) {
this.view = view;
pixRect = new Rect();
tileRect = new RectF();
if(isVisible()){
startThread();
}
paint = new Paint();
paint.setFilterBitmap(true);
}
public boolean isVisible() {
return visible;
}
private final static Log log = LogUtil.getLog(MapTileLayer.class);
private final static String YANDEX_PREFFIX = ".YandexTraffic_";
private boolean updateThreadRan = false;
public void setVisible(boolean visible) {
if(this.visible != visible){
if(isVisible() != visible){
if(visible){
Toast.makeText(view.getContext(), R.string.thanks_yandex_traffic, Toast.LENGTH_LONG).show();
startThread();
} else {
stopThread();
}
this.visible = visible;
}
super.setVisible(visible);
}
}
private synchronized void startThread(){
if (handler == null) {
new Thread("Yandex traffic") { //$NON-NLS-1$
@Override
public void onDraw(Canvas canvas, RectF latlonRect, RectF tilesRect, boolean nightMode) {
updateTimeStamp();
super.onDraw(canvas, latlonRect, tilesRect, nightMode);
}
protected void updateTimeStamp() {
if ((mTimestamp == null || (System.currentTimeMillis() - lastTimestampUpdated) > DELTA) && !updateThreadRan) {
updateThreadRan = true;
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
handler = new Handler();
Looper.loop();
}
}.start();
}
}
private synchronized void stopThread(){
if (handler != null) {
handler.post(new Runnable() {
@Override
public void run() {
Looper.myLooper().quit();
}
});
handler = null;
}
}
protected void checkedCachedImages(int zoom){
boolean inside = cMinX <= tileRect.left && tileRect.right <= cMaxX && cMinY <= tileRect.top && tileRect.bottom <= cMaxY ;
if (!inside || (cZoom != zoom)) {
cMinX = ((int) tileRect.left);
cMaxX = ((int) tileRect.right ) + 1;
cMinY = ((int) tileRect.top);
cMaxY = ((int) tileRect.bottom ) + 1;
if (cZoom != zoom) {
cZoom = zoom;
clearCache();
}
if (handler != null) {
if (!handler.hasMessages(1)) {
Message msg = Message.obtain(handler, new Runnable() {
@Override
public void run() {
updateCachedImages(cMinX, cMaxX, cMinY, cMaxY, cZoom, 0);
}
});
msg.what = 1;
handler.sendMessage(msg);
}
}
}
}
protected void updateCachedImages(int tMinX, int tMaxX, int tMinY, int tMaxY, int tZoom, int callInd){
try {
updateTimeStamp();
if (mTimestamp != null) {
// clear before to save memory
Set<String> unusedTiles = new HashSet<String>(tiles.keySet());
for (int i = cMinX; i <= cMaxX; i++) {
for (int j = cMinY; j <= cMaxY; j++) {
String tileId = calculateTileId(i, j, cZoom);
unusedTiles.remove(tileId);
try {
updateTimeStampImpl();
} finally {
}
}
for(String s : unusedTiles){
tiles.remove(s);
}
for (int i = tMinX; i <= tMaxX; i++) {
for (int j = tMinY; j <= tMaxY; j++) {
String tileId = calculateTileId(i, j, tZoom);
if(tiles.get(tileId) == null){
downloadTile(tileId, i, j, tZoom, mTimestamp);
if(tMaxX != cMaxX || tMinX!= cMinX || tMaxY != cMaxY || tMinY!= cMinY || tZoom !=cZoom){
return;
}
view.refreshMap();
}
}
}
}
} catch (IOException e) {
log.error("IOException", e); //$NON-NLS-1$
} catch (OutOfMemoryError e) {
tiles.clear();
System.gc();
if(callInd == 0){
updateCachedImages(tMinX, tMaxX, tMinY, tMaxY, tZoom, 1);
}
}, "UpdateYandexTraffic").start();
}
}
private StringBuilder builder = new StringBuilder(50);
protected synchronized String calculateTileId(int tileX, int tileY, int zoom){
builder.setLength(0);
builder.append(zoom).append('/').append(tileX).append('/').append(tileY);
return builder.toString();
}
protected void downloadTile(String tileId, int tileX, int tileY, int zoom, String timeStamp) {
if (zoom > 17) {
return;
}
String u = "http://jgo.maps.yandex.net/tiles?l=trf&x=" + tileX + "&y=" + tileY + "&z=" + zoom + "&tm=" + timeStamp; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$//$NON-NLS-4$
long time = System.currentTimeMillis();
try {
if (log.isDebugEnabled()) {
log.debug("Start loading traffic : " + u); //$NON-NLS-1$
protected void updateTimeStampImpl() {
if (mTimestamp == null || (System.currentTimeMillis() - lastTimestampUpdated) > DELTA) {
log.info("Updating timestamp"); //$NON-NLS-1$
try {
BufferedInputStream in = new BufferedInputStream(new URL("http://jgo.maps.yandex.net/trf/stat.js").openStream(), 1024); //$NON-NLS-1$
ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
BufferedOutputStream out = new BufferedOutputStream(dataStream, 1024);
Algoritms.streamCopy(in, out);
out.flush();
String str = dataStream.toString();
// JSONObject json = new JSONObject(str.replace("YMaps.TrafficLoader.onLoad(\"stat\",", "").replace("});", "}"));
int start = str.indexOf("timestamp:"); //$NON-NLS-1$
start = str.indexOf("\"", start) + 1; //$NON-NLS-1$
int end = str.indexOf("\"", start); //$NON-NLS-1$
// exception case
if (start < 0 || end < 0) {
return;
}
String newTimestamp = str.substring(start, end);
lastTimestampUpdated = System.currentTimeMillis();
Algoritms.closeStream(in);
Algoritms.closeStream(out);
log.info("Timestamp updated"); //$NON-NLS-1$
if (!newTimestamp.equals(mTimestamp)) {
mTimestamp = newTimestamp;
TileSourceTemplate template = new TileSourceTemplate(YANDEX_PREFFIX + mTimestamp,
"http://jgo.maps.yandex.net/tiles?l=trf&x={1}&y={2}&z={3}&tm" + mTimestamp, ".png", 17, 7, 256, 8, 18000);
clearCache();
this.map = template;
}
} catch (IOException e) {
log.info("Exception while updating yandex traffic template", e);
}
InputStream is = new URL(u).openStream();
Options opt = new BitmapFactory.Options();
Bitmap bmp = BitmapFactory.decodeStream(is, null, opt);
is.close();
tiles.put(tileId, bmp);
if (log.isDebugEnabled()) {
log.debug("Loaded traffic : " + tileId + " " + (System.currentTimeMillis() - time) + "ms " + tiles.size()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
} catch (IOException e) {
// File not found very often exception
log.error("Traffic loading failed " + e.getMessage()); //$NON-NLS-1$
}
}
@Override
public void onDraw(Canvas canvas, RectF latLonBounds, RectF tilesRect, boolean nightMode) {
if (visible) {
pixRect.set(0, 0, view.getWidth(), view.getHeight());
float tileY = (float) MapUtils.getTileEllipsoidNumberY(view.getZoom(), view.getLatitude());
view.calculateTileRectangle(pixRect, view.getCenterPointX(), view.getCenterPointY(), view.getXTile(), tileY, tileRect);
double topLat = MapUtils.getLatitudeFromEllipsoidTileY(view.getZoom(), (int) tileRect.top);
double leftLon = MapUtils.getLongitudeFromTile(view.getZoom(), (int) tileRect.left);
int x = view.getMapXForPoint(leftLon);
int y = view.getMapYForPoint(topLat);
checkedCachedImages(view.getZoom());
float right = FloatMath.ceil(tileRect.right);
float bottom = FloatMath.ceil(tileRect.bottom);
for (int i = (int) tileRect.left; i <= right; i++) {
for (int j = (int) tileRect.top; j <= bottom; j++) {
String tId = calculateTileId(i, j, view.getZoom());
if (tiles.get(tId) != null) {
dstImageRect.top = y + (j - (int) tileRect.top) * view.getTileSize();
dstImageRect.left = x + (i - (int) tileRect.left) * view.getTileSize();
dstImageRect.bottom = dstImageRect.top + view.getTileSize();
dstImageRect.right = dstImageRect.left + view.getTileSize();
canvas.drawBitmap(tiles.get(tId), srcImageRect, dstImageRect, paint);
}
}
}
}
}
protected void updateTimeStamp() throws IOException {
if (mTimestamp == null || (System.currentTimeMillis() - lastTimestampUpdated) > DELTA) {
log.info("Updating timestamp"); //$NON-NLS-1$
BufferedInputStream in = new BufferedInputStream(new URL("http://jgo.maps.yandex.net/trf/stat.js").openStream(), 1024); //$NON-NLS-1$
ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
BufferedOutputStream out = new BufferedOutputStream(dataStream, 1024);
Algoritms.streamCopy(in, out);
out.flush();
String str = dataStream.toString();
// JSONObject json = new JSONObject(str.replace("YMaps.TrafficLoader.onLoad(\"stat\",", "").replace("});", "}"));
int start = str.indexOf("timestamp:"); //$NON-NLS-1$
start = str.indexOf("\"", start) + 1; //$NON-NLS-1$
int end = str.indexOf("\"", start); //$NON-NLS-1$
// exception case
if (start < 0 || end < 0) {
return;
}
mTimestamp = str.substring(start, end);
lastTimestampUpdated = System.currentTimeMillis();
Algoritms.closeStream(in);
Algoritms.closeStream(out);
log.info("Timestamp updated"); //$NON-NLS-1$
clearCache();
}
public void destroyLayer() {
super.destroyLayer();
clearCache();
}
private void clearCache() {
tiles.clear();
}
@Override
public boolean drawInScreenPixels() {
return false;
}
@Override
public boolean onLongPressEvent(PointF point) {
return false;
}
@Override
public boolean onTouchEvent(PointF point) {
return false;
}
@Override
public void destroyLayer() {
if(isVisible()){
stopThread();
File dir = view.getSettings().extendOsmandPath(ResourceManager.TILES_PATH);
for (File ds : dir.listFiles()) {
if (ds.isDirectory() && ds.getName().startsWith(YANDEX_PREFFIX)) {
Algoritms.removeAllFiles(ds);
}
}
}
}
}