Issue #189 grid retrieval api.
Change-Id: Ice9432ab703420c8d441860de085a8d34a2610c8 Former-commit-id: e0b91523c54227ef15588f5dd672b2450a9dd8ac
This commit is contained in:
parent
63cdc32988
commit
56d375da86
4 changed files with 531 additions and 93 deletions
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Reference in a new issue