package net.osmand.plus.views;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
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.plus.R;
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 {
private static final long DELTA = 60000;
private OsmandMapTileView view;
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;
}
public void setVisible(boolean visible) {
if(this.visible != visible){
if(visible){
Toast.makeText(view.getContext(), R.string.thanks_yandex_traffic, Toast.LENGTH_LONG).show();
startThread();
} else {
stopThread();
}
this.visible = visible;
}
}
private synchronized void startThread(){
if (handler == null) {
new Thread("Yandex traffic") { //$NON-NLS-1$
@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);
}
}
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);
}
}
}
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$
}
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, 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();
}
}
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();
}
}
}