package au.gov.amsa.geo.model; import org.apache.log4j.Logger; import au.gov.amsa.util.navigation.Position; import au.gov.amsa.util.navigation.Position.LongitudePair; public class GridTraversor { private static Logger log = Logger.getLogger(GridTraversor.class); private static final double SMALL_INCREMENT_DEGREES = 0.0000001; private final Options options; public GridTraversor(Options options) { this.options = options; } private static double bearingDegrees(Position a, Position b) { double val = a.getBearingDegrees(b); if (val == 0 && a.getLat() == b.getLat()) { if (b.getLon() > a.getLon()) val = 90; else if (b.getLon() < a.getLon()) val = 270; else // same point val = 0; } return val; } public Position nextPoint(Position a, Position b) { if (a.equals(b)) return b; double bearingDegrees = bearingDegrees(a, b); // if on left edge heading left or top edge heading up then nudge into // next cell // TODO is this required? Cell cell1 = Cell.cellAt(a.getLat(), a.getLon(), options).get(); if (bearingDegrees > 180 && a.getLon() == cell1.leftEdgeLongitude(options)) { a = new Position(a.getLat(), a.getLon() - SMALL_INCREMENT_DEGREES); cell1 = Cell.cellAt(a.getLat(), a.getLon(), options).get(); bearingDegrees = bearingDegrees(a, b); } else if ((bearingDegrees > 270 || bearingDegrees < 90) && a.getLat() == cell1.topEdgeLatitude(options)) { a = new Position(a.getLat() + SMALL_INCREMENT_DEGREES, a.getLon()); cell1 = Cell.cellAt(a.getLat(), a.getLon(), options).get(); bearingDegrees = bearingDegrees(a, b); } Cell cell2 = Cell.cellAt(b.getLat(), b.getLon(), options).get(); if (cell1.equals(cell2)) return b; else { double targetLon = getTargetLon(cell1, bearingDegrees); // check if crosses target lat based on bearing double leftLon = cell1.leftEdgeLongitude(options); double rightLon = cell1.rightEdgeLongitude(options); double targetLat = getTargetLat(cell1, bearingDegrees); if (bearingDegrees == 0 || bearingDegrees == 180) return Position.create(targetLat, a.getLon()); { Position result = nextPointCrossingLatitude(a, b, leftLon, rightLon, targetLat, bearingDegrees); if (result != null) return result; } { double otherLat = getNonTargetLat(cell1, bearingDegrees); Position result = nextPointCrossingLatitude(a, b, leftLon, rightLon, otherLat, bearingDegrees); if (result != null) return result; } // see if crosses left or right edge Double latCrossing = a.getLatitudeOnGreatCircle(b, targetLon); if (latCrossing != null) { double topLat = cell1.topEdgeLatitude(options); double bottomLat = cell1.bottomEdgeLatitude(options); if (topLat >= latCrossing && bottomLat <= latCrossing) return Position.create(latCrossing, targetLon); } log.warn("unexpected! Could not calculate next point for segment between\n a = " + a + " b = " + b + "\noptions=" + options); return b; } } private double getNonTargetLat(Cell cell, double bearingDegrees) { if (bearingDegrees >= 270 || bearingDegrees < 90) return cell.bottomEdgeLatitude(options); else return cell.topEdgeLatitude(options); } private Position nextPointCrossingLatitude(Position a, Position b, double leftLon, double rightLon, double targetLat, double bearingDegrees) { LongitudePair lonCrossingCandidates = a.getLongitudeOnGreatCircle(b, targetLat); if (lonCrossingCandidates != null) { double lonCrossing; // choose candidate closest in longitude to point a along path to b boolean candidate1ok = leftLon <= lonCrossingCandidates.getLon1() && lonCrossingCandidates.getLon1() <= rightLon && !(lonCrossingCandidates.getLon1() == a.getLon() && targetLat == a .getLat()); boolean candidate2ok = leftLon <= lonCrossingCandidates.getLon2() && lonCrossingCandidates.getLon2() <= rightLon && !(lonCrossingCandidates.getLon2() == a.getLon() && targetLat == a .getLat()); if (candidate1ok && candidate2ok) { // choose the best of the candidates // no units used in calculations because only doing comparisons // so don't care if it's km or nautical miles double distance1ToB = new Position(targetLat, lonCrossingCandidates.getLon1()).getDistanceToKm(b); double distance2ToB = new Position(targetLat, lonCrossingCandidates.getLon2()).getDistanceToKm(b); double distanceAToB = a.getDistanceToKm(b); if (distance1ToB > distanceAToB) candidate1ok = false; if (distance2ToB > distanceAToB) candidate2ok = false; if (candidate1ok && candidate2ok) { if (distance1ToB < distance2ToB) { candidate1ok = false; } else candidate2ok = false; } if (candidate1ok) lonCrossing = lonCrossingCandidates.getLon1(); else if (candidate2ok) lonCrossing = lonCrossingCandidates.getLon2(); else // neither of the candidates are on the way to b! return null; } else if (candidate1ok) { lonCrossing = lonCrossingCandidates.getLon1(); } else if (candidate2ok) lonCrossing = lonCrossingCandidates.getLon2(); else return null; // check that lonCrossing is on the segment a to b double bearingDegreesTest = bearingDegrees(a, Position.create(targetLat, lonCrossing)); double diff = Position.getBearingDifferenceDegrees(bearingDegrees, bearingDegreesTest); if (Math.abs(diff) > 90) return null; return Position.create(targetLat, lonCrossing); } else return null; } private double getTargetLon(Cell cell, double bearingDegrees) { if (bearingDegrees >= 0 && bearingDegrees < 180) return cell.rightEdgeLongitude(options); else return cell.leftEdgeLongitude(options); } private double getTargetLat(Cell cell, double bearingDegrees) { if (bearingDegrees >= 270 || bearingDegrees < 90) return cell.topEdgeLatitude(options); else return cell.bottomEdgeLatitude(options); } }