Issue #2672 Handle envelopes in data access requests of gridded types.

Change-Id: I4a58e6489e2173457d50e6f3c06e2e8fffdb678c

Former-commit-id: f634fe40e6fa7a8a3af575e79cb3d996761db983
This commit is contained in:
Ben Steffensmeier 2014-02-04 17:18:57 -06:00
parent 7b59bd41eb
commit dc5b5526a3
10 changed files with 649 additions and 222 deletions

View file

@ -29,7 +29,6 @@ import org.geotools.coverage.grid.GridEnvelope2D;
import org.geotools.coverage.grid.GridGeometry2D;
import org.geotools.coverage.grid.InvalidGridGeometryException;
import org.geotools.geometry.DirectPosition2D;
import org.geotools.geometry.Envelope2D;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
@ -46,8 +45,8 @@ import com.raytheon.uf.common.geospatial.ReferencedCoordinate;
import com.raytheon.uf.common.geospatial.interpolation.BilinearInterpolation;
import com.raytheon.uf.common.geospatial.interpolation.Interpolation;
import com.raytheon.uf.common.geospatial.interpolation.NearestNeighborInterpolation;
import com.raytheon.uf.common.geospatial.util.EnvelopeIntersection;
import com.raytheon.uf.common.geospatial.util.GridGeometryWrapChecker;
import com.raytheon.uf.common.geospatial.util.SubGridGeometryCalculator;
import com.raytheon.uf.common.gridcoverage.GridCoverage;
import com.raytheon.uf.common.gridcoverage.LatLonGridCoverage;
import com.raytheon.uf.common.status.IUFStatusHandler;
@ -70,8 +69,6 @@ import com.raytheon.viz.grid.rsc.GridResourceData;
import com.raytheon.viz.grid.util.ReprojectionUtil;
import com.raytheon.viz.grid.xml.FieldDisplayTypesFactory;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
/**
*
@ -82,7 +79,7 @@ import com.vividsolutions.jts.geom.Geometry;
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------- -------- ----------- --------------------------
* ------------- -------- ----------- -----------------------------------------
* Mar 09, 2011 bsteffen Initial creation
* Feb 25, 2013 1659 bsteffen Add PDOs to D2DGridResource in
* constructor to avoid duplicate data
@ -94,6 +91,8 @@ import com.vividsolutions.jts.geom.Geometry;
* Sep 24, 2013 15972 D. Friedman Make reprojection of grids configurable.
* Nov 19, 2013 2532 bsteffen Special handling of grids larger than the
* world.
* Feb 04, 2014 2672 bsteffen Extract subgridding logic to geospatial
* plugin.
*
* </pre>
*
@ -174,35 +173,33 @@ public class D2DGridResource extends GridResource<GridResourceData> implements
IDataRecord[] dataRecs = GridResourceData.getDataRecordsForTilt(
gridRecord, descriptor);
if (dataRecs == null) {
GridGeometry2D subGridGeometry = gridGeometry;
if (!gridLargerThanWorld) {
subGridGeometry = calculateSubgrid(gridGeometry);
}
if (subGridGeometry == null) {
return null;
} else if (subGridGeometry.equals(gridGeometry)) {
try {
SubGridGeometryCalculator subGrid = new SubGridGeometryCalculator(
descriptor.getGridGeometry().getEnvelope(),
gridGeometry);
if (subGrid.isEmpty()) {
return null;
} else if (subGrid.isFull()) {
dataRecs = DataCubeContainer.getDataRecord(gridRecord);
} else {
Request request = Request.buildSlab(
subGrid.getGridRangeLow(true),
subGrid.getGridRangeHigh(false));
dataRecs = DataCubeContainer.getDataRecord(gridRecord,
request, null);
/*
* gridGeometries used in renderables are expected to have
* min x,y be 0.
*/
gridGeometry = subGrid.getZeroedSubGridGeometry();
dataModified = true;
}
} catch (TransformException e) {
/* Not a big deal, just request all data. */
statusHandler.handle(Priority.DEBUG,
"Unable to request subgrid, full grid will be used.",
e);
dataRecs = DataCubeContainer.getDataRecord(gridRecord);
} else if (subGridGeometry != null) {
/* transform subgrid envelope into a slab request. */
GridEnvelope2D subGridRange = subGridGeometry.getGridRange2D();
int[] min = { subGridRange.getLow(0), subGridRange.getLow(1) };
int[] max = { subGridRange.getHigh(0) + 1,
subGridRange.getHigh(1) + 1 };
Request request = Request.buildSlab(min, max);
dataRecs = DataCubeContainer.getDataRecord(gridRecord, request,
null);
/*
* gridGeometries used in renderables are expected to have min
* x,y be 0.
*/
subGridRange.x = 0;
subGridRange.y = 0;
gridGeometry = new GridGeometry2D(subGridRange,
subGridGeometry.getEnvelope());
dataModified = true;
}
if (dataRecs == null) {
return null;
}
}
@ -257,53 +254,6 @@ public class D2DGridResource extends GridResource<GridResourceData> implements
return data;
}
protected GridGeometry2D calculateSubgrid(GridGeometry2D dataGeometry) {
try {
CoordinateReferenceSystem dataCRS = dataGeometry
.getCoordinateReferenceSystem();
org.opengis.geometry.Envelope descEnv = descriptor
.getGridGeometry().getEnvelope();
Envelope2D dataEnv = dataGeometry.getEnvelope2D();
int dataWidth = dataGeometry.getGridRange2D().width;
int dataHeight = dataGeometry.getGridRange2D().height;
/*
* Use grid spacing to determine a threshold for
* EnvelopeIntersection. This guarantees the result is within one
* grid cell.
*/
double dx = dataEnv.width / dataWidth;
double dy = dataEnv.height / dataHeight;
double threshold = Math.max(dx, dy);
Geometry geom = EnvelopeIntersection.createEnvelopeIntersection(
descEnv, dataEnv, threshold, dataWidth, dataHeight);
/* Convert from jts envelope to geotools envelope. */
Envelope env = geom.getEnvelopeInternal();
Envelope2D subEnv = new Envelope2D(dataCRS, env.getMinX(),
env.getMinY(), env.getWidth(), env.getHeight());
GridEnvelope2D subRange = dataGeometry.worldToGrid(subEnv);
/* Add a 1 pixel border so interpolation near the edges is nice */
subRange.grow(1, 1);
/* Make sure not to grow bigger than original grid. */
subRange = new GridEnvelope2D(subRange.intersection(dataGeometry
.getGridRange2D()));
if (subRange.isEmpty()) {
return null;
}
return new GridGeometry2D(subRange, dataGeometry.getGridToCRS(),
dataCRS);
} catch (FactoryException e) {
/* Not a big deal, just request all data. */
statusHandler.handle(Priority.DEBUG, "Unable to request subgrid.",
e);
} catch (TransformException e) {
/* Not a big deal, just request all data. */
/* Java 7 multiple exception catches are going to be amazing! */
statusHandler.handle(Priority.DEBUG, "Unable to request subgrid.",
e);
}
return dataGeometry;
}
public GeneralGridData reprojectData(GeneralGridData data) {
if (descriptor == null) {
return data;

View file

@ -0,0 +1,55 @@
/**
* 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.dataaccess.exception;
/**
* An exception for when an IDataFactory cannot handle a request because the
* requested envelope cannot be mapped onto available data. For example it is
* valid for a factory to throw this exception if data in a mercator projection
* is requested over the pole, which cannot be represented in mercator
* projections.
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------- -------- ----------- --------------------------
* Feb 04, 2014 2672 bsteffen Initial creation
*
* </pre>
*
* @author bsteffen
* @version 1.0
*/
public class EnvelopeProjectionException extends DataAccessException {
private static final long serialVersionUID = 1;
public EnvelopeProjectionException(String message, Throwable cause) {
super(message, cause);
}
public EnvelopeProjectionException(String message) {
super(message);
}
}

View file

@ -24,15 +24,23 @@ import java.util.List;
import java.util.Map;
import org.geotools.coverage.grid.GridGeometry2D;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.opengis.referencing.operation.TransformException;
import com.raytheon.uf.common.dataaccess.IDataRequest;
import com.raytheon.uf.common.dataaccess.exception.DataRetrievalException;
import com.raytheon.uf.common.dataaccess.exception.EnvelopeProjectionException;
import com.raytheon.uf.common.dataaccess.grid.IGridData;
import com.raytheon.uf.common.dataaccess.util.DataWrapperUtil;
import com.raytheon.uf.common.dataaccess.util.PDOUtil;
import com.raytheon.uf.common.dataplugin.PluginDataObject;
import com.raytheon.uf.common.dataquery.responses.DbQueryResponse;
import com.raytheon.uf.common.datastorage.Request;
import com.raytheon.uf.common.datastorage.records.IDataRecord;
import com.raytheon.uf.common.geospatial.interpolation.data.DataSource;
import com.raytheon.uf.common.geospatial.util.SubGridGeometryCalculator;
import com.vividsolutions.jts.geom.Envelope;
/**
* An abstract factory for getting grid data from plugins that use
@ -42,12 +50,15 @@ import com.raytheon.uf.common.datastorage.records.IDataRecord;
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Jan 17, 2013 bsteffen Initial creation
* Feb 14, 2013 1614 bsteffen Refactor data access framework to use
* single request.
* Jan 14, 2014 2667 mnash Remove getGeometryData methods
* Date Ticket# Engineer Description
* ------------- -------- ----------- -----------------------------------------
* Jan 17, 2013 bsteffen Initial creation
* Feb 14, 2013 1614 bsteffen Refactor data access framework to use
* single request.
* Jan 14, 2014 2667 mnash Remove getGeometryData methods
* Feb 04, 2014 2672 bsteffen Enable subgridding when envelopes are
* requested
*
* </pre>
*
* @author bsteffen
@ -82,28 +93,86 @@ public abstract class AbstractGridDataPluginFactory extends
PluginDataObject pdo = (PluginDataObject) resultMap.get(null);
IDataRecord dataRecord = getDataRecord(pdo);
/*
* Extract the grid geometry.
*/
GridGeometry2D gridGeometry = getGridGeometry(pdo);
IGridData defaultGridData = null;
defaultGridData = this.constructGridDataResponse(request, pdo,
gridGeometry, dataRecord);
DataSource dataSource = null;
gridData.add(defaultGridData);
Envelope envelope = request.getEnvelope();
if (envelope != null) {
ReferencedEnvelope requestEnv = new ReferencedEnvelope(
envelope, DefaultGeographicCRS.WGS84);
SubGridGeometryCalculator subGrid = calculateSubGrid(
requestEnv, gridGeometry);
if (subGrid == null || !subGrid.isEmpty()) {
dataSource = getDataSource(pdo, subGrid);
if (subGrid != null) {
gridGeometry = subGrid.getZeroedSubGridGeometry();
}
}
} else {
dataSource = getDataSource(pdo, null);
}
if (dataSource != null) {
gridData.add(this.constructGridDataResponse(request, pdo,
gridGeometry, dataSource));
}
}
return gridData.toArray(new IGridData[gridData.size()]);
}
protected IDataRecord getDataRecord(PluginDataObject pdo) {
/**
* Generate a SubGridGeometryCalculator appropriate for determining what
* area of data to request for this dataType. A return type of null can be
* used to indicate the entire gridGeometry should be used.
*
* @param envelope
* The requested envelope in WGS84
* @param gridGeometry
* The gridGeometry.
* @return a SubGridGeometryCalculator.
* @throws EnvelopeProjectionException
*/
protected SubGridGeometryCalculator calculateSubGrid(
ReferencedEnvelope envelope, GridGeometry2D gridGeometry)
throws EnvelopeProjectionException {
try {
return PDOUtil.getDataRecord(pdo, "Data", Request.ALL);
return new SubGridGeometryCalculator(envelope, gridGeometry);
} catch (TransformException e) {
throw new EnvelopeProjectionException(
"Error determining subgrid from envelope: " + envelope, e);
}
}
/**
* Request the raw data for a pdo.
*
* @param pdo
* the pdo with metadata popualted
* @param subGrid
* object describing area requested.
* @return a DataSource holding the raw data.
*/
protected DataSource getDataSource(PluginDataObject pdo,
SubGridGeometryCalculator subGrid) {
try {
IDataRecord dataRecord = null;
if (subGrid == null || subGrid.isFull()) {
dataRecord = PDOUtil.getDataRecord(pdo, "Data", Request.ALL);
} else if (!subGrid.isEmpty()) {
Request dataStoreReq = Request.buildSlab(
subGrid.getGridRangeLow(true),
subGrid.getGridRangeHigh(false));
dataRecord = PDOUtil.getDataRecord(pdo, "Data", dataStoreReq);
} else {
return null;
}
return DataWrapperUtil.constructArrayWrapper(dataRecord, false);
} catch (Exception e) {
e.printStackTrace();
throw new DataRetrievalException(
"Failed to retrieve the IDataRecord for PluginDataObject: "
+ pdo.toString(), e);
@ -129,6 +198,6 @@ public abstract class AbstractGridDataPluginFactory extends
*/
protected abstract IGridData constructGridDataResponse(
IDataRequest request, PluginDataObject pdo,
GridGeometry2D gridGeometry, IDataRecord dataRecord);
GridGeometry2D gridGeometry, DataSource dataSource);
}

View file

@ -31,29 +31,28 @@ import com.raytheon.uf.common.dataaccess.exception.DataRetrievalException;
import com.raytheon.uf.common.dataaccess.grid.IGridData;
import com.raytheon.uf.common.dataaccess.impl.AbstractGridDataPluginFactory;
import com.raytheon.uf.common.dataaccess.impl.DefaultGridData;
import com.raytheon.uf.common.dataaccess.util.DataWrapperUtil;
import com.raytheon.uf.common.dataplugin.PluginDataObject;
import com.raytheon.uf.common.dataplugin.gfe.db.objects.GFERecord;
import com.raytheon.uf.common.dataplugin.gfe.db.objects.GridLocation;
import com.raytheon.uf.common.dataplugin.gfe.db.objects.GridParmInfo;
import com.raytheon.uf.common.dataplugin.gfe.grid.Grid2DByte;
import com.raytheon.uf.common.dataplugin.gfe.grid.Grid2DFloat;
import com.raytheon.uf.common.dataplugin.gfe.grid.IGrid2D;
import com.raytheon.uf.common.dataplugin.gfe.slice.DiscreteGridSlice;
import com.raytheon.uf.common.dataplugin.gfe.slice.IGridSlice;
import com.raytheon.uf.common.dataplugin.gfe.slice.ScalarGridSlice;
import com.raytheon.uf.common.dataplugin.gfe.slice.WeatherGridSlice;
import com.raytheon.uf.common.dataplugin.gfe.weather.WeatherKey;
import com.raytheon.uf.common.dataplugin.level.Level;
import com.raytheon.uf.common.dataplugin.level.MasterLevel;
import com.raytheon.uf.common.dataquery.requests.DbQueryRequest;
import com.raytheon.uf.common.dataquery.requests.RequestConstraint;
import com.raytheon.uf.common.dataquery.requests.RequestConstraint.ConstraintType;
import com.raytheon.uf.common.dataquery.responses.DbQueryResponse;
import com.raytheon.uf.common.datastorage.records.ByteDataRecord;
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.interpolation.data.ByteArrayWrapper;
import com.raytheon.uf.common.geospatial.interpolation.data.DataSource;
import com.raytheon.uf.common.geospatial.interpolation.data.FloatArrayWrapper;
import com.raytheon.uf.common.geospatial.interpolation.data.OffsetDataSource;
import com.raytheon.uf.common.geospatial.util.SubGridGeometryCalculator;
import com.raytheon.uf.common.util.StringUtil;
/**
@ -64,15 +63,16 @@ import com.raytheon.uf.common.util.StringUtil;
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Feb 4, 2013 bsteffen Initial creation
* Feb 14, 2013 1614 bsteffen Refactor data access framework to use
* single request.
* May 02, 2013 1949 bsteffen Update GFE data access in Product
* Browser, Volume Browser, and Data Access
* Framework.
* 10/31/2013 2508 randerso Change to use DiscreteGridSlice.getKeys()
* Date Ticket# Engineer Description
* ------------- -------- ----------- --------------------------
* Feb 04, 2013 bsteffen Initial creation
* Feb 14, 2013 1614 bsteffen Refactor data access framework to use
* single request.
* May 02, 2013 1949 bsteffen Update GFE data access in Product
* Browser, Volume Browser, and Data Access
* Framework.
* Oct 31, 2013 2508 randerso Change to use DiscreteGridSlice.getKeys()
* Feb 04, 2014 2672 bsteffen Enable requesting subgrids.
*
* </pre>
*
@ -104,11 +104,10 @@ public class GFEGridFactory extends AbstractGridDataPluginFactory implements
@Override
protected IGridData constructGridDataResponse(IDataRequest request,
PluginDataObject pdo, GridGeometry2D gridGeometry,
IDataRecord dataRecord) {
DataSource dataSource) {
GFERecord gfeRecord = asGFERecord(pdo);
DefaultGridData defaultGridData = new DefaultGridData(
DataWrapperUtil.constructArrayWrapper(dataRecord, false),
DefaultGridData defaultGridData = new DefaultGridData(dataSource,
gridGeometry);
defaultGridData.setDataTime(pdo.getDataTime());
defaultGridData.setParameter(gfeRecord.getParmName());
@ -121,9 +120,10 @@ public class GFEGridFactory extends AbstractGridDataPluginFactory implements
attrs.put(MODEL_NAME, gfeRecord.getDbId().getModelName());
attrs.put(MODEL_TIME, gfeRecord.getDbId().getModelTime());
attrs.put(SITE_ID, gfeRecord.getDbId().getSiteId());
if (dataRecord.getDataAttributes().containsKey(KEYS)) {
attrs.put(KEYS, StringUtil.join((String[]) dataRecord
.getDataAttributes().get(KEYS), ','));
Object messageData = gfeRecord.getMessageData();
if (messageData instanceof Object[]) {
attrs.put(KEYS, StringUtil.join((Object[]) messageData, ','));
}
defaultGridData.setAttributes(attrs);
@ -203,58 +203,49 @@ public class GFEGridFactory extends AbstractGridDataPluginFactory implements
}
@Override
protected IDataRecord getDataRecord(PluginDataObject pdo) {
protected DataSource getDataSource(PluginDataObject pdo,
SubGridGeometryCalculator subGrid) {
GFERecord gfeRecord = asGFERecord(pdo);
IGridSlice slice = null;
try {
IGridSlice slice = GFEDataAccessUtil.getSlice(gfeRecord);
GridLocation loc = slice.getGridInfo().getGridLoc();
gfeRecord.setGridInfo(slice.getGridInfo());
IGrid2D data = null;
Map<String, Object> attrs = new HashMap<String, Object>();
if (slice instanceof ScalarGridSlice) {
// This also grabs vector data.
data = ((ScalarGridSlice) slice).getScalarGrid();
return new FloatDataRecord("Data", gfeRecord.getDataURI(),
((Grid2DFloat) data).getFloats(), 2, new long[] {
loc.getNx(), loc.getNy() });
} else if (slice instanceof DiscreteGridSlice) {
DiscreteGridSlice castedSlice = (DiscreteGridSlice) slice;
data = castedSlice.getDiscreteGrid();
Object[] dKeys = castedSlice.getKeys();
String[] keys = new String[dKeys.length];
for (int i = 0; i < dKeys.length; i++) {
keys[i] = dKeys[i].toString();
}
byte[] bytes = ((Grid2DByte) data).getBytes();
ByteDataRecord record = new ByteDataRecord("Data",
gfeRecord.getDataURI(), bytes, 2, new long[] {
loc.getNx(), loc.getNy() });
attrs.put(KEYS, keys);
record.setDataAttributes(attrs);
return record;
} else if (slice instanceof WeatherGridSlice) {
WeatherGridSlice castedSlice = (WeatherGridSlice) slice;
data = castedSlice.getWeatherGrid();
WeatherKey[] wKeys = castedSlice.getKeys();
String[] keys = new String[wKeys.length];
for (int i = 0; i < wKeys.length; i++) {
keys[i] = wKeys[i].toString();
}
byte[] bytes = ((Grid2DByte) data).getBytes();
ByteDataRecord record = new ByteDataRecord("Data",
gfeRecord.getDataURI(), bytes, 2, new long[] {
loc.getNx(), loc.getNy() });
attrs.put(KEYS, keys);
record.setDataAttributes(attrs);
return record;
} else {
throw new DataRetrievalException("Unknown slice of type "
+ slice.getClass().getSimpleName());
}
slice = GFEDataAccessUtil.getSlice(gfeRecord);
} catch (Exception e) {
throw new DataRetrievalException(e);
}
GridParmInfo info = slice.getGridInfo();
GridLocation loc = info.getGridLoc();
gfeRecord.setGridInfo(slice.getGridInfo());
DataSource dataSource = null;
Object[] keys = null;
if (slice instanceof ScalarGridSlice) {
// This also grabs vector data.
Grid2DFloat data = ((ScalarGridSlice) slice).getScalarGrid();
dataSource = new FloatArrayWrapper(data.getFloats(), loc.getNx(),
loc.getNy());
} else if (slice instanceof DiscreteGridSlice) {
DiscreteGridSlice discreteSlice = (DiscreteGridSlice) slice;
Grid2DByte data = discreteSlice.getDiscreteGrid();
keys = discreteSlice.getKeys();
dataSource = new ByteArrayWrapper(data.getBytes(), loc.getNx(),
loc.getNy());
} else if (slice instanceof WeatherGridSlice) {
WeatherGridSlice weatherSlice = (WeatherGridSlice) slice;
Grid2DByte data = weatherSlice.getWeatherGrid();
keys = weatherSlice.getKeys();
dataSource = new ByteArrayWrapper(data.getBytes(), loc.getNx(),
loc.getNy());
} else {
throw new DataRetrievalException("Unknown slice of type "
+ slice.getClass().getSimpleName());
}
if (subGrid != null && !subGrid.isFull()) {
int[] offsets = subGrid.getGridRangeLow(true);
dataSource = new OffsetDataSource(dataSource, offsets[0],
offsets[1]);
}
gfeRecord.setMessageData(keys);
return dataSource;
}
@Override

View file

@ -38,7 +38,6 @@ import com.raytheon.uf.common.dataaccess.exception.DataRetrievalException;
import com.raytheon.uf.common.dataaccess.grid.IGridData;
import com.raytheon.uf.common.dataaccess.impl.AbstractGridDataPluginFactory;
import com.raytheon.uf.common.dataaccess.impl.DefaultGridData;
import com.raytheon.uf.common.dataaccess.util.DataWrapperUtil;
import com.raytheon.uf.common.dataplugin.PluginDataObject;
import com.raytheon.uf.common.dataplugin.grid.GridConstants;
import com.raytheon.uf.common.dataplugin.grid.GridRecord;
@ -48,7 +47,7 @@ import com.raytheon.uf.common.dataplugin.level.Level;
import com.raytheon.uf.common.dataplugin.level.mapping.LevelMapper;
import com.raytheon.uf.common.dataquery.requests.RequestConstraint;
import com.raytheon.uf.common.dataquery.requests.RequestConstraint.ConstraintType;
import com.raytheon.uf.common.datastorage.records.IDataRecord;
import com.raytheon.uf.common.geospatial.interpolation.data.DataSource;
import com.raytheon.uf.common.parameter.mapping.ParameterMapper;
import com.raytheon.uf.common.util.mapping.Mapper;
@ -59,11 +58,13 @@ import com.raytheon.uf.common.util.mapping.Mapper;
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Jan 17, 2013 bsteffen Initial creation
* Feb 14, 2013 1614 bsteffen Refactor data access framework to use
* single request.
* Date Ticket# Engineer Description
* ------------- -------- ----------- --------------------------
* Jan 17, 2013 bsteffen Initial creation
* Feb 14, 2013 1614 bsteffen Refactor data access framework to use
* single request.
* Feb 04, 2014 2672 bsteffen Enable requesting subgrids.
*
*
* </pre>
*
@ -187,7 +188,7 @@ public class GridDataAccessFactory extends AbstractGridDataPluginFactory
@Override
protected IGridData constructGridDataResponse(IDataRequest request,
PluginDataObject pdo, GridGeometry2D gridGeometry,
IDataRecord dataRecord) {
DataSource dataSource) {
if (pdo instanceof GridRecord == false) {
throw new DataRetrievalException(this.getClass().getSimpleName()
+ " cannot handle " + pdo.getClass().getSimpleName());
@ -210,8 +211,8 @@ public class GridDataAccessFactory extends AbstractGridDataPluginFactory
parameter, namespace, requestParameters);
if (identifiers.containsKey(GridConstants.DATASET_ID)) {
List<String> requestedDatasets = Arrays.asList(identifiers.get(GridConstants.DATASET_ID)
.toString());
List<String> requestedDatasets = Arrays.asList(identifiers.get(
GridConstants.DATASET_ID).toString());
datasetId = reverseResolveMapping(
DatasetIdMapper.getInstance(), datasetId, namespace,
requestedDatasets);
@ -243,8 +244,8 @@ public class GridDataAccessFactory extends AbstractGridDataPluginFactory
}
}
DefaultGridData defaultGridData = new DefaultGridData(
DataWrapperUtil.constructArrayWrapper(dataRecord), gridGeometry);
DefaultGridData defaultGridData = new DefaultGridData(dataSource,
gridGeometry);
defaultGridData.setDataTime(pdo.getDataTime());
defaultGridData.setParameter(parameter);
defaultGridData.setLevel(level);

View file

@ -28,21 +28,21 @@ import java.util.Map;
import java.util.Set;
import org.geotools.coverage.grid.GridGeometry2D;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.opengis.referencing.FactoryException;
import com.raytheon.uf.common.dataaccess.IDataFactory;
import com.raytheon.uf.common.dataaccess.IDataRequest;
import com.raytheon.uf.common.dataaccess.exception.DataRetrievalException;
import com.raytheon.uf.common.dataaccess.exception.EnvelopeProjectionException;
import com.raytheon.uf.common.dataaccess.grid.IGridData;
import com.raytheon.uf.common.dataaccess.impl.AbstractGridDataPluginFactory;
import com.raytheon.uf.common.dataaccess.impl.DefaultGridData;
import com.raytheon.uf.common.dataaccess.util.DataWrapperUtil;
import com.raytheon.uf.common.dataaccess.util.PDOUtil;
import com.raytheon.uf.common.dataplugin.PluginDataObject;
import com.raytheon.uf.common.dataplugin.level.Level;
import com.raytheon.uf.common.dataplugin.level.MasterLevel;
import com.raytheon.uf.common.dataplugin.radar.RadarRecord;
import com.raytheon.uf.common.dataplugin.radar.RadarStoredData;
import com.raytheon.uf.common.dataplugin.radar.projection.RadarProjectionFactory;
import com.raytheon.uf.common.dataplugin.radar.util.RadarDataRetriever;
import com.raytheon.uf.common.dataplugin.radar.util.RadarInfo;
@ -50,27 +50,35 @@ import com.raytheon.uf.common.dataplugin.radar.util.RadarInfoDict;
import com.raytheon.uf.common.dataplugin.radar.util.RadarUtil;
import com.raytheon.uf.common.dataquery.requests.RequestConstraint;
import com.raytheon.uf.common.dataquery.requests.RequestConstraint.ConstraintType;
import com.raytheon.uf.common.datastorage.records.ByteDataRecord;
import com.raytheon.uf.common.datastorage.records.IDataRecord;
import com.raytheon.uf.common.geospatial.interpolation.data.ByteArrayWrapper;
import com.raytheon.uf.common.geospatial.interpolation.data.DataSource;
import com.raytheon.uf.common.geospatial.interpolation.data.DataWrapper1D;
import com.raytheon.uf.common.geospatial.interpolation.data.ShortArrayWrapper;
import com.raytheon.uf.common.geospatial.util.SubGridGeometryCalculator;
import com.raytheon.uf.common.localization.PathManagerFactory;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
/**
*
* A data factory for getting radar data from the metadata database. There are
* currently not any required identifiers.
*
* Radar does not return subgrids for request envelopes like other gridded
* types. Instead data for only icaos within the request envelope are returned
* and all data for the product is used. This is done because subgridding radial
* products is complex and this is not often what a caller actually wants.
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Jan 23, 2013 bsteffen Initial creation
* Feb 14, 2013 1614 bsteffen Refactor data access framework to use
* single request.
* Date Ticket# Engineer Description
* ------------- -------- ----------- --------------------------
* Jan 23, 2013 bsteffen Initial creation
* Feb 14, 2013 1614 bsteffen Refactor data access framework to use
* single request.
* Feb 04, 2014 2672 bsteffen Enable requesting icaos within envelope.
*
*
* </pre>
*
@ -86,6 +94,10 @@ public class RadarGridFactory extends AbstractGridDataPluginFactory implements
private static final String ICAO = "icao";
private static final String LONGITUDE = "longitude";
private static final String LATITUDE = "latitude";
private static final String FORMAT = "format";
private static final String RADIAL_FORMAT = "Radial";
@ -106,19 +118,9 @@ public class RadarGridFactory extends AbstractGridDataPluginFactory implements
@Override
protected IGridData constructGridDataResponse(IDataRequest request,
PluginDataObject pdo, GridGeometry2D gridGeometry,
IDataRecord dataRecord) {
DataSource dataSource) {
RadarRecord radarRecord = asRadarRecord(pdo);
DataWrapper1D wrapper = DataWrapperUtil.constructArrayWrapper(
dataRecord, false);
wrapper.setFillValue(0);
DataSource source = wrapper;
if (radarRecord.getFormat().equals(RADIAL_FORMAT)) {
// The raw data is in bin,radial format but the grid geometries we
// use are radial,bin so need to do some swapping.
source = new AxisSwapDataSource(source, radarRecord.getNumBins());
}
DefaultGridData defaultGridData = new DefaultGridData(source,
DefaultGridData defaultGridData = new DefaultGridData(dataSource,
gridGeometry);
defaultGridData.setDataTime(pdo.getDataTime());
// reverse map parameter to match request.
@ -161,14 +163,20 @@ public class RadarGridFactory extends AbstractGridDataPluginFactory implements
RadarRecord radarRecord = asRadarRecord(pdo);
if (radarRecord.getFormat().equals(RADIAL_FORMAT)) {
try {
float[] angleData = radarRecord.getAngleData();
if (angleData == null) {
populateRecord(radarRecord);
angleData = radarRecord.getAngleData();
}
// NOTE: do not set swapXY=true even though it matches the raw
// data better because there is lots of code, especially on the
// Viz side that does not correctly handle the resulting
// GridGeometry.
return RadarProjectionFactory.constructGridGeometry(
new Coordinate(radarRecord.getLongitude(), radarRecord
.getLatitude()), radarRecord.getAngleData(),
radarRecord.getGateResolution(), radarRecord
.getLatitude()), angleData, radarRecord
.getGateResolution(), radarRecord
.getTrueElevationAngle(), radarRecord
.getNumBins(), false);
} catch (FactoryException e) {
@ -188,19 +196,82 @@ public class RadarGridFactory extends AbstractGridDataPluginFactory implements
}
@Override
protected IDataRecord getDataRecord(PluginDataObject pdo) {
protected SubGridGeometryCalculator calculateSubGrid(
ReferencedEnvelope envelope, GridGeometry2D gridGeometry)
throws EnvelopeProjectionException {
/*
* The SubGridGeometryCalculator cannot accurately calculate subgrids
* into RadialBin projections. For this factory the request envelope is
* only used to limit the sites, not to subgrid. Returning null causes
* the super class to request a full grid.
*/
return null;
}
@Override
protected DataSource getDataSource(PluginDataObject pdo,
SubGridGeometryCalculator subGrid) {
RadarRecord radarRecord = asRadarRecord(pdo);
DataSource dataSource = getDataSource(radarRecord);
if (dataSource == null) {
/*
* Radial data prepopulates the record to get the gridGeometry but
* raster data waits until now.
*/
populateRecord(radarRecord);
dataSource = getDataSource(radarRecord);
if (dataSource == null) {
throw new DataRetrievalException("No grid data found for "
+ radarRecord);
}
}
if (radarRecord.getFormat().equals(RADIAL_FORMAT)) {
/*
* The raw data is in bin,radial format but the grid geometries we
* use are radial,bin so need to do some swapping.
*/
dataSource = new AxisSwapDataSource(dataSource,
radarRecord.getNumBins());
}
return dataSource;
}
/**
* Populate a DataSource from the raw data(byte or short) in the provided
* record.
*
* @param radarRecord
* @return a DataSource or null if the record is not populated or has no
* grid data.
*/
private DataSource getDataSource(RadarRecord radarRecord) {
int nx = radarRecord.getNumBins();
int ny = radarRecord.getNumRadials();
byte[] bytes = radarRecord.getRawData();
if (bytes != null) {
ByteArrayWrapper wrapper = new ByteArrayWrapper(bytes, nx, ny);
wrapper.setFillValue(0);
return wrapper;
}
short[] shorts = radarRecord.getRawShortData();
if (shorts != null) {
ShortArrayWrapper wrapper = new ShortArrayWrapper(shorts, nx, ny);
wrapper.setFillValue(0);
return wrapper;
}
return null;
}
protected void populateRecord(RadarRecord radarRecord)
throws DataRetrievalException {
try {
RadarDataRetriever.populateRadarRecord(PDOUtil.getDataStore(pdo),
radarRecord);
RadarDataRetriever.populateRadarRecord(
PDOUtil.getDataStore(radarRecord), radarRecord);
} catch (Exception e) {
throw new DataRetrievalException(e);
}
IDataRecord rec = new ByteDataRecord(RadarStoredData.RAW_DATA_ID,
radarRecord.getDataURI(), radarRecord.getRawData(), 2,
new long[] { radarRecord.getNumBins(),
radarRecord.getNumRadials() });
return rec;
}
@Override
@ -236,6 +307,17 @@ public class RadarGridFactory extends AbstractGridDataPluginFactory implements
constraints.put(ICAO, icaoConstraint);
}
if (request.getEnvelope() != null) {
Envelope envelope = request.getEnvelope();
String minLon = Double.toString(envelope.getMinX());
String maxLon = Double.toString(envelope.getMaxX());
constraints.put(LONGITUDE, new RequestConstraint(minLon, maxLon));
String minLat = Double.toString(envelope.getMinY());
String maxLat = Double.toString(envelope.getMaxY());
constraints.put(LATITUDE, new RequestConstraint(minLat, maxLat));
}
Map<String, Object> identifiers = request.getIdentifiers();
if (identifiers != null && identifiers.containsKey(ICAO)) {
constraints.put(ICAO, new RequestConstraint(identifiers.get(ICAO)
@ -315,7 +397,7 @@ public class RadarGridFactory extends AbstractGridDataPluginFactory implements
* ------------ ---------- ----------- --------------------------
* Jan 25, 2013 bsteffen Initial creation
* Feb 14, 2013 1614 bsteffen refactor data access framework to use
* single request.
* single request.
*
* </pre>
*

View file

@ -47,9 +47,11 @@ import org.opengis.referencing.operation.MathTransform;
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Jun 6, 2012 bsteffen Initial creation
* Date Ticket# Engineer Description
* ------------- -------- ----------- -----------------------------------------
* Jun 06, 2012 bsteffen Initial creation
* Feb 08, 2012 2672 bsteffen Fix projection of points between the last
* and first radial.
*
* </pre>
*
@ -99,6 +101,11 @@ public class RadialBinMapProjection extends AzimuthRangeMapProjection {
int nextRadial = (int) Math.ceil(radial) % normalAngleData.length;
float prevAngle = normalAngleData[prevRadial];
float nextAngle = normalAngleData[nextRadial];
if (nextAngle + 180 < prevAngle) {
nextAngle += 360;
} else if (nextAngle - 180 > prevAngle) {
nextAngle -= 360;
}
double az = prevAngle + (radial - prevRadial) * (nextAngle - prevAngle);
return super.inverseTransformNormalized(az, ran, dest);
}

View file

@ -35,13 +35,12 @@ import com.raytheon.uf.common.dataaccess.IDataRequest;
import com.raytheon.uf.common.dataaccess.exception.DataRetrievalException;
import com.raytheon.uf.common.dataaccess.impl.AbstractGridDataPluginFactory;
import com.raytheon.uf.common.dataaccess.impl.DefaultGridData;
import com.raytheon.uf.common.dataaccess.util.DataWrapperUtil;
import com.raytheon.uf.common.dataplugin.PluginDataObject;
import com.raytheon.uf.common.dataplugin.satellite.SatelliteRecord;
import com.raytheon.uf.common.dataplugin.satellite.units.SatelliteUnits;
import com.raytheon.uf.common.dataquery.requests.RequestConstraint;
import com.raytheon.uf.common.dataquery.requests.RequestConstraint.ConstraintType;
import com.raytheon.uf.common.datastorage.records.IDataRecord;
import com.raytheon.uf.common.geospatial.interpolation.data.DataSource;
/**
* A data factory for getting satellite data from the metadata database. There
@ -51,12 +50,13 @@ import com.raytheon.uf.common.datastorage.records.IDataRecord;
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Jan 02, 2012 bkowal Initial creation
* Jan 22, 2012 bsteffen Extract common functionality to AbstractGridDataPluginFactory
* Feb 14, 2013 1614 bsteffen Refactor data access framework to use
* single request.
* Date Ticket# Engineer Description
* ------------- -------- ----------- --------------------------
* Jan 02, 2012 bkowal Initial creation
* Jan 22, 2012 bsteffen Extract common functionality to AbstractGridDataPluginFactory
* Feb 14, 2013 1614 bsteffen Refactor data access framework to use
* single request.
* Feb 04, 2014 2672 bsteffen Enable requesting subgrids.
*
* </pre>
*
@ -84,15 +84,14 @@ public class SatelliteGridFactory extends AbstractGridDataPluginFactory
protected DefaultGridData constructGridDataResponse(IDataRequest request,
PluginDataObject pdo, GridGeometry2D gridGeometry,
IDataRecord dataRecord) {
DataSource dataSource) {
if(pdo instanceof SatelliteRecord == false){
throw new DataRetrievalException(this.getClass().getSimpleName()
+ " cannot handle " + pdo.getClass().getSimpleName());
}
SatelliteRecord satelliteRecord = (SatelliteRecord) pdo;
DefaultGridData defaultGridData = new DefaultGridData(
DataWrapperUtil.constructArrayWrapper(dataRecord, false),
DefaultGridData defaultGridData = new DefaultGridData(dataSource,
gridGeometry);
defaultGridData.setDataTime(pdo.getDataTime());
defaultGridData.setParameter(satelliteRecord.getPhysicalElement());

View file

@ -0,0 +1,60 @@
/**
* 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.interpolation.data;
/**
* DataSource which wraps another DataSource but offsets the coordinates by
* constant values. This can be useful for generating a DataSource that
* represents a subgrid of a full grid where the subgrid should be zero indexed.
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------- -------- ----------- --------------------------
* Feb 04, 2014 2672 bsteffen Initial creation
*
* </pre>
*
* @author bsteffen
* @version 1.0
*/
public class OffsetDataSource implements DataSource {
protected final DataSource wrappedSource;
protected final int xOffset;
protected final int yOffset;
public OffsetDataSource(DataSource wrappedSource, int xOffset, int yOffset) {
this.wrappedSource = wrappedSource;
this.xOffset = xOffset;
this.yOffset = yOffset;
}
@Override
public double getDataValue(int x, int y) {
return wrappedSource.getDataValue(xOffset + x, yOffset + y);
}
}

View file

@ -0,0 +1,213 @@
/**
* 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.GridEnvelope2D;
import org.geotools.coverage.grid.GridGeometry2D;
import org.geotools.geometry.Envelope2D;
import org.opengis.coverage.grid.GridEnvelope;
import org.opengis.coverage.grid.GridGeometry;
import org.opengis.geometry.Envelope;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;
import com.vividsolutions.jts.geom.Geometry;
/**
* Calculates a portion of a grid geometry that overlaps an envelope in a
* different projection. Provides convienence methods for accessing versions of
* the GridGeometry and range that are easier to use.
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------- -------- ----------- --------------------------
* Feb 04, 2014 2672 bsteffen Initial creation
*
* </pre>
*
* @author bsteffen
* @version 1.0
*/
public class SubGridGeometryCalculator {
private final Envelope envelope;
private final GridGeometry gridGeometry;
private final GridGeometry subGridGeometry;
/**
* Create a new SubGridGeometryCalculator.
*
* @param envelope
* The area which should be included in the subgrid, all grid
* points within this area will be included. Grid points outside
* the envelope will be included only if they are needed to keep
* the grid evenly spaced(for example when the difference in CRS
* has significant warping.)
* @param gridGeometry
* The full grid geometry for which a subgrid is needed. The
* subgrid will be defined in the same grid space as this
* geometry.
* @throws TransformException
* If something goes wrong mapping the envelope crs onto the
* grid geometry crs.
*/
public SubGridGeometryCalculator(Envelope envelope, GridGeometry gridGeometry)
throws TransformException {
this.envelope = envelope;
this.gridGeometry = gridGeometry;
this.subGridGeometry = calculate(envelope, gridGeometry);
}
protected static GridGeometry calculate(Envelope envelope,
GridGeometry gridGeometry) throws TransformException {
GridGeometry2D gg2D = GridGeometry2D.wrap(gridGeometry);
CoordinateReferenceSystem gridCRS = gg2D.getCoordinateReferenceSystem();
GridEnvelope2D gridRange = gg2D.getGridRange2D();
Envelope2D gridEnv = gg2D.getEnvelope2D();
int gridWidth = gridRange.width;
int gridHeight = gridRange.height;
/*
* Use grid spacing to determine a threshold for EnvelopeIntersection.
* This guarantees the result is within one grid cell.
*/
double dx = gridEnv.width / gridWidth;
double dy = gridEnv.height / gridHeight;
double threshold = Math.max(dx, dy);
Geometry geom = null;
try {
geom = EnvelopeIntersection.createEnvelopeIntersection(envelope,
gridEnv, threshold, gridWidth, gridHeight);
} catch (FactoryException e) {
throw new TransformException("Error initializing transforms.", e);
}
/* Convert from jts envelope to geotools envelope. */
com.vividsolutions.jts.geom.Envelope env = geom.getEnvelopeInternal();
Envelope2D subEnv = new Envelope2D(gridCRS, env.getMinX(),
env.getMinY(), env.getWidth(), env.getHeight());
GridEnvelope2D subRange = gg2D.worldToGrid(subEnv);
/* Add a 1 pixel border so interpolation near the edges is nice */
subRange.grow(1, 1);
/* Make sure not to grow bigger than original grid. */
subRange = new GridEnvelope2D(subRange.intersection(gridRange));
if (subRange.equals(gridRange)) {
return gridGeometry;
}
return new GridGeometry2D(subRange, gridGeometry.getGridToCRS(),
gridCRS);
}
/**
* @return The original envelope used to create this calculator.
*/
public Envelope getEnvelope() {
return envelope;
}
/**
* @return The original grid geometry used to create this calculator.
*/
public GridGeometry getGridGeometry() {
return gridGeometry;
}
/**
* @return The grid geometry describing the overlapping portion. The range
* of the geometry will have values that line up with the source
* geometry.
*/
public GridGeometry getSubGridGeometry() {
return subGridGeometry;
}
/**
* Converts/Casts the sub grid geometry to a {@link GridGeometry2D}
*
* @see #getSubGridGeometry()
*/
public GridGeometry2D getSubGridGeometry2D() {
return GridGeometry2D.wrap(subGridGeometry);
}
/**
* Creates a new grid geometry describing the subgrid area with the minimum
* values of the range set to 0. This type of geometry does not have
* information about the range but is often more compatible with certain
* code that assumes 0 minimum ranges.
*
* @return
*/
public GridGeometry2D getZeroedSubGridGeometry() {
GridGeometry2D subGridGeometry = getSubGridGeometry2D();
GridEnvelope2D subGridRange = subGridGeometry.getGridRange2D();
subGridRange.x = 0;
subGridRange.y = 0;
return new GridGeometry2D(subGridRange, subGridGeometry.getEnvelope());
}
public int[] getGridRangeLow(boolean inclusive) {
int[] low = subGridGeometry.getGridRange().getLow()
.getCoordinateValues();
if (!inclusive) {
for (int i = 0; i < low.length; i += 1) {
low[i] -= 1;
}
}
return low;
}
public int[] getGridRangeHigh(boolean inclusive) {
int[] high = subGridGeometry.getGridRange().getHigh()
.getCoordinateValues();
if (!inclusive) {
for (int i = 0; i < high.length; i += 1) {
high[i] += 1;
}
}
return high;
}
/**
* @return true if the envelope does not intersect the grid geometry.
*/
public boolean isEmpty() {
GridEnvelope range = subGridGeometry.getGridRange();
for (int i = 0; i < range.getDimension(); i += 1) {
if (range.getSpan(i) <= 0) {
return true;
}
}
return false;
}
/**
* @return true if the envelope intersects the entire grid geometry.
*/
public boolean isFull() {
return subGridGeometry == gridGeometry;
}
}