// License: GPL. Copyright (C) 2012 Russell Edwards
package org.openstreetmap.josm.plugins.gpsblam;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.data.coor.CachedLatLon;
import org.openstreetmap.josm.data.coor.EastNorth;
import org.openstreetmap.josm.data.projection.Projection;
import org.openstreetmap.josm.gui.MapView;
class GPSBlamMarker {
private final CachedLatLon mean;
private final CachedLatLon hair1Coord1, hair1Coord2, hair2Coord1, hair2Coord2;
private final CachedLatLon ellipseCoord1, ellipseCoord2, ellipseCoord3; // 1=TL 2=TR 3=BL, where main axis = +R, minor +U
private static final double FAC = 2.45; // 2.45 gives 95% CI for 2D
/** construct a blammarker by analysis of selected GPS points */
GPSBlamMarker(GPSBlamInputData inputData) {
// get mean east, north
double meanEast=0.0, meanNorth=0.0;
for (CachedLatLon cll : inputData) {
EastNorth en = cll.getEastNorth();
meanEast += en.east();
meanNorth += en.north();
}
double n = inputData.size();
meanEast /= n;
meanNorth /= n;
// get covariance matrix
double ca=0.0, cb=0.0, cc=0.0, cd=0.0;
double deast, dnorth;
for (CachedLatLon cll : inputData) {
EastNorth en = cll.getEastNorth();
deast = en.east()-meanEast;
dnorth = en.north()-meanNorth;
ca += deast*deast;
cb += deast*dnorth;
cd += dnorth*dnorth;
}
cc = cb;
ca /= n;
cb /= n;
cc /= n;
cd /= n;
// direction and spread analysis
double t = ca+cd, d = ca*cd-cb*cc; // trace, determinant
double variance1 = 0.5*t + Math.sqrt(0.25*t*t-d); // Eigenvalue 1
double variance2 = 0.5*t - Math.sqrt(0.25*t*t-d); // Eigenvalue 2
double evec1East = variance1-cd, evec1North = cc; // eigenvec1
double evec2East = variance2-cd, evec2North = cc; // eigenvec2
double evec1Fac = Math.sqrt(variance1)/Math.sqrt(evec1East*evec1East+evec1North*evec1North);
double evec2Fac = Math.sqrt(variance2)/Math.sqrt(evec2East*evec2East+evec2North*evec2North);
double sigma1East = evec1East * evec1Fac, sigma1North = evec1North * evec1Fac;
double sigma2East = evec2East * evec2Fac, sigma2North = evec2North * evec2Fac;
// save latlon coords of the mean and the ends of the crosshairs
Projection proj = Main.getProjection();
mean = new CachedLatLon(proj.eastNorth2latlon(new EastNorth(meanEast, meanNorth)));
hair1Coord1 = new CachedLatLon(proj.eastNorth2latlon(
new EastNorth(meanEast-sigma1East*FAC, meanNorth-sigma1North*FAC)));
hair1Coord2 = new CachedLatLon(proj.eastNorth2latlon(
new EastNorth(meanEast+sigma1East*FAC, meanNorth+sigma1North*FAC)));
hair2Coord1 = new CachedLatLon(proj.eastNorth2latlon(
new EastNorth(meanEast-sigma2East*FAC, meanNorth-sigma2North*FAC)));
hair2Coord2 = new CachedLatLon(proj.eastNorth2latlon(
new EastNorth(meanEast+sigma2East*FAC, meanNorth+sigma2North*FAC)));
double efac = FAC/Math.sqrt(inputData.getNDays());
// TopLeft, TopRight, BottomLeft in frame where sigma1=R sigma2=Top
ellipseCoord1 = new CachedLatLon(proj.eastNorth2latlon(
new EastNorth(meanEast+(-sigma1East+sigma2East)*efac, meanNorth+(-sigma1North+sigma2North)*efac))); //
ellipseCoord2 = new CachedLatLon(proj.eastNorth2latlon(
new EastNorth(meanEast+(sigma1East+sigma2East)*efac, meanNorth+(sigma1North+sigma2North)*efac))); //
ellipseCoord3 = new CachedLatLon(proj.eastNorth2latlon(
new EastNorth(meanEast+(-sigma1East-sigma2East)*efac, meanNorth+(-sigma1North-sigma2North)*efac))); //
}
void paint(Graphics2D g, MapView mv) {
g.setColor(Color.GREEN);
g.setPaintMode();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setStroke(new BasicStroke(2.0f));
Point hair1Point1 = mv.getPoint(hair1Coord1.getEastNorth());
Point hair1Point2 = mv.getPoint(hair1Coord2.getEastNorth());
Point hair2Point1 = mv.getPoint(hair2Coord1.getEastNorth());
Point hair2Point2 = mv.getPoint(hair2Coord2.getEastNorth());
g.drawLine(hair1Point1.x, hair1Point1.y, hair1Point2.x, hair1Point2.y);
g.drawLine(hair2Point1.x, hair2Point1.y, hair2Point2.x, hair2Point2.y);
Point2D meanPoint = mv.getPoint2D(mean.getEastNorth());
Point2D ellipsePoint1 = mv.getPoint2D(ellipseCoord1.getEastNorth());
Point2D ellipsePoint2 = mv.getPoint2D(ellipseCoord2.getEastNorth());
Point2D ellipsePoint3 = mv.getPoint2D(ellipseCoord3.getEastNorth());
double majorAxis = ellipsePoint2.distance(ellipsePoint1);
double minorAxis = ellipsePoint3.distance(ellipsePoint1);
double angle = -Math.atan2(-(ellipsePoint2.getY()-ellipsePoint1.getY()), ellipsePoint2.getX()-ellipsePoint1.getX());
Shape e = new Ellipse2D.Double(meanPoint.getX()-majorAxis*0.5, meanPoint.getY()-minorAxis*0.5,
majorAxis, minorAxis);
g.rotate(angle, meanPoint.getX(), meanPoint.getY());
g.draw(e);
g.rotate(-angle, meanPoint.getX(), meanPoint.getY());
}
}