/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.mozstumbler.client.mapview;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.SystemClock;
import org.mozilla.mozstumbler.client.ObservedLocationsReceiver;
import org.mozilla.mozstumbler.service.core.logging.ClientLog;
import org.mozilla.mozstumbler.svclocator.services.log.LoggerUtil;
import org.mozilla.osmdroid.views.MapView;
import org.mozilla.osmdroid.views.Projection;
import org.mozilla.osmdroid.views.overlay.Overlay;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
class ObservationPointsOverlay extends Overlay {
private static final String LOG_TAG = LoggerUtil.makeLogTag(ObservationPointsOverlay.class);
private static final long DRAW_TIME_MILLIS = 30; // Abort drawing after this time
private static final int TIME_CHECK_MULTIPLE = 100; // Check the time after drawing this many
final DevicePixelConverter mConvertPx;
private final Paint mRedPaint = new Paint();
private final Paint mGreenPaint = new Paint();
private final Paint mCellPaint = new Paint();
private final Paint mWifiPaint = new Paint();
private final Paint mBlackStrokePaint = new Paint();
private final Paint mBlackStrokePaintThin = new Paint();
private final Paint mBlackMLSLinePaint = new Paint();
private final int mSize3px;
public boolean mOnMapShowMLS;
LinkedHashMap<Integer, ObservationPoint> mHashedGrid;
private Point mHashedGridAnchorPoint = new Point(0, 0);
ObservationPointsOverlay(Context ctx) {
super(ctx);
mConvertPx = new DevicePixelConverter(ctx);
mGreenPaint.setColor(Color.GREEN);
mGreenPaint.setStyle(Paint.Style.FILL);
mRedPaint.setColor(Color.RED);
mRedPaint.setStyle(Paint.Style.FILL);
mBlackStrokePaint.setColor(Color.BLACK);
mBlackStrokePaint.setStyle(Paint.Style.STROKE);
mBlackStrokePaint.setStrokeWidth(mConvertPx.pxToDp(2));
mBlackMLSLinePaint.setARGB(160, 0, 0, 0);
mBlackMLSLinePaint.setStyle(Paint.Style.STROKE);
mBlackMLSLinePaint.setStrokeWidth(mConvertPx.pxToDp(1));
mBlackStrokePaintThin.setColor(Color.BLACK);
mBlackStrokePaintThin.setStyle(Paint.Style.STROKE);
mCellPaint.setColor(Color.BLUE);
mCellPaint.setStyle(Paint.Style.STROKE);
mCellPaint.setStrokeWidth(mConvertPx.pxToDp(2.5f));
mWifiPaint.setARGB(255, 160, 0, 180);
mWifiPaint.setStyle(Paint.Style.STROKE);
mWifiPaint.setStrokeWidth(mConvertPx.pxToDp(2.5f));
mSize3px = mConvertPx.pxToDp(3f);
}
void update(ObservationPoint obsPoint, MapView mapView, boolean isMlsPointUpdate) {
if ((isMlsPointUpdate && obsPoint.pointMLS == null) ||
(!isMlsPointUpdate && obsPoint.pointGPS == null)) {
ClientLog.w(LOG_TAG, "Caller error: geoPoint is null");
return;
}
if (!isMlsPointUpdate) {
final Projection pj = mapView.getProjection();
final Point point = pj.toPixels(obsPoint.pointGPS.getLatitude(), obsPoint.pointGPS.getLongitude(), null);
// add to hashed grid
addToGridHash(obsPoint, point, new Point(mapView.getScrollX(), mapView.getScrollY()));
}
mapView.postInvalidate();
}
private void drawDot(Canvas c, Point p, float radiusInnerRing, Paint fillPaint, Paint strokePaint) {
c.drawCircle(p.x, p.y, radiusInnerRing, fillPaint);
c.drawCircle(p.x, p.y, radiusInnerRing, strokePaint);
}
private void drawCellScan(Canvas c, Point p) {
final int size = mSize3px;
RectF r = new RectF(p.x - size, p.y - size, p.x + size, p.y + size);
c.drawRoundRect(r, 1f, 1f, mCellPaint);
}
private void drawWifiScan(Canvas c, Point p) {
final int size = mSize3px;
c.drawCircle(p.x, p.y, size, mWifiPaint);
}
private void addToGridHash(ObservationPoint obsPoint, Point screenPoint, Point currentScroll) {
if (obsPoint.mCellCount == 0 && obsPoint.mWifiCount == 0) {
return;
}
int hash = hashedGridPoint(screenPoint.x, screenPoint.y, currentScroll.x, currentScroll.y);
ObservationPoint gp = mHashedGrid.get(hash);
if (gp == null || toTypeBitField(obsPoint) > toTypeBitField(gp)) {
mHashedGrid.put(hash, obsPoint);
}
}
public void zoomChanged(MapView mapView) {
mHashedGrid = new LinkedHashMap<Integer, ObservationPoint>();
mHashedGridAnchorPoint = new Point(mapView.getScrollX(), mapView.getScrollY());
final Projection pj = mapView.getProjection();
List<ObservationPoint> points = ObservedLocationsReceiver.getInstance().getObservationPoints_callerMustLock();
synchronized (points) {
final Iterator<ObservationPoint> i = points.iterator();
final Point gps = new Point();
ObservationPoint point;
Point zero = new Point(0, 0);
while (i.hasNext()) {
point = i.next();
pj.toPixels(point.pointGPS.getLatitude(), point.pointGPS.getLongitude(), gps);
addToGridHash(point, gps, zero);
}
}
}
private int hashedGridPoint(int x, int y, int scrollX, int scrollY) {
x = (int) Math.round((x - mHashedGridAnchorPoint.x + scrollX) / (mSize3px * 2.0));
y = (int) Math.round((y - mHashedGridAnchorPoint.y + scrollY) / (mSize3px * 2.0));
return x * 10000 + y;
}
private int toTypeBitField(ObservationPoint point) {
int wifiBit = point.mWifiCount > 0 ? 2 : 0;
int cellBit = point.mCellCount > 0 ? 1 : 0;
return cellBit | wifiBit;
}
protected void draw(Canvas c, MapView osmv, boolean shadow) {
final long endTime = SystemClock.uptimeMillis() + DRAW_TIME_MILLIS;
List<ObservationPoint> points = ObservedLocationsReceiver.getInstance().getObservationPoints_callerMustLock();
synchronized (points) {
if (shadow || points.size() < 1) {
return;
}
final Projection pj = osmv.getProjection();
final float radiusInnerRing = mSize3px;
int count = 0;
// The overlay occupies the entire screen, so this returns the screen (0,0,w,h).
Rect clip = c.getClipBounds();
if (mHashedGrid == null || mHashedGrid.size() < 1) {
return;
}
final Point gps = new Point();
ArrayList<HashMap.Entry<Integer, ObservationPoint>> arrayList =
new ArrayList<HashMap.Entry<Integer, ObservationPoint>>(mHashedGrid.entrySet());
ListIterator<HashMap.Entry<Integer, ObservationPoint>> revIterator =
arrayList.listIterator(mHashedGrid.size());
while (revIterator.hasPrevious()) {
HashMap.Entry<Integer, ObservationPoint> entry = revIterator.previous();
ObservationPoint point = entry.getValue();
pj.toPixels(point.pointGPS.getLatitude(), point.pointGPS.getLongitude(), gps);
if (!clip.contains(gps.x, gps.y)) {
continue;
}
boolean hasWifiScan = point.mWifiCount > 0;
boolean hasCellScan = point.mCellCount > 0;
if (hasCellScan && hasWifiScan) {
drawDot(c, gps, radiusInnerRing, mGreenPaint, mBlackStrokePaint);
} else if (hasWifiScan) {
drawWifiScan(c, gps);
} else if (hasCellScan) {
drawCellScan(c, gps);
}
if ((++count % TIME_CHECK_MULTIPLE == 0) && (SystemClock.uptimeMillis() > endTime)) {
break;
}
}
if (!mOnMapShowMLS) {
return;
}
// Draw as a 2nd layer over the observation points
final Point mls = new Point();
revIterator = arrayList.listIterator(mHashedGrid.size());
while (revIterator.hasPrevious()) {
HashMap.Entry<Integer, ObservationPoint> entry = revIterator.previous();
ObservationPoint point = entry.getValue();
if (point.pointMLS != null) {
pj.toPixels(point.pointGPS.getLatitude(), point.pointGPS.getLongitude(), gps);
pj.toPixels(point.pointMLS.getLatitude(), point.pointMLS.getLongitude(), mls);
drawDot(c, mls, radiusInnerRing - 1, mRedPaint, mBlackStrokePaintThin);
c.drawLine(gps.x, gps.y, mls.x, mls.y, mBlackMLSLinePaint);
}
if ((++count % TIME_CHECK_MULTIPLE == 0) && (SystemClock.uptimeMillis() > endTime)) {
break;
}
}
}
}
}