Merge "Issue #189 grid retrieval api. Change-Id: Ice9432ab703420c8d441860de085a8d34a2610c8" into development

Former-commit-id: 9f5e970a5e [formerly 8dfd659e03] [formerly 92ff920171] [formerly 92ff920171 [formerly 0b4f2bd666]] [formerly 9f5e970a5e [formerly 8dfd659e03] [formerly 92ff920171] [formerly 92ff920171 [formerly 0b4f2bd666]] [formerly 1cd1f8b793 [formerly 92ff920171 [formerly 0b4f2bd666] [formerly 1cd1f8b793 [formerly dbaf10dc2036589e21245a7166182ee01e401717]]]]]
Former-commit-id: 1cd1f8b793
Former-commit-id: 5ee0468805 [formerly 3e871e74dd] [formerly 78cad6b0e1] [formerly ac53ba5cdeefd7816b7278c8e34ff9107ad3d377 [formerly 43275093f444c4ecc0da9cd2da47618f814895a4] [formerly 78cad6b0e1 [formerly 24e21a3e48]]]
Former-commit-id: d257a4115371c1cb623cbfaac7bfdf76a2559fe7 [formerly a265deee3d05689256f2101e0fb9e48c5b35a19c] [formerly a34caf2edf [formerly a0327c25fb]]
Former-commit-id: c40e5dc793e06c7f9f906ae27e38d27021e72579 [formerly a34caf2edf]
Former-commit-id: e0668a8fc4
This commit is contained in:
Richard Peter 2012-11-20 11:37:58 -06:00 committed by Gerrit Code Review
commit b541a3a520
4 changed files with 531 additions and 93 deletions

View file

@ -10,29 +10,33 @@ Eclipse-BuddyPolicy: registered, ext, global
Import-Package: com.raytheon.uf.common.dataplugin,
com.raytheon.uf.common.dataplugin.annotations,
com.raytheon.uf.common.dataplugin.persist,
com.raytheon.uf.common.datastorage.records,
com.raytheon.uf.common.geospatial,
com.raytheon.uf.common.gridcoverage,
com.raytheon.uf.common.localization,
com.raytheon.uf.common.parameter,
com.raytheon.uf.common.serialization,
com.raytheon.uf.common.serialization.annotations,
com.raytheon.uf.common.serialization.comm,
com.raytheon.uf.common.status,
com.raytheon.uf.common.time,
com.vividsolutions.jts.geom,
javax.measure.unit,
javax.persistence,
org.hibernate.annotations,
org.opengis.metadata.spatial
Export-Package: com.raytheon.uf.common.dataplugin.grid,
com.raytheon.uf.common.dataplugin.grid.dataquery,
com.raytheon.uf.common.dataplugin.grid.dataset,
com.raytheon.uf.common.dataplugin.grid.datastorage,
com.raytheon.uf.common.dataplugin.grid.mapping,
com.raytheon.uf.common.dataplugin.grid.request,
com.raytheon.uf.common.dataplugin.grid.units,
com.raytheon.uf.common.dataplugin.grid.util
Require-Bundle: com.raytheon.uf.common.comm;bundle-version="1.12.1174",
com.raytheon.uf.common.dataplugin.level;bundle-version="1.12.1174",
Require-Bundle: javax.measure,
com.raytheon.uf.common.geospatial,
org.geotools,
com.raytheon.uf.common.localization,
com.raytheon.uf.common.datastorage,
com.raytheon.uf.common.gridcoverage;bundle-version="1.0.0",
com.raytheon.uf.common.parameter;bundle-version="1.0.0",
com.raytheon.uf.common.util;bundle-version="1.12.1174",
com.raytheon.uf.common.dataquery;bundle-version="1.0.0"
com.raytheon.uf.common.comm;bundle-version="1.12.1174",
com.raytheon.uf.common.dataquery;bundle-version="1.0.0",
com.raytheon.uf.common.dataplugin.level;bundle-version="1.12.1174",
com.raytheon.uf.common.util;bundle-version="1.12.1174"

View file

@ -0,0 +1,358 @@
/**
* This software was developed and / or modified by Raytheon Company,
* pursuant to Contract DG133W-05-CQ-1067 with the US Government.
*
* U.S. EXPORT CONTROLLED TECHNICAL DATA
* This software product contains export-restricted data whose
* export/transfer/disclosure is restricted by U.S. law. Dissemination
* to non-U.S. persons whether in the United States or abroad requires
* an export license or other authorization.
*
* Contractor Name: Raytheon Company
* Contractor Address: 6825 Pine Street, Suite 340
* Mail Stop B8
* Omaha, NE 68106
* 402.291.0100
*
* See the AWIPS II Master Rights File ("Master Rights File.pdf") for
* further licensing information.
**/
package com.raytheon.uf.common.dataplugin.grid.datastorage;
import java.io.File;
import java.io.FileNotFoundException;
import javax.measure.converter.ConversionException;
import javax.measure.converter.UnitConverter;
import javax.measure.unit.Unit;
import org.geotools.coverage.grid.GridGeometry2D;
import org.opengis.metadata.spatial.PixelOrientation;
import com.raytheon.uf.common.dataplugin.annotations.DataURI;
import com.raytheon.uf.common.dataplugin.grid.GridPathProvider;
import com.raytheon.uf.common.dataplugin.grid.GridRecord;
import com.raytheon.uf.common.dataplugin.persist.IHDFFilePathProvider;
import com.raytheon.uf.common.datastorage.DataStoreFactory;
import com.raytheon.uf.common.datastorage.IDataStore;
import com.raytheon.uf.common.datastorage.Request;
import com.raytheon.uf.common.datastorage.StorageException;
import com.raytheon.uf.common.datastorage.records.FloatDataRecord;
import com.raytheon.uf.common.datastorage.records.IDataRecord;
import com.raytheon.uf.common.geospatial.MapUtil;
import com.raytheon.uf.common.geospatial.util.GridGeometryWrapChecker;
import com.raytheon.uf.common.gridcoverage.GridCoverage;
import com.raytheon.uf.common.gridcoverage.LambertConformalGridCoverage;
import com.raytheon.uf.common.gridcoverage.LatLonGridCoverage;
import com.raytheon.uf.common.gridcoverage.MercatorGridCoverage;
import com.raytheon.uf.common.gridcoverage.PolarStereoGridCoverage;
import com.raytheon.uf.common.gridcoverage.exception.GridCoverageException;
import com.raytheon.uf.common.gridcoverage.subgrid.SubGrid;
import com.raytheon.uf.common.localization.IPathManager;
import com.raytheon.uf.common.localization.msgs.GetServersRequest;
import com.raytheon.uf.common.localization.msgs.GetServersResponse;
import com.raytheon.uf.common.parameter.lookup.ParameterLookup;
import com.raytheon.uf.common.serialization.comm.RequestRouter;
import com.vividsolutions.jts.geom.Coordinate;
/**
* Convenience class for requesting grid data. This class provides automatic
* unit conversion and also the ability to allow worldwide data to overlap.
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Nov 14, 2012 bsteffen Initial creation
*
* </pre>
*
* @author bsteffen
* @version 1.0
*/
public class GridDataRetriever {
protected static String serverDataDir;
protected GridRecord record;
protected GridCoverage requestCoverage;
protected Request request = Request.ALL;
protected int worldWrapColumns = -1;
protected Unit<?> unit;
protected UnitConverter converter;
/**
* Construct a GridDataRetriever that will retrieve data for the provided
* GridRecord.
*
* @param record
* GridRecord for which data is retrieved.
*/
public GridDataRetriever(GridRecord record) {
this.record = record;
}
/**
* Construct a GridDataRetriever that will retrieve data for the provided
* dataURI. The dataURI must be a valid dataURI for a grid record.
*
* @param dataUri
* dataURI for grid data to retrieve.
*/
public GridDataRetriever(String dataUri) {
this.record = new GridRecord(dataUri);
this.record.setParameter(ParameterLookup.getInstance().getParameter(
record.getParameter().getAbbreviation()));
}
/**
* For data that wraps around the world, specify the number of redundant
* columns to add. For data that does not wrap around the world this method
* call has no affect and the full data set will be retrieved.
*
* @param worldWrapColumns
* The number of redundant columns to include in the x direction
* for wrapping grids.
* @return a boolean true if the data is world wrapping, false if not(in
* which case this method call has no affect).
* @throws GridCoverageException
* thrown when there are errors constructing a wrapping
* GridCoverage
*/
public boolean setWorldWrapColumns(int worldWrapColumns)
throws GridCoverageException {
GridCoverage dataLoc = record.getLocation();
GridGeometry2D dataGeom = dataLoc.getGridGeometry();
int wrapX = GridGeometryWrapChecker.checkForWrapping(dataGeom);
if (wrapX != -1) {
int newX = wrapX + worldWrapColumns;
if (newX == dataLoc.getNx()) {
this.request = Request.ALL;
} else if (newX < dataLoc.getNx()) {
Coordinate upperRight = new Coordinate(newX - 1, 0);
upperRight = MapUtil.gridCoordinateToLatLon(upperRight,
PixelOrientation.CENTER, dataLoc);
setRequestArea(dataLoc.getLowerLeftLon(),
dataLoc.getLowerLeftLat(), upperRight.x, upperRight.y);
} else {
this.request = Request.ALL;
if (dataLoc instanceof LatLonGridCoverage) {
LatLonGridCoverage newLoc = new LatLonGridCoverage(
(LatLonGridCoverage) dataLoc);
newLoc.setLa2(0);
newLoc.setLo2(0);
requestCoverage = newLoc;
} else if (dataLoc instanceof MercatorGridCoverage) {
MercatorGridCoverage newLoc = new MercatorGridCoverage(
(MercatorGridCoverage) dataLoc);
newLoc.setLa2(null);
newLoc.setLo2(null);
requestCoverage = newLoc;
} else if (dataLoc instanceof LambertConformalGridCoverage) {
requestCoverage = new LambertConformalGridCoverage(
(LambertConformalGridCoverage) dataLoc);
} else if (dataLoc instanceof PolarStereoGridCoverage) {
// I really doubt it is possible to world wrap a
// PolarStereoCoverage, but just in case...
requestCoverage = new PolarStereoGridCoverage(
(PolarStereoGridCoverage) dataLoc);
} else {
throw new GridCoverageException(
"Cannot wrap data for projection of type "
+ dataLoc.getClass().getName());
}
requestCoverage.setNx(newX);
requestCoverage.setGridGeometry(null);
requestCoverage.initialize();
this.worldWrapColumns = newX - dataLoc.getNx();
}
return true;
} else {
return false;
}
}
/**
* Set the requested area to be a subgrid of the total area. This uses the
* trim functionality of GridCoverage to generate an area which has corners
* at the provided latitude and longitude.
*
* @param lon1
* @param lat1
* @param lon2
* @param lat2
* @throws GridDataRetrievalException
* @throws GridCoverageException
*/
protected void setRequestArea(double lon1, double lat1, double lon2,
double lat2) throws GridCoverageException {
SubGrid subGrid = new SubGrid();
subGrid.setLowerLeftLat(Math.min(lat1, lat2));
subGrid.setLowerLeftLon(Math.min(lat1, lat2));
subGrid.setUpperRightLat(Math.min(lat1, lat2));
subGrid.setUpperRightLon(Math.min(lat1, lat2));
requestCoverage = record.getLocation().trim(subGrid);
int[] minIndex = { subGrid.getUpperLeftX(), subGrid.getUpperLeftY() };
int[] maxIndex = { subGrid.getUpperLeftX() + subGrid.getNX(),
subGrid.getUpperLeftY() + subGrid.getNY() };
request = Request.buildSlab(minIndex, maxIndex);
requestCoverage.initialize();
}
/**
* Set the desired unit for the data being retrieved. If this method is
* successful the data will automatically be converted when it is retrieved.
*
* @param unit
* the desired unit for the data.
* @throws ConversionException
* occurs when the provided unit is incompatible with the
* storage unit of the data.
*/
public void setUnit(Unit<?> unit) throws ConversionException {
this.converter = record.getParameter().getUnit().getConverterTo(unit);
this.unit = unit;
}
/**
* retrieve the grid data. This method will automatically perform any
* requested unit conversion or world wrap adjustments.
*
* @return FloatDataRecord containing the raw values for this grid.
* @throws StorageException
* if anything goes wrong while retrieving the data, for example
* if the data cannot be found in the datastore or if there are
* problems communicating with the servers.
*/
public FloatDataRecord getDataRecord() throws StorageException {
IDataStore ds = DataStoreFactory.getDataStore(findStorageLocation());
IDataRecord dataRecord;
try {
dataRecord = ds.retrieve(getGroup(), "Data", request);
} catch (FileNotFoundException e) {
throw new StorageException(e.getLocalizedMessage(), null, e);
}
FloatDataRecord floatRecord = (FloatDataRecord) dataRecord;
boolean cloned = false;
if (converter != null) {
if (!cloned) {
floatRecord = (FloatDataRecord) floatRecord.clone();
cloned = true;
}
float[] data = floatRecord.getFloatData();
for (int i = 0; i < data.length; i += 1) {
if (data[i] <= -9999) {
// -9999 and -999999 are both commonly used no data values,
// and rarely are such extremes valid. No Data Value.
data[i] = data[i];
} else {
data[i] = (float) converter.convert(data[i]);
}
}
}
if (worldWrapColumns > 0) {
if (!cloned) {
floatRecord = (FloatDataRecord) floatRecord.clone();
cloned = true;
}
int nx = (int) floatRecord.getSizes()[0];
int newNx = nx + worldWrapColumns;
int ny = (int) floatRecord.getSizes()[1];
float[] oldData = floatRecord.getFloatData();
float[] newData = new float[newNx * ny];
for (int y = 0; y < ny; y += 1) {
// this is in a loop so it works correctly when extraColumns >
// nx
for (int numCopied = 0; numCopied < newNx; numCopied += nx) {
System.arraycopy(oldData, y * nx, newData, y * newNx
+ numCopied, Math.min(nx, newNx - numCopied));
}
}
floatRecord.setSizes(new long[] { newNx, ny });
floatRecord.setFloatData(newData);
}
return floatRecord;
}
/**
* Get a GridCoverage describing the spatial area covered by the retrieved
* data. If setWorldWrapColumns or setRequestArea was used then this will
* return a coverage describing the modified area, otherwise this is the
* same as the coverage in the GridRecord.
*
* @return grid coverage of the data record.
*/
public GridCoverage getCoverage() {
if (requestCoverage == null) {
return record.getLocation();
} else {
return requestCoverage;
}
}
/**
* Get a unit object describing the retrieved data. If setUnit was
* successfully used then this will return the same unit, otherwise it
* returns the storage unit of the data.
*
* @return unit of the data record
*/
public Unit<?> getUnit() {
if (unit == null) {
return record.getParameter().getUnit();
}
return unit;
}
private String getGroup() {
if (GridPathProvider.STATIC_PARAMETERS.contains(record.getParameter()
.getAbbreviation())) {
return DataURI.SEPARATOR + record.getLocation().getId()
+ DataURI.SEPARATOR
+ record.getParameter().getAbbreviation();
} else {
return record.getDataURI();
}
}
private File findStorageLocation() throws StorageException {
IHDFFilePathProvider pathProvider = record.getHDFPathProvider();
String path = pathProvider.getHDFPath(record.getPluginName(), record);
String fileName = pathProvider.getHDFFileName(record.getPluginName(),
record);
return new File(getServerDataDir() + IPathManager.SEPARATOR
+ record.getPluginName() + IPathManager.SEPARATOR + path
+ IPathManager.SEPARATOR + fileName);
}
private static synchronized String getServerDataDir()
throws StorageException {
if (serverDataDir == null) {
// TODO cave already knows the server data dir in VizApp, and edex
// has it in system properties but we can't access either because
// this is common code, architecturally we need some way around
// this. For now this will send it's own request which is slightly
// wasteful but not terribly harmful.
try {
GetServersResponse response = (GetServersResponse) RequestRouter
.route(new GetServersRequest());
serverDataDir = response.getServerDataDir();
} catch (Exception e) {
throw new StorageException("Error communicating with server.",
null, e);
}
}
return serverDataDir;
}
}

View file

@ -20,12 +20,8 @@
package com.raytheon.uf.common.geospatial.interpolation.data;
import org.geotools.coverage.grid.GeneralGridGeometry;
import org.geotools.geometry.DirectPosition2D;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.referencing.operation.MathTransform;
import com.raytheon.uf.common.geospatial.util.GridGeometryWrapChecker;
/**
*
@ -63,7 +59,7 @@ public abstract class AbstractDataWrapper implements DataSource,
public AbstractDataWrapper(GeneralGridGeometry geometry) {
this.nx = geometry.getGridRange().getSpan(0);
this.ny = geometry.getGridRange().getSpan(1);
checkForWrapping(geometry);
this.wrapX = GridGeometryWrapChecker.checkForWrapping(geometry);
}
public AbstractDataWrapper(int nx, int ny) {
@ -117,82 +113,4 @@ public abstract class AbstractDataWrapper implements DataSource,
protected abstract void setDataValueInternal(double dataValue, int x, int y);
// Attempt to detect the case where a geographic coordinate reference
// system wraps around the world so that values out of range on the
// X-axis can be retrieved from the other side of the grid. If this is
// the case the sourceWrapX value will be set to the number of grid
// cells that are needed to wrap all the way around the world.
protected void checkForWrapping(GeneralGridGeometry geometry) {
try {
CoordinateReferenceSystem sourceCRS = geometry
.getCoordinateReferenceSystem();
MathTransform grid2crs = geometry
.getGridToCRS(PixelInCell.CELL_CENTER);
MathTransform crs2LatLon = CRS.findMathTransform(sourceCRS,
DefaultGeographicCRS.WGS84);
// Although theoretically any two points would yield the same
// result, nx is chosen as the x coordinate because it overcomes
// some of the normalization performed in geotools math transforms.
// For most math transforms, geotools will normalize the LatLon
// values into into the range centralMeridian +/- 180. In these
// cases the 360 degree shift performed later is completely
// worthless since geotools will normalize the points into the
// same range regardless of how it is shifted. For a worldwide grid
// the nx coordinate is guaranteed to be just slightly over the
// normalized range, so that when the transform normalizes it will
// use a point that is -360 degrees away from the original point.
// This means that even though the -360 degree shift we apply is
// worthless, the normalization process actually applies an
// equivelant shift that yields the correct result. The end result
// is that whether the transform normalizes or not there is always a
// shift of -360 degrees for all worldwide grids.
// Start with two points in grid space, one on each corner side of
// the y direction.
DirectPosition2D corner1 = new DirectPosition2D(nx, 0);
DirectPosition2D corner2 = new DirectPosition2D(nx, ny - 1);
// transform the points to crs space.
grid2crs.transform(corner1, corner1);
grid2crs.transform(corner2, corner2);
// and then to latLon space
crs2LatLon.transform(corner1, corner1);
crs2LatLon.transform(corner2, corner2);
// shift the points by 360 degrees because the goal is to know how
// many grid cells exist in one 360 roll of longitude.
corner1.x = corner1.x - 360;
corner2.x = corner2.x - 360;
// transform back to crs.
crs2LatLon.inverse().transform(corner1, corner1);
crs2LatLon.inverse().transform(corner2, corner2);
// and back to grid space
grid2crs.inverse().transform(corner1, corner1);
grid2crs.inverse().transform(corner2, corner2);
// the difference between the starting x value and the current x
// value is the number
int sourceWrapX = (int) (nx - corner1.x);
// In order to wrap then the transformed point x value should be
// on the other side of the grid and the y value should not have
// changed significantly. Additionally the wrapped x value
// should fall exactly on a grid cell.
if (corner1.x > nx - 1) {
return;
} else if (Math.abs(corner1.y - 0) > 0.0001) {
return;
} else if (Math.abs(corner2.y - ny + 1) > 0.0001) {
return;
} else if (Math.abs(corner1.x + sourceWrapX - nx) > 0.0001) {
return;
} else if (Math.abs(corner2.x + sourceWrapX - nx) > 0.0001) {
return;
} else {
this.wrapX = sourceWrapX;
}
} catch (Exception e) {
// if anything goes wrong in this process just assume we don't
// wrap the x axis, thats not a big deal and it is normal for
// non geographic coordinate systems.
;
}
}
}

View file

@ -0,0 +1,158 @@
/**
* This software was developed and / or modified by Raytheon Company,
* pursuant to Contract DG133W-05-CQ-1067 with the US Government.
*
* U.S. EXPORT CONTROLLED TECHNICAL DATA
* This software product contains export-restricted data whose
* export/transfer/disclosure is restricted by U.S. law. Dissemination
* to non-U.S. persons whether in the United States or abroad requires
* an export license or other authorization.
*
* Contractor Name: Raytheon Company
* Contractor Address: 6825 Pine Street, Suite 340
* Mail Stop B8
* Omaha, NE 68106
* 402.291.0100
*
* See the AWIPS II Master Rights File ("Master Rights File.pdf") for
* further licensing information.
**/
package com.raytheon.uf.common.geospatial.util;
import org.geotools.coverage.grid.GeneralGridGeometry;
import org.geotools.geometry.DirectPosition2D;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
/**
* Provide utility methods for determining if a grid geometry is world wide and
* wrapping so that out of range on the X-axis can be retrieved from the other
* side of the grid.
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Oct 5, 2012 bsteffen Initial creation
*
* </pre>
*
* @author bsteffen
* @version 1.0
*/
public class GridGeometryWrapChecker {
private static double TOLERANCE = 0.00001;
/**
*
* Attempt to detect the case where a geographic coordinate reference system
* wraps around the world so that values out of range on the X-axis can be
* retrieved from the other side of the grid.
*
* If the provided grid geometry wraps than the result will be a positive
* value representing the number of grid cells used to wrap around the
* world. The return value can be used to extend the range of the grid
* infinitely in either direction by reusing column values at an interval of
* the return value. For example if a grid normally has an x range from 0 to
* 359 and the result of this method is 360 then the range can be extended
* to 360 by reusing the values from the 0 column, in fact any integer
* column can be referenced by simply taking columnNumber % 360 to get a
* value within the original valid range.
*
* If the grid does not wrap around then the result is -1;
*
* @param geometry
* @return
*/
public static int checkForWrapping(GeneralGridGeometry geometry) {
try {
int nx = geometry.getGridRange().getSpan(0);
int ny = geometry.getGridRange().getSpan(1);
CoordinateReferenceSystem sourceCRS = geometry
.getCoordinateReferenceSystem();
MathTransform grid2crs = geometry
.getGridToCRS(PixelInCell.CELL_CENTER);
MathTransform crs2LatLon = CRS.findMathTransform(sourceCRS,
DefaultGeographicCRS.WGS84);
MathTransform latLon2crs = crs2LatLon.inverse();
MathTransform crs2grid = grid2crs.inverse();
// Although theoretically any two points would yield the same
// result, nx is chosen as the x coordinate because it overcomes
// some of the normalization performed in geotools math transforms.
// For most math transforms, geotools will normalize the LatLon
// values into into the range centralMeridian +/- 180. In these
// cases the 360 degree shift performed later is completely
// worthless since geotools will normalize the points into the
// same range regardless of how it is shifted. For a worldwide grid
// the nx coordinate is guaranteed to be just slightly over the
// normalized range, so that when the transform normalizes it will
// use a point that is -360 degrees away from the original point.
// This means that even though the -360 degree shift we apply is
// worthless, the normalization process actually applies an
// equivelant shift that yields the correct result. The end result
// is that whether the transform normalizes or not there is always a
// shift of -360 degrees for all worldwide grids.
// Start with two points in grid space, one on each corner side of
// the y direction.
DirectPosition2D corner1 = new DirectPosition2D(nx, 0);
DirectPosition2D corner2 = new DirectPosition2D(nx, ny - 1);
// transform the points to crs space.
grid2crs.transform(corner1, corner1);
grid2crs.transform(corner2, corner2);
// and then to latLon space
crs2LatLon.transform(corner1, corner1);
crs2LatLon.transform(corner2, corner2);
// shift the points by 360 degrees because the goal is to know how
// many grid cells exist in one 360 roll of longitude.
corner1.x = corner1.x - 360;
corner2.x = corner2.x - 360;
// transform back to crs.
latLon2crs.transform(corner1, corner1);
latLon2crs.transform(corner2, corner2);
// and back to grid space
crs2grid.transform(corner1, corner1);
crs2grid.transform(corner2, corner2);
// the difference between the starting x value and the current x
// value is the number
int sourceWrapX = (int) (nx - corner1.x);
// In order to wrap then the transformed point x value should be
// on the other side of the grid and the y value should not have
// changed significantly. Additionally the wrapped x value
// should fall exactly on a grid cell.
if (corner1.x > nx - 1) {
return -1;
} else if (Math.abs(corner1.y - 0) > TOLERANCE) {
return -1;
} else if (Math.abs(corner2.y - ny + 1) > TOLERANCE) {
return -1;
} else if (Math.abs(corner1.x + sourceWrapX - nx) > TOLERANCE) {
return -1;
} else if (Math.abs(corner2.x + sourceWrapX - nx) > TOLERANCE) {
return -1;
} else {
return sourceWrapX;
}
} catch (FactoryException e) {
// Every known crs which can world wrap does not throw factory
// exceptions when getting transformation so if an exception is
// thrown assume it is because the grid does not world wrap.
return -1;
} catch (TransformException e) {
// this exception is expected for some grids that do not world
// wrap, often because while checking transformation is performed
// for invalid coordinates.
return -1;
}
}
}