Issue #189 grid retrieval api.
Change-Id: Ice9432ab703420c8d441860de085a8d34a2610c8 Former-commit-id:93ac9ccaf2
[formerly9bccb6ac7c
] [formerly56d375da86
[formerly e0b91523c54227ef15588f5dd672b2450a9dd8ac]] Former-commit-id:56d375da86
Former-commit-id:dd1a3154e5
This commit is contained in:
parent
082e1cbd09
commit
98cab0f2b3
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,
|
Import-Package: com.raytheon.uf.common.dataplugin,
|
||||||
com.raytheon.uf.common.dataplugin.annotations,
|
com.raytheon.uf.common.dataplugin.annotations,
|
||||||
com.raytheon.uf.common.dataplugin.persist,
|
com.raytheon.uf.common.dataplugin.persist,
|
||||||
com.raytheon.uf.common.datastorage.records,
|
|
||||||
com.raytheon.uf.common.geospatial,
|
com.raytheon.uf.common.geospatial,
|
||||||
com.raytheon.uf.common.gridcoverage,
|
|
||||||
com.raytheon.uf.common.localization,
|
com.raytheon.uf.common.localization,
|
||||||
com.raytheon.uf.common.parameter,
|
|
||||||
com.raytheon.uf.common.serialization,
|
com.raytheon.uf.common.serialization,
|
||||||
com.raytheon.uf.common.serialization.annotations,
|
com.raytheon.uf.common.serialization.annotations,
|
||||||
com.raytheon.uf.common.serialization.comm,
|
com.raytheon.uf.common.serialization.comm,
|
||||||
com.raytheon.uf.common.status,
|
com.raytheon.uf.common.status,
|
||||||
com.raytheon.uf.common.time,
|
com.raytheon.uf.common.time,
|
||||||
com.vividsolutions.jts.geom,
|
com.vividsolutions.jts.geom,
|
||||||
javax.measure.unit,
|
|
||||||
javax.persistence,
|
javax.persistence,
|
||||||
org.hibernate.annotations,
|
org.hibernate.annotations,
|
||||||
org.opengis.metadata.spatial
|
org.opengis.metadata.spatial
|
||||||
Export-Package: com.raytheon.uf.common.dataplugin.grid,
|
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.dataset,
|
||||||
|
com.raytheon.uf.common.dataplugin.grid.datastorage,
|
||||||
com.raytheon.uf.common.dataplugin.grid.mapping,
|
com.raytheon.uf.common.dataplugin.grid.mapping,
|
||||||
com.raytheon.uf.common.dataplugin.grid.request,
|
com.raytheon.uf.common.dataplugin.grid.request,
|
||||||
com.raytheon.uf.common.dataplugin.grid.units,
|
com.raytheon.uf.common.dataplugin.grid.units,
|
||||||
com.raytheon.uf.common.dataplugin.grid.util
|
com.raytheon.uf.common.dataplugin.grid.util
|
||||||
Require-Bundle: com.raytheon.uf.common.comm;bundle-version="1.12.1174",
|
Require-Bundle: javax.measure,
|
||||||
com.raytheon.uf.common.dataplugin.level;bundle-version="1.12.1174",
|
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.parameter;bundle-version="1.0.0",
|
||||||
com.raytheon.uf.common.util;bundle-version="1.12.1174",
|
com.raytheon.uf.common.comm;bundle-version="1.12.1174",
|
||||||
com.raytheon.uf.common.dataquery;bundle-version="1.0.0"
|
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;
|
package com.raytheon.uf.common.geospatial.interpolation.data;
|
||||||
|
|
||||||
import org.geotools.coverage.grid.GeneralGridGeometry;
|
import org.geotools.coverage.grid.GeneralGridGeometry;
|
||||||
import org.geotools.geometry.DirectPosition2D;
|
|
||||||
import org.geotools.referencing.CRS;
|
import com.raytheon.uf.common.geospatial.util.GridGeometryWrapChecker;
|
||||||
import org.geotools.referencing.crs.DefaultGeographicCRS;
|
|
||||||
import org.opengis.referencing.crs.CoordinateReferenceSystem;
|
|
||||||
import org.opengis.referencing.datum.PixelInCell;
|
|
||||||
import org.opengis.referencing.operation.MathTransform;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -63,7 +59,7 @@ public abstract class AbstractDataWrapper implements DataSource,
|
||||||
public AbstractDataWrapper(GeneralGridGeometry geometry) {
|
public AbstractDataWrapper(GeneralGridGeometry geometry) {
|
||||||
this.nx = geometry.getGridRange().getSpan(0);
|
this.nx = geometry.getGridRange().getSpan(0);
|
||||||
this.ny = geometry.getGridRange().getSpan(1);
|
this.ny = geometry.getGridRange().getSpan(1);
|
||||||
checkForWrapping(geometry);
|
this.wrapX = GridGeometryWrapChecker.checkForWrapping(geometry);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AbstractDataWrapper(int nx, int ny) {
|
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);
|
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