// License: Public Domain. For details, see LICENSE file.
package livegps;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.Point2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.data.Bounds;
import org.openstreetmap.josm.data.coor.LatLon;
import org.openstreetmap.josm.data.gpx.GpxData;
import org.openstreetmap.josm.data.gpx.GpxTrack;
import org.openstreetmap.josm.data.gpx.WayPoint;
import org.openstreetmap.josm.data.preferences.CachingProperty;
import org.openstreetmap.josm.data.preferences.ColorProperty;
import org.openstreetmap.josm.gui.MapView;
import org.openstreetmap.josm.gui.layer.GpxLayer;
public class LiveGpsLayer extends GpxLayer implements PropertyChangeListener {
public static final String LAYER_NAME = tr("LiveGPS layer");
public static final String C_LIVEGPS_COLOR_POSITION = "color.livegps.position";
public static final String C_LIVEGPS_COLOR_POSITION_ESTIMATE = "color.livegps.position_estimate";
private static final CachingProperty<Color> COLOR_POSITION =
new ColorProperty(C_LIVEGPS_COLOR_POSITION, Color.RED).cached();
private static final CachingProperty<Color> COLOR_POSITION_ESTIMATE =
new ColorProperty(C_LIVEGPS_COLOR_POSITION_ESTIMATE, Color.CYAN).cached();
private static final int DEFAULT_REFRESH_INTERVAL = 250;
private static final int DEFAULT_CENTER_INTERVAL = 5000;
private static final int DEFAULT_CENTER_FACTOR = 80;
private static final String oldC_REFRESH_INTERVAL = "livegps.refreshinterval"; /* in seconds */
private static final String C_REFRESH_INTERVAL = "livegps.refresh_interval_msec"; /* in msec */
private static final String C_CENTER_INTERVAL = "livegps.center_interval_msec"; /* in msec */
private static final String C_CENTER_FACTOR = "livegps.center_factor" /* in percent */;
private static final String C_CURSOR_H = "livegps.cursor_height"; /* in pixels */
private static final String C_CURSOR_W = "livegps.cursor_width"; /* in pixels */
private static final String C_CURSOR_T = "livegps.cursor_thickness"; /* in pixels */
private int refreshInterval;
private int centerInterval;
private double centerFactor;
private long lastRedraw = 0;
private long lastCenter = 0;
LiveGpsData lastData;
LatLon lastPos;
WayPoint lastPoint;
private final AppendableGpxTrackSegment trackSegment;
boolean autocenter;
private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
public LiveGpsLayer(GpxData data) {
super(data, LAYER_NAME);
trackSegment = new AppendableGpxTrackSegment();
Map<String, Object> attr = new HashMap<>();
attr.put("desc", "josm live gps");
GpxTrack trackBeingWritten = new SingleSegmentGpxTrack(trackSegment, attr);
data.tracks.add(trackBeingWritten);
initIntervals();
}
void setCurrentPosition(double lat, double lon) {
LatLon thisPos = new LatLon(lat, lon);
if ((lastPos != null) && (thisPos.equalsEpsilon(lastPos)))
// no change in position
// maybe show a "paused" cursor or some such
return;
lastPos = thisPos;
lastPoint = new WayPoint(thisPos);
lastPoint.attr.put("time", dateFormat.format(new Date()));
trackSegment.addWaypoint(lastPoint);
if (autocenter)
conditionalCenter(thisPos);
}
public void center() {
if (lastPoint != null)
Main.map.mapView.zoomTo(lastPoint.getCoor());
}
public void conditionalCenter(LatLon Pos) {
Point2D P = Main.map.mapView.getPoint2D(Pos);
Rectangle rv = Main.map.mapView.getBounds(null);
Date date = new Date();
long current = date.getTime();
rv.grow(-(int) (rv.getHeight() * centerFactor), -(int) (rv.getWidth() * centerFactor));
if (!rv.contains(P) || (centerInterval > 0 && current - lastCenter >= centerInterval)) {
Main.map.mapView.zoomTo(Pos);
lastCenter = current;
}
}
public void setAutoCenter(boolean ac) {
autocenter = ac;
}
@Override
public void paint(Graphics2D g, MapView mv, Bounds bounds) {
super.paint(g, mv, bounds);
if (lastPoint == null)
return;
Point screen = mv.getPoint(lastPoint.getCoor());
int TriaHeight = Main.pref.getInteger(C_CURSOR_H, 20);
int TriaWidth = Main.pref.getInteger(C_CURSOR_W, 10);
int TriaThick = Main.pref.getInteger(C_CURSOR_T, 4);
/*
* Draw a bold triangle.
* In case of deep zoom draw also a thin DOP oval.
*/
g.setColor(COLOR_POSITION_ESTIMATE.get());
int w, h;
double ppm = 100 / mv.getDist100Pixel(); /* pixels per metre */
w = (int) Math.round(lastData.getEpx() * ppm);
h = (int) Math.round(lastData.getEpy() * ppm);
if (w > TriaWidth || h > TriaWidth) {
int xo, yo;
yo = screen.y - Math.round(h/2);
xo = screen.x - Math.round(w/2);
g.drawOval(xo, yo, w, h);
}
int[] x = new int[4];
int[] y = new int[4];
float course = lastData.getCourse();
float csin = (float) Math.sin(Math.toRadians(course));
float ccos = (float) Math.cos(Math.toRadians(course));
float csin120 = (float) Math.sin(Math.toRadians(course + 120));
float ccos120 = (float) Math.cos(Math.toRadians(course + 120));
float csin240 = (float) Math.sin(Math.toRadians(course + 240));
float ccos240 = (float) Math.cos(Math.toRadians(course + 240));
g.setColor(COLOR_POSITION.get());
for (int i = 0; i < TriaThick; i++, TriaHeight--, TriaWidth--) {
x[0] = screen.x + Math.round(TriaHeight * csin);
y[0] = screen.y - Math.round(TriaHeight * ccos);
x[1] = screen.x + Math.round(TriaWidth * csin120);
y[1] = screen.y - Math.round(TriaWidth * ccos120);
x[2] = screen.x;
y[2] = screen.y;
x[3] = screen.x + Math.round(TriaWidth * csin240);
y[3] = screen.y - Math.round(TriaWidth * ccos240);
g.drawPolygon(x, y, 4);
}
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (!isVisible()) {
return;
}
if ("gpsdata".equals(evt.getPropertyName())) {
lastData = (LiveGpsData) evt.getNewValue();
if (lastData.isFix()) {
setCurrentPosition(lastData.getLatitude(), lastData.getLongitude());
if (allowRedraw())
Main.map.repaint();
}
}
}
/**
* Check, if a redraw is currently allowed.
*
* @return true, if a redraw is permitted, false, if a re-draw
* should be suppressed.
*/
private boolean allowRedraw() {
Date date = new Date();
long current = date.getTime();
if (current - lastRedraw >= refreshInterval) {
lastRedraw = current;
return true;
} else
return false;
}
/**
* Retrieve the refreshInterval and centerInterval from the configuration. Be compatible
* with old version that stored refreshInterval in seconds. If no such configuration key
* exists, it will be initialized here.
*/
private void initIntervals() {
if ((refreshInterval = Main.pref.getInteger(oldC_REFRESH_INTERVAL, 0)) != 0) {
refreshInterval *= 1000;
Main.pref.put(oldC_REFRESH_INTERVAL, null);
} else
refreshInterval = Main.pref.getInteger(C_REFRESH_INTERVAL, DEFAULT_REFRESH_INTERVAL);
centerInterval = Main.pref.getInteger(C_CENTER_INTERVAL, DEFAULT_CENTER_INTERVAL);
centerFactor = Main.pref.getInteger(C_CENTER_FACTOR, DEFAULT_CENTER_FACTOR);
if (centerFactor <= 1 || centerFactor >= 99)
centerFactor = DEFAULT_CENTER_FACTOR;
Main.pref.putInteger(C_REFRESH_INTERVAL, refreshInterval);
Main.pref.putInteger(C_CENTER_INTERVAL, centerInterval);
Main.pref.putInteger(C_CENTER_FACTOR, (int) centerFactor);
/*
* Do one time conversion of factor: user value means "how big is inner rectangle
* comparing to screen in percent", machine value means "what is the shrink ratio
* for each dimension on _both_ sides".
*/
centerFactor = (100 - centerFactor) / 2 / 100;
}
}