package net.osmand.plus.views;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import net.osmand.plus.OsmandSettings;
import pl.edu.agh.logic.TrafficDataListener;
import pl.edu.agh.logic.TrafficDataProvider;
import pl.edu.agh.logic.TrafficDataProvider.Status;
import pl.edu.agh.model.RectD;
import pl.edu.agh.model.SimpleLocationInfo;
import pl.edu.agh.model.TrafficData;
import pl.edu.agh.model.TrafficInfo;
import pl.edu.agh.model.Vector;
import pl.edu.agh.utils.GeometryUtils;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Cap;
import android.graphics.Paint.Join;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.RectF;
import android.os.Handler;
import android.widget.Toast;
public class AGHTrafficLayer implements OsmandMapLayer, TrafficDataListener {
private static final int MIN_STROKE_WIDTH = 6;
private static final int MAX_STROKE_WIDTH = 18;
private static final int ZOOM_FOR_MAX_STROKE_WIDTH = 18;
private static final double DRAWING_AREA_BOUND_SIZE = 0.02;
private static final Map<TrafficDataProvider.Status, String> STATUS_MAPPING = new HashMap<TrafficDataProvider.Status, String>() {
{
put(Status.FETCHING, "Fetching traffic data...");
put(Status.ERROR, "Error during fetching traffic data.");
put(Status.COMPLETED, "Traffic data has been fetched.");
}
};
private static final SortedMap<Double, Integer> SPEED_MAPPING = new TreeMap<Double, Integer>() {
{
put(4.0, 0x970000);
put(8.25, Color.RED);
put(12.5, 0xFF7F27);
put(16.75, 0xFFEF00);
put(Double.MAX_VALUE, 0x00DA00);
}
};
private OsmandMapTileView view;
private Handler handler;
private TrafficDataProvider trafficDataProvider;
private void makeToastInUIThread(final String description) {
if (!OsmandSettings.getMapActivityEnabled(OsmandSettings.getPrefs(view.getContext()))) {
return;
}
handler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(view.getContext(), description, Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void initLayer(OsmandMapTileView view) {
this.trafficDataProvider = view.getApplication().getTrafficDataProvider();
this.view = view;
this.handler = new Handler();
trafficDataProvider.registerListener(this);
}
@Override
public void onDraw(Canvas canvas, RectF latlonRect, boolean nightMode) {
if (view.getZoom() < TrafficDataProvider.MIN_ZOOM) {
return;
}
TrafficData trafficData = trafficDataProvider.getTrafficData(
new SimpleLocationInfo(view.getLongitude(), view.getLatitude()), view.getZoom(), latlonRect);
if (trafficData == null) {
return;
}
RectD extendedBound = new RectD(latlonRect).expandByDistance(DRAWING_AREA_BOUND_SIZE);
for (TrafficInfo trafficInfo : trafficData.getTrafficInfos()) {
if (GeometryUtils.existsPointInsideRect(trafficInfo.getWay(), extendedBound)) {
drawTrafficInfo(trafficInfo, canvas);
}
}
}
private void drawTrafficInfo(TrafficInfo trafficInfo, Canvas canvas) {
if (trafficInfo.isOneWay()) {
drawOneWayStreet(trafficInfo, canvas);
} else {
drawTwoWayStreet(trafficInfo, canvas);
}
}
private void drawTwoWayStreet(TrafficInfo trafficInfo, Canvas canvas) {
List<SimpleLocationInfo> way = trafficInfo.getWay();
for (int i = 1; i < way.size(); i++) {
drawSection(way.get(i - 1), way.get(i), trafficInfo, canvas);
}
}
private void drawSection(SimpleLocationInfo begin, SimpleLocationInfo end, TrafficInfo trafficInfo, Canvas canvas) {
int strokeWidth = calculateStrokeWidth();
Point sectionBegin = new Point(view.getMapXForPoint(begin.getLongitude()), view.getMapYForPoint(begin
.getLatitude()));
Point sectionEnd = new Point(view.getMapXForPoint(end.getLongitude()), view.getMapYForPoint(end.getLatitude()));
if (trafficInfo.getDirectWaySpeed() != null) {
Vector directWay = new Vector(sectionBegin, sectionEnd).translatePerpendicularly(strokeWidth / 2.0,
Vector.Direction.RIGHT);
drawSectionWithSpeed(directWay, trafficInfo.getDirectWaySpeed(), canvas);
}
if (trafficInfo.getReverseWaySpeed() != null) {
Vector reverseWay = new Vector(sectionBegin, sectionEnd).translatePerpendicularly(strokeWidth / 2.0,
Vector.Direction.LEFT);
drawSectionWithSpeed(reverseWay, trafficInfo.getReverseWaySpeed(), canvas);
}
}
private void drawSectionWithSpeed(Vector section, double speed, Canvas canvas) {
canvas.drawLine(section.getBegin().x, section.getBegin().y, section.getEnd().x, section.getEnd().y,
getPaintForSpeed(speed));
}
private void drawOneWayStreet(TrafficInfo trafficInfo, Canvas canvas) {
Path path = createPathFromPoints(trafficInfo.getWay());
Paint paint = getPaintForSpeed(trafficInfo.getDirectWaySpeed());
canvas.drawPath(path, paint);
}
private Paint getPaintForSpeed(Double speed) {
Paint paint = new Paint();
paint.setColor(getColorForSpeed(speed));
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(calculateStrokeWidth());
paint.setAntiAlias(true);
paint.setStrokeCap(Cap.BUTT);
paint.setStrokeJoin(Join.BEVEL);
paint.setAlpha(160);
return paint;
}
private int calculateStrokeWidth() {
return Math.max(
(int) Math.round(MAX_STROKE_WIDTH
/ (Math.pow(1.5,
ZOOM_FOR_MAX_STROKE_WIDTH - Math.min(view.getZoom(), ZOOM_FOR_MAX_STROKE_WIDTH)))),
MIN_STROKE_WIDTH);
}
private int getColorForSpeed(Double directWaySpeed) {
Map.Entry<Double, Integer> entry = null;
Iterator<Map.Entry<Double, Integer>> it = SPEED_MAPPING.entrySet().iterator();
while (it.hasNext()) {
entry = (Map.Entry<Double, Integer>) it.next();
if (entry.getKey() > directWaySpeed)
break;
}
return entry.getValue();
}
private Path createPathFromPoints(List<SimpleLocationInfo> way) {
Path path = new Path();
int x = view.getMapXForPoint(way.get(0).getLongitude());
int y = view.getMapYForPoint(way.get(0).getLatitude());
path.moveTo(x, y);
for (int i = 1; i < way.size(); i++) {
SimpleLocationInfo point = way.get(i);
x = view.getMapXForPoint(point.getLongitude());
y = view.getMapYForPoint(point.getLatitude());
path.lineTo(x, y);
}
return path;
}
@Override
public void destroyLayer() {
trafficDataProvider.unregisterListener(this);
}
@Override
public boolean onTouchEvent(PointF point) {
return false;
}
@Override
public boolean onLongPressEvent(PointF point) {
return false;
}
@Override
public boolean drawInScreenPixels() {
return false;
}
@Override
public void onDataProvided(TrafficData trafficData) {
view.refreshMap();
}
@Override
public void onStatusChanged(Status status) {
if (status == Status.ERROR)
makeToastInUIThread(STATUS_MAPPING.get(status));
}
}