View Javadoc
1   package au.gov.amsa.geo.model;
2   
3   import static au.gov.amsa.util.navigation.Position.to180;
4   
5   import java.math.BigDecimal;
6   import java.util.TreeSet;
7   
8   import org.apache.log4j.Logger;
9   
10  import com.google.common.annotations.VisibleForTesting;
11  import com.google.common.base.Optional;
12  import com.google.common.collect.BiMap;
13  import com.google.common.collect.HashBiMap;
14  
15  //TODO tested only in the environs of Australian SAR region (60 degrees longitude to just over the 180 boundary to the east)
16  public class Grid {
17  
18  	private static Logger log = Logger.getLogger(Grid.class);
19  
20  	private final TreeSet<Double> lats;
21  
22  	/**
23  	 * top lat of cell <--> index of cell
24  	 */
25  	private final BiMap<Double, Long> latIndexes;
26  
27  	private final TreeSet<Double> lons;
28  	/**
29  	 * left lon of cell <--> index of cell
30  	 */
31  	private final BiMap<Double, Long> lonIndexes;
32  
33  	private final Options options;
34  
35  	public Grid(Options options) {
36  		this.options = options;
37  		lats = new TreeSet<Double>();
38  		{
39  			BigDecimal lat = getStartLat(options);
40  			while (lat.doubleValue() >= options.getFilterBounds()
41  					.getBottomRightLat()) {
42  				lats.add(lat.doubleValue());
43  				lat = lat.subtract(options.getCellSizeDegrees());
44  			}
45  			lat = lat.subtract(options.getCellSizeDegrees());
46  			lats.add(lat.doubleValue());
47  		}
48  
49  		{
50  			latIndexes = HashBiMap.create();
51  			long index = 0;
52  			for (double lat : lats.descendingSet()) {
53  				latIndexes.put(lat, index);
54  				index++;
55  			}
56  		}
57  
58  		{
59  			lons = new TreeSet<Double>();
60  			BigDecimal lon = getStartLon(options);
61  			// handle values around the 180 longitude line
62  			// TODO this will need more handling for arbitrary regions on the
63  			// earth's surface.
64  			double maxLon;
65  			if (options.getFilterBounds().getBottomRightLon() < lon
66  					.doubleValue())
67  				maxLon = options.getFilterBounds().getBottomRightLon() + 360;
68  			else
69  				maxLon = options.getFilterBounds().getBottomRightLon();
70  			while (lon.doubleValue() <= maxLon) {
71  				lons.add(to180(lon.doubleValue()));
72  				lon = lon.add(options.getCellSizeDegrees());
73  			}
74  			lon.add(options.getCellSizeDegrees());
75  			lons.add(lon.doubleValue());
76  		}
77  		{
78  			lonIndexes = HashBiMap.create();
79  			long index = 0;
80  			for (double lon : lons) {
81  				lonIndexes.put(lon, index);
82  				index++;
83  			}
84  		}
85  
86  	}
87  
88  	@VisibleForTesting
89  	static BigDecimal getStartLat(Options options) {
90  		final long moveStartLatUpByCells;
91  		if (options.getFilterBounds().getTopLeftLat() == options.getOriginLat()
92  				.doubleValue())
93  			moveStartLatUpByCells = 0;
94  		else
95  			moveStartLatUpByCells = Math.max(0, Math.round(Math.floor((options
96  					.getFilterBounds().getTopLeftLat() - options.getOriginLat()
97  					.doubleValue())
98  					/ options.getCellSizeDegrees().doubleValue()) + 1));
99  		BigDecimal result = options.getOriginLat();
100 		for (int i = 0; i < moveStartLatUpByCells; i++)
101 			result = result.add(options.getCellSizeDegrees());
102 		return result;
103 	}
104 
105 	@VisibleForTesting
106 	static BigDecimal getStartLon(Options options) {
107 		final long moveStartLonLeftByCells;
108 		if (options.getFilterBounds().getTopLeftLon() == options.getOriginLon()
109 				.doubleValue())
110 			moveStartLonLeftByCells = 0;
111 		else {
112 			moveStartLonLeftByCells = Math.max(0, Math.round(Math
113 					.floor((options.getOriginLon().doubleValue() - options
114 							.getFilterBounds().getTopLeftLon())
115 							/ options.getCellSizeDegrees().doubleValue()) + 1));
116 		}
117 		BigDecimal result = options.getOriginLon();
118 		for (int i = 0; i < moveStartLonLeftByCells; i++)
119 			result = result.subtract(options.getCellSizeDegrees());
120 		return result;
121 	}
122 
123 	public Optional<Cell> cellAt(double lat, double lon) {
124 		if (!options.getFilterBounds().contains(lat, lon))
125 			return Optional.absent();
126 		else {
127 			Long latIndex = latIndexes.get(lats.ceiling(lat));
128 			Long lonIndex = lonIndexes.get(lons.floor(lon));
129 			return Optional.of(new Cell(latIndex, lonIndex));
130 		}
131 	}
132 
133 	public double leftEdgeLongitude(Cell cell) {
134 		return leftEdgeLongitude(cell.getLonIndex());
135 	}
136 
137 	private double leftEdgeLongitude(long lonIndex) {
138 		return lonIndexes.inverse().get(lonIndex);
139 	}
140 
141 	public double rightEdgeLongitude(Cell cell) {
142 		try {
143 			return lonIndexes.inverse().get(cell.getLonIndex() + 1);
144 		} catch (RuntimeException e) {
145 			log.warn("cell=" + cell + ", options=" + options);
146 			throw e;
147 		}
148 	}
149 
150 	public double topEdgeLatitude(Cell cell) {
151 		return topEdgeLatitude(cell.getLatIndex());
152 	}
153 
154 	public double topEdgeLatitude(long latIndex) {
155 		return latIndexes.inverse().get(latIndex);
156 	}
157 
158 	public double bottomEdgeLatitude(Cell cell) {
159 		return latIndexes.inverse().get(cell.getLatIndex() + 1);
160 	}
161 
162 	public double centreLat(long latIndex) {
163 		return topEdgeLatitude(latIndex)
164 				- options.getCellSizeDegrees().doubleValue() / 2;
165 	}
166 
167 	public double centreLon(long lonIndex) {
168 		return leftEdgeLongitude(lonIndex)
169 				+ options.getCellSizeDegrees().doubleValue() / 2;
170 	}
171 
172 }