View Javadoc
1   package au.gov.amsa.geo.model;
2   
3   import org.apache.log4j.Logger;
4   
5   import au.gov.amsa.util.navigation.Position;
6   import au.gov.amsa.util.navigation.Position.LongitudePair;
7   
8   public class GridTraversor {
9   
10  	private static Logger log = Logger.getLogger(GridTraversor.class);
11  
12  	private static final double SMALL_INCREMENT_DEGREES = 0.0000001;
13  
14  	private final Options options;
15  
16  	public GridTraversor(Options options) {
17  		this.options = options;
18  	}
19  
20  	private static double bearingDegrees(Position a, Position b) {
21  		double val = a.getBearingDegrees(b);
22  		if (val == 0 && a.getLat() == b.getLat()) {
23  			if (b.getLon() > a.getLon())
24  				val = 90;
25  			else if (b.getLon() < a.getLon())
26  				val = 270;
27  			else
28  				// same point
29  				val = 0;
30  		}
31  		return val;
32  	}
33  
34  	public Position nextPoint(Position a, Position b) {
35  		if (a.equals(b))
36  			return b;
37  		double bearingDegrees = bearingDegrees(a, b);
38  
39  		// if on left edge heading left or top edge heading up then nudge into
40  		// next cell
41  		// TODO is this required?
42  		Cell cell1 = Cell.cellAt(a.getLat(), a.getLon(), options).get();
43  		if (bearingDegrees > 180
44  				&& a.getLon() == cell1.leftEdgeLongitude(options)) {
45  			a = new Position(a.getLat(), a.getLon() - SMALL_INCREMENT_DEGREES);
46  			cell1 = Cell.cellAt(a.getLat(), a.getLon(), options).get();
47  			bearingDegrees = bearingDegrees(a, b);
48  		} else if ((bearingDegrees > 270 || bearingDegrees < 90)
49  				&& a.getLat() == cell1.topEdgeLatitude(options)) {
50  			a = new Position(a.getLat() + SMALL_INCREMENT_DEGREES, a.getLon());
51  			cell1 = Cell.cellAt(a.getLat(), a.getLon(), options).get();
52  			bearingDegrees = bearingDegrees(a, b);
53  		}
54  
55  		Cell cell2 = Cell.cellAt(b.getLat(), b.getLon(), options).get();
56  		if (cell1.equals(cell2))
57  			return b;
58  		else {
59  
60  			double targetLon = getTargetLon(cell1, bearingDegrees);
61  
62  			// check if crosses target lat based on bearing
63  			double leftLon = cell1.leftEdgeLongitude(options);
64  			double rightLon = cell1.rightEdgeLongitude(options);
65  			double targetLat = getTargetLat(cell1, bearingDegrees);
66  
67  			if (bearingDegrees == 0 || bearingDegrees == 180)
68  				return Position.create(targetLat, a.getLon());
69  
70  			{
71  				Position result = nextPointCrossingLatitude(a, b, leftLon,
72  						rightLon, targetLat, bearingDegrees);
73  				if (result != null)
74  					return result;
75  			}
76  			{
77  				double otherLat = getNonTargetLat(cell1, bearingDegrees);
78  				Position result = nextPointCrossingLatitude(a, b, leftLon,
79  						rightLon, otherLat, bearingDegrees);
80  				if (result != null)
81  					return result;
82  			}
83  
84  			// see if crosses left or right edge
85  			Double latCrossing = a.getLatitudeOnGreatCircle(b, targetLon);
86  
87  			if (latCrossing != null) {
88  				double topLat = cell1.topEdgeLatitude(options);
89  				double bottomLat = cell1.bottomEdgeLatitude(options);
90  				if (topLat >= latCrossing && bottomLat <= latCrossing)
91  					return Position.create(latCrossing, targetLon);
92  			}
93  
94  			log.warn("unexpected! Could not calculate next point for segment between\n a = "
95  					+ a + " b = " + b + "\noptions=" + options);
96  			return b;
97  
98  		}
99  	}
100 
101 	private double getNonTargetLat(Cell cell, double bearingDegrees) {
102 		if (bearingDegrees >= 270 || bearingDegrees < 90)
103 			return cell.bottomEdgeLatitude(options);
104 		else
105 			return cell.topEdgeLatitude(options);
106 	}
107 
108 	private Position nextPointCrossingLatitude(Position a, Position b,
109 			double leftLon, double rightLon, double targetLat,
110 			double bearingDegrees) {
111 		LongitudePair lonCrossingCandidates = a.getLongitudeOnGreatCircle(b,
112 				targetLat);
113 		if (lonCrossingCandidates != null) {
114 			double lonCrossing;
115 			// choose candidate closest in longitude to point a along path to b
116 			boolean candidate1ok = leftLon <= lonCrossingCandidates.getLon1()
117 					&& lonCrossingCandidates.getLon1() <= rightLon
118 					&& !(lonCrossingCandidates.getLon1() == a.getLon() && targetLat == a
119 							.getLat());
120 
121 			boolean candidate2ok = leftLon <= lonCrossingCandidates.getLon2()
122 					&& lonCrossingCandidates.getLon2() <= rightLon
123 					&& !(lonCrossingCandidates.getLon2() == a.getLon() && targetLat == a
124 							.getLat());
125 			if (candidate1ok && candidate2ok) {
126 				// choose the best of the candidates
127 
128 				// no units used in calculations because only doing comparisons
129 				// so don't care if it's km or nautical miles
130 				double distance1ToB = new Position(targetLat,
131 						lonCrossingCandidates.getLon1()).getDistanceToKm(b);
132 				double distance2ToB = new Position(targetLat,
133 						lonCrossingCandidates.getLon2()).getDistanceToKm(b);
134 				double distanceAToB = a.getDistanceToKm(b);
135 				if (distance1ToB > distanceAToB)
136 					candidate1ok = false;
137 				if (distance2ToB > distanceAToB)
138 					candidate2ok = false;
139 				if (candidate1ok && candidate2ok) {
140 					if (distance1ToB < distance2ToB) {
141 						candidate1ok = false;
142 					} else
143 						candidate2ok = false;
144 				}
145 				if (candidate1ok)
146 					lonCrossing = lonCrossingCandidates.getLon1();
147 				else if (candidate2ok)
148 					lonCrossing = lonCrossingCandidates.getLon2();
149 				else
150 					// neither of the candidates are on the way to b!
151 					return null;
152 			} else if (candidate1ok) {
153 				lonCrossing = lonCrossingCandidates.getLon1();
154 			} else if (candidate2ok)
155 				lonCrossing = lonCrossingCandidates.getLon2();
156 			else
157 				return null;
158 			// check that lonCrossing is on the segment a to b
159 			double bearingDegreesTest = bearingDegrees(a,
160 					Position.create(targetLat, lonCrossing));
161 			double diff = Position.getBearingDifferenceDegrees(bearingDegrees,
162 					bearingDegreesTest);
163 			if (Math.abs(diff) > 90)
164 				return null;
165 			return Position.create(targetLat, lonCrossing);
166 		} else
167 			return null;
168 	}
169 
170 	private double getTargetLon(Cell cell, double bearingDegrees) {
171 		if (bearingDegrees >= 0 && bearingDegrees < 180)
172 			return cell.rightEdgeLongitude(options);
173 		else
174 			return cell.leftEdgeLongitude(options);
175 	}
176 
177 	private double getTargetLat(Cell cell, double bearingDegrees) {
178 		if (bearingDegrees >= 270 || bearingDegrees < 90)
179 			return cell.topEdgeLatitude(options);
180 		else
181 			return cell.bottomEdgeLatitude(options);
182 	}
183 
184 }