package au.gov.amsa.geo.model; import static au.gov.amsa.util.navigation.Position.to180; import java.math.BigDecimal; import java.util.TreeSet; import org.apache.log4j.Logger; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Optional; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; //TODO tested only in the environs of Australian SAR region (60 degrees longitude to just over the 180 boundary to the east) public class Grid { private static Logger log = Logger.getLogger(Grid.class); private final TreeSet<Double> lats; /** * top lat of cell <--> index of cell */ private final BiMap<Double, Long> latIndexes; private final TreeSet<Double> lons; /** * left lon of cell <--> index of cell */ private final BiMap<Double, Long> lonIndexes; private final Options options; public Grid(Options options) { this.options = options; lats = new TreeSet<Double>(); { BigDecimal lat = getStartLat(options); while (lat.doubleValue() >= options.getFilterBounds() .getBottomRightLat()) { lats.add(lat.doubleValue()); lat = lat.subtract(options.getCellSizeDegrees()); } lat = lat.subtract(options.getCellSizeDegrees()); lats.add(lat.doubleValue()); } { latIndexes = HashBiMap.create(); long index = 0; for (double lat : lats.descendingSet()) { latIndexes.put(lat, index); index++; } } { lons = new TreeSet<Double>(); BigDecimal lon = getStartLon(options); // handle values around the 180 longitude line // TODO this will need more handling for arbitrary regions on the // earth's surface. double maxLon; if (options.getFilterBounds().getBottomRightLon() < lon .doubleValue()) maxLon = options.getFilterBounds().getBottomRightLon() + 360; else maxLon = options.getFilterBounds().getBottomRightLon(); while (lon.doubleValue() <= maxLon) { lons.add(to180(lon.doubleValue())); lon = lon.add(options.getCellSizeDegrees()); } lon.add(options.getCellSizeDegrees()); lons.add(lon.doubleValue()); } { lonIndexes = HashBiMap.create(); long index = 0; for (double lon : lons) { lonIndexes.put(lon, index); index++; } } } @VisibleForTesting static BigDecimal getStartLat(Options options) { final long moveStartLatUpByCells; if (options.getFilterBounds().getTopLeftLat() == options.getOriginLat() .doubleValue()) moveStartLatUpByCells = 0; else moveStartLatUpByCells = Math.max(0, Math.round(Math.floor((options .getFilterBounds().getTopLeftLat() - options.getOriginLat() .doubleValue()) / options.getCellSizeDegrees().doubleValue()) + 1)); BigDecimal result = options.getOriginLat(); for (int i = 0; i < moveStartLatUpByCells; i++) result = result.add(options.getCellSizeDegrees()); return result; } @VisibleForTesting static BigDecimal getStartLon(Options options) { final long moveStartLonLeftByCells; if (options.getFilterBounds().getTopLeftLon() == options.getOriginLon() .doubleValue()) moveStartLonLeftByCells = 0; else { moveStartLonLeftByCells = Math.max(0, Math.round(Math .floor((options.getOriginLon().doubleValue() - options .getFilterBounds().getTopLeftLon()) / options.getCellSizeDegrees().doubleValue()) + 1)); } BigDecimal result = options.getOriginLon(); for (int i = 0; i < moveStartLonLeftByCells; i++) result = result.subtract(options.getCellSizeDegrees()); return result; } public Optional<Cell> cellAt(double lat, double lon) { if (!options.getFilterBounds().contains(lat, lon)) return Optional.absent(); else { Long latIndex = latIndexes.get(lats.ceiling(lat)); Long lonIndex = lonIndexes.get(lons.floor(lon)); return Optional.of(new Cell(latIndex, lonIndex)); } } public double leftEdgeLongitude(Cell cell) { return leftEdgeLongitude(cell.getLonIndex()); } private double leftEdgeLongitude(long lonIndex) { return lonIndexes.inverse().get(lonIndex); } public double rightEdgeLongitude(Cell cell) { try { return lonIndexes.inverse().get(cell.getLonIndex() + 1); } catch (RuntimeException e) { log.warn("cell=" + cell + ", options=" + options); throw e; } } public double topEdgeLatitude(Cell cell) { return topEdgeLatitude(cell.getLatIndex()); } public double topEdgeLatitude(long latIndex) { return latIndexes.inverse().get(latIndex); } public double bottomEdgeLatitude(Cell cell) { return latIndexes.inverse().get(cell.getLatIndex() + 1); } public double centreLat(long latIndex) { return topEdgeLatitude(latIndex) - options.getCellSizeDegrees().doubleValue() / 2; } public double centreLon(long lonIndex) { return leftEdgeLongitude(lonIndex) + options.getCellSizeDegrees().doubleValue() / 2; } }