// Copyright (C) 2010 Aleksandr Dobkin, Michael Choi, and Christopher Mills.
//
// This file is part of BusRadar <https://github.com/orgs/busradar/>.
//
// BusRadar is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// BusRadar is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
package busradar.madison;
import java.util.ArrayList;
import java.util.Arrays;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.view.GestureDetector;
import android.view.MotionEvent;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;
import com.google.android.maps.Projection;
import static busradar.madison.G.*;
class BusOverlay extends com.google.android.maps.Overlay
implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener {
MapView map_view;
final static int touch_allowance = 20;
GeoPoint selection;
int zoom_level = 0;
GestureDetector gesture_detector;
static class BusLocation {
public GeoPoint loc;
public int heading;
}
BusOverlay(MapView map_view) {
gesture_detector = new GestureDetector(this);
gesture_detector.setOnDoubleTapListener(this);
this.map_view = map_view;
}
static final Paint paint = new Paint(); {
paint.setColor(0xffff0000);
}
static final int stroke_width = dp2px(5);
static final int stroke_width_2s = round(stroke_width/2.0);
static final int stroke_width_4s = round(stroke_width/4.0);
static final int stroke_width_8s = round(stroke_width/8.0);
static final Paint line_paint = new Paint() {{
setStrokeWidth(dp2px(5));
setAntiAlias(true);
setColor(0x90ff0000);
setStyle(Paint.Style.STROKE);
setStrokeJoin(Paint.Join.ROUND);
setStrokeCap(Paint.Cap.ROUND);
}};
static final Paint text_paint = new Paint() {{
float size = getTextSize();
setTextSize( metrics.density * size * 1.0f );
setTypeface(Typeface.create(getTypeface(), Typeface.BOLD));
setAntiAlias(true);
setColor(0xff000000);
}};
static final Paint label_paint = new Paint() {{
setAntiAlias(true);
setColor(0xB0ffffff);
}};
static final Paint circle_paint = new Paint() {{
this.setColor(0x90FFFF99);
this.setStrokeWidth(0);
this.setStyle(Style.FILL);
this.setAntiAlias(true);
}};
@Override public final void
draw(Canvas canvas, MapView map, boolean shadow)
{
if (shadow)
return;
Projection proj = map.getProjection();
Point p1 = new Point();
Point p2 = new Point();
Point point = new Point();
int lonspan = map.getLongitudeSpan();
int latspan = map.getLatitudeSpan();
GeoPoint center = map.getMapCenter();
double pixel = (double) map.getLongitudeSpan() / map.getWidth();
int minlon = center.getLongitudeE6() - lonspan / 2;
int maxlon = center.getLongitudeE6() + lonspan / 2;
int minlat = center.getLatitudeE6() - latspan / 2;
int maxlat = center.getLatitudeE6() + latspan / 2;
Point min = new Point(0, 0);
Point max = new Point(map.getWidth()-1, map.getHeight()-1);
//proj.toPixels(new GeoPoint(maxlat, minlon), min);
//proj.toPixels(new GeoPoint(minlat, maxlon), max);
//ProxyGeoPoint pgp = new ProxyGeoPoint(0, 0);
int x;
if (zoom_level != (x = map.getZoomLevel()))
selection = null;
if (selection != null) {
proj.toPixels(selection, point);
//canvas.drawPoint(point.x, point.y, line_paint);
canvas.drawCircle(point.x, point.y, Math.round(touch_allowance*metrics.density), circle_paint);
}
zoom_level = x;
int zoomLevel = map.getZoomLevel();
//if (zoomLevel <= 15)
// line_paint.setStrokeWidth(stroke_width_8s);
//else if (zoomLevel == 16)
// line_paint.setStrokeWidth(stroke_width_4s);
if (zoomLevel <= 15)
line_paint.setStrokeWidth(stroke_width_2s);
else
line_paint.setStrokeWidth(stroke_width);
if (G.active_route >= 0) {
ArrayList<RouteTree.Line> lines = new ArrayList<RouteTree.Line>();
line_paint.setColor(0x90000000 | G.routes[G.active_route].color);
RouteTree tree = G.routes[G.active_route].tree;
tree.find(
round(minlon-10*pixel), round(minlat-10*pixel),
round(maxlon+10*pixel), round(maxlat+10*pixel), lines);
//tree.find(Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, lines);
//System.out.printf("BusRadar: tree find %s, %d, %dn %d total=%d leaves=%d\n",
// minlon-5*pixel, minlat-5*pixel, maxlon+5*pixel, maxlat+5*pixel,
// lines.size(), tree.getNumberOfLeaves());
Path path = new Path();
for (RouteTree.Line line : lines) {
proj.toPixels(new GeoPoint(line.lat1, line.lon1), p1);
proj.toPixels(new GeoPoint(line.lat2, line.lon2), p2);
path.moveTo(p1.x, p1.y);
path.lineTo(p2.x, p2.y);
//canvas.drawLine(p1.x, p1.y, p2.x, p2.y, line_paint);
//canvas.drawCircle(p1.x, p1.y, 3, paint);
//canvas.drawCircle(p2.x, p2.y, 3, paint);
}
canvas.drawPath(path, line_paint);
}
//paint.setColor(0xffff0000);
if (zoomLevel >= 14) {
ArrayList<QuadTree.Element> geopoints = G.stops_tree.get(
round(minlon-dp2px(32)*pixel), round(minlat-dp2px(32)*pixel),
round(maxlon+dp2px(32)*pixel), round(maxlat+dp2px(32)*pixel));
//System.out.printf("draw %d points\n", geopoints.size());
for (QuadTree.Element geopoint : geopoints ) {
if (G.active_route >= 0 && Arrays.binarySearch(geopoint.routes, G.active_route) < 0)
continue;
//pgp.lat = geopoint.lat; pgp.lon = geopoint.lon;
//proj.toPixels(pgp, point);
proj.toPixels(new GeoPoint(geopoint.lat, geopoint.lon), point);
//canvas.drawCircle(point.x, point.y, 3, paint);
Bitmap b;
switch (geopoint.dir) {
case 'N':
b = G.bitmap_stop_north;
if (zoomLevel <= 15)
b = G.bitmap_stop_north_8s;
else if (zoomLevel == 16)
b = G.bitmap_stop_north_4s;
else if (zoomLevel == 17)
b = G.bitmap_stop_north_2s;
canvas.drawBitmap(b, point.x, point.y-b.getHeight()/2, paint);
break;
case 'S':
b = G.bitmap_stop_south;
if (zoomLevel <= 15)
b = G.bitmap_stop_south_8s;
else if (zoomLevel == 16)
b = G.bitmap_stop_south_4s;
else if (zoomLevel == 17)
b = G.bitmap_stop_south_2s;
canvas.drawBitmap(b, point.x-b.getWidth(), point.y-b.getHeight()/2, paint);
break;
case 'E':
b = G.bitmap_stop_east;
if (zoomLevel <= 15)
b = G.bitmap_stop_east_8s;
else if (zoomLevel == 16)
b = G.bitmap_stop_east_4s;
else if (zoomLevel == 17)
b = G.bitmap_stop_east_2s;
canvas.drawBitmap(b, point.x-b.getWidth()/2, point.y, paint);
break;
case 'W':
b = G.bitmap_stop_west;
if (zoomLevel <= 15)
b = G.bitmap_stop_west_8s;
else if (zoomLevel == 16)
b = G.bitmap_stop_west_4s;
else if (zoomLevel == 17)
b = G.bitmap_stop_west_2s;
canvas.drawBitmap(b, point.x-b.getWidth()/2, point.y-b.getHeight(), paint);
break;
default:
b = G.bitmap_stop_nodir;
if (zoomLevel <= 15)
b = G.bitmap_stop_nodir_8s;
else if (zoomLevel == 16)
b = G.bitmap_stop_nodir_4s;
else if (zoomLevel == 17)
b = G.bitmap_stop_west_2s;
canvas.drawBitmap(b, point.x-b.getWidth()/2, point.y-b.getHeight(), paint);
}
}
}
if (G.bus_locs != null) {
int upper_right_count = 0;
int upper_left_count = 0;
int lower_right_count = 0;
int lower_left_count = 0;
for (BusOverlay.BusLocation bus_loc : G.bus_locs) {
proj.toPixels(bus_loc.loc, point);
if (point.x < min.x || point.x > max.x || point.y < min.y || point.y > max.y) {
int heading = bus_loc.heading;
char dir = ' ';
if (heading < (0+22) || bus_loc.heading >= (315+22)) {
dir = '↑';
} else if (heading < (45+22)) {
dir = '↗';
} else if (heading < (90+22)) {
dir = '→';
} else if (heading < (135+22)) {
dir = '↘';
} else if (heading < (180+22)) {
dir = '↓';
} else if (heading < (225+22)) {
dir = '↙';
} else if (heading < (270+22)) {
dir = '←';
} else if (heading < (315+22)) {
dir = '↖';
}
double dist_meters = dist(center.getLatitudeE6()/1.E6, center.getLongitudeE6()/1.E6,
bus_loc.loc.getLatitudeE6()/1.E6, bus_loc.loc.getLongitudeE6()/1.E6);
double dist_miles = dist_meters * 0.000621371192;
String msg = String.format("%.2f mi%c", dist_miles, dir);
//GeoPoint gps = map.getMapCenter(); //G.location_overlay.getMyLocation();
//float[] results = new float[1];
//Location.distanceBetween(gps.getLatitudeE6()/1.E6, gps.getLongitudeE6()/1.E6,
// bus_loc.loc.getLatitudeE6()/1.E6, bus_loc.loc.getLongitudeE6()/1.E6, results);
if (point.x > max.x) {
if (point.y < min.y) {
draw_text_right(canvas, msg, max.x, min.y + dp2px(13+16*upper_right_count++));
}
else if (point.y > max.y - dp2px(13)) {
draw_text_right(canvas, msg, max.x, max.y - dp2px(45+16*lower_right_count++));
}
else {
draw_text_right(canvas, msg, max.x, point.y);
}
}
else if (point.x < min.x) {
if (point.y < min.y) {
draw_text_left(canvas, msg, min.x+dp2px(3), min.y+dp2px(13+16*upper_left_count++));
}
else if (point.y > max.y - dp2px(13)) {
draw_text_left(canvas, msg, min.x+dp2px(3), max.y - dp2px(45+16*lower_left_count++));
}
else {
draw_text_left(canvas, msg, min.x+dp2px(3), point.y);
}
}
else if (point.y > max.y) {
draw_text_left(canvas, msg, point.x, max.y-dp2px(45));
}
else { //if (point.y < min.y) {
draw_text_left(canvas, msg, point.x, min.y+dp2px(13));
}
continue;
}
Bitmap b;
int heading = bus_loc.heading;
if (heading < (0+22) || heading >= (315+22)) {
b = G.bitmap_bus_north;
canvas.drawBitmap(b, point.x, point.y-b.getHeight()/2, paint);
} else if (heading < (45+22)) {
b = G.bitmap_bus_northeast;
canvas.drawBitmap(b, point.x-b.getWidth()/2, point.y-b.getHeight(), paint);
} else if (heading < (90+22)) {
b = G.bitmap_bus_east;
canvas.drawBitmap(b, point.x-b.getWidth()/2, point.y-b.getHeight(), paint);
} else if (heading < (135+22)) {
b = G.bitmap_bus_southeast;
canvas.drawBitmap(b, point.x-b.getWidth()/2, point.y-b.getHeight(), paint);
} else if (heading < (180+22)) {
b = G.bitmap_bus_south;
canvas.drawBitmap(b, point.x-b.getWidth(), point.y-b.getHeight()/2, paint);
} else if (heading < (225+22)) {
b = G.bitmap_bus_southwest;
canvas.drawBitmap(b, point.x-b.getWidth()/2, point.y-b.getHeight(), paint);
} else if (heading < (270+22)) {
b = G.bitmap_bus_west;
canvas.drawBitmap(b, point.x-b.getWidth()/2, point.y-b.getHeight(), paint);
} else if (heading < (315+22)) {
b = G.bitmap_bus_southwest;
canvas.drawBitmap(b, point.x-b.getWidth()/2, point.y-b.getHeight(), paint);
}
//canvas.drawCircle(point.x, point.y, 3, paint);
}
}
}
@Override
public boolean onTap(GeoPoint p, MapView map) {
//ProxyGeoPoint pgp = new ProxyGeoPoint(0, 0);
if (map.getZoomLevel() < 15)
return false;
selection = p;
int lat = p.getLatitudeE6();
int lon = p.getLongitudeE6();
double pixel = (double) map.getLongitudeSpan() / map.getWidth();
ArrayList<QuadTree.Element> geopoints = G.stops_tree.get(
round(lon-pixel*dp2px(100)), round(lat-pixel*dp2px(100)),
round(lon+pixel*dp2px(100)), round(lat+pixel*dp2px(100)));
Projection proj = map.getProjection();
Point point = new Point();
Point touch = new Point();
proj.toPixels(p, touch);
final ArrayList<QuadTree.Element> touched = new ArrayList<QuadTree.Element>();
Bitmap b;
for(QuadTree.Element e: geopoints) {
if (G.active_route >= 0 && Arrays.binarySearch(e.routes, G.active_route) < 0)
continue;
//pgp.lat = e.lat; pgp.lon = e.lon;
proj.toPixels(new GeoPoint(e.lat, e.lon), point);
switch (e.dir) {
case 'N':
b = G.bitmap_stop_north;
point.x += b.getWidth()/2;
break;
case 'S':
b = G.bitmap_stop_south;
point.x -= b.getWidth()/2;
break;
case 'E':
b = G.bitmap_stop_east;
point.y += b.getHeight()/2;
break;
case 'W':
b = G.bitmap_stop_west;
point.y -= b.getHeight()/2;
break;
default:
b = G.bitmap_stop_nodir;
point.y -= b.getHeight()/2;
}
double dist = Math.sqrt(Math.pow(point.x-touch.x, 2) + Math.pow(point.y-touch.y, 2));
if (dist < touch_allowance * metrics.density) {
touched.add(e);
}
}
if (touched.size() == 1) {
int latx = touched.get(0).lat;
int lonx = touched.get(0).lon;
StopDialog d = new StopDialog(G.activity, touched.get(0).id, latx , lonx);
d.show();
return true;
}
else if (touched.size() > 1) {
if (map.getZoomLevel() == 19) {
String[] names = new String[touched.size()];
for (int i = 0; i < names.length; i++)
names[i] = DB.getStopInfo(touched.get(i).id).name;
new AlertDialog.Builder(G.activity, android.R.style.Theme_DeviceDefault_Dialog)
.setTitle("Choose a stop")
.setItems(names,
new DialogInterface.OnClickListener() {
public void
onClick(DialogInterface dialog, int which)
{
new StopDialog(G.activity, touched.get(which).id,
touched.get(which).lat, touched.get(which).lon)
.show();
}
})
.create().show();
}
else {
proj.toPixels(p, touch);
map.getController().zoomInFixing(touch.x, touch.y);
}
return true;
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent e, MapView m) {
gesture_detector.onTouchEvent(e);
return false;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
if (selection != null) {
Projection proj = map_view.getProjection();
Point touch = new Point();
proj.toPixels(selection, touch);
map_view.getController().zoomInFixing(touch.x, touch.y);
} else {
map_view.getController().zoomIn();
}
return true;
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
return false;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
return false;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float f1, float f2) {
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float f1, float f2) {
return false;
}
@Override
public void onLongPress(MotionEvent e1) {
}
@Override
public void onShowPress(MotionEvent e1) {
}
@Override
public boolean onSingleTapUp(MotionEvent e1) {
return false;
}
@Override
public boolean onDown(MotionEvent e1) {
return false;
}
final static void
draw_text_right(Canvas canvas, String msg, float x, float y)
{
int len = round(text_paint.measureText(msg));
RectF rect = new RectF(x-len-dp2px(3), y-dp2px(13), x+dp2px(3), y+dp2px(3));
canvas.drawRoundRect(rect, dp2px(3), dp2px(3), label_paint);
canvas.drawText(msg, x-len, y, text_paint);
}
final static void
draw_text_left(Canvas canvas, String msg, float x, float y)
{
int len = round(text_paint.measureText(msg));
RectF rect = new RectF(x-dp2px(3), y-dp2px(13), x+len+dp2px(3), y+dp2px(3));
canvas.drawRoundRect(rect, dp2px(3), dp2px(3), label_paint);
canvas.drawText(msg, x, y, text_paint);
}
final static double
dist(double lat_a, double lng_a, double lat_b, double lng_b) {
double a1 = lat_a / (180f/3.14159265358979);
double a2 = lng_a / (180f/3.14159265358979);
double b1 = lat_b / (180f/3.14159265358979);
double b2 = lng_b / (180f/3.14159265358979);
double t1 = Math.cos(a1)*Math.cos(a2)*Math.cos(b1)*Math.cos(b2);
double t2 = Math.cos(a1)*Math.sin(a2)*Math.cos(b1)*Math.sin(b2);
double t3 = Math.sin(a1)*Math.sin(b1);
double tt = Math.acos(t1 + t2 + t3);
return 6371000*tt;
}
}