diff --git a/cave/com.raytheon.viz.grid/src/com/raytheon/viz/grid/rsc/general/D2DGridResource.java b/cave/com.raytheon.viz.grid/src/com/raytheon/viz/grid/rsc/general/D2DGridResource.java index 6121f0ecbb..c59e6fccf8 100644 --- a/cave/com.raytheon.viz.grid/src/com/raytheon/viz/grid/rsc/general/D2DGridResource.java +++ b/cave/com.raytheon.viz.grid/src/com/raytheon/viz/grid/rsc/general/D2DGridResource.java @@ -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. * * * @@ -174,35 +173,33 @@ public class D2DGridResource extends GridResource 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 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; diff --git a/edexOsgi/com.raytheon.uf.common.dataaccess/src/com/raytheon/uf/common/dataaccess/exception/EnvelopeProjectionException.java b/edexOsgi/com.raytheon.uf.common.dataaccess/src/com/raytheon/uf/common/dataaccess/exception/EnvelopeProjectionException.java new file mode 100644 index 0000000000..75aac28809 --- /dev/null +++ b/edexOsgi/com.raytheon.uf.common.dataaccess/src/com/raytheon/uf/common/dataaccess/exception/EnvelopeProjectionException.java @@ -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. + * + *
+ * 
+ * SOFTWARE HISTORY
+ * 
+ * Date          Ticket#  Engineer    Description
+ * ------------- -------- ----------- --------------------------
+ * Feb 04, 2014  2672     bsteffen    Initial creation
+ * 
+ * 
+ * + * @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); + } + +} diff --git a/edexOsgi/com.raytheon.uf.common.dataaccess/src/com/raytheon/uf/common/dataaccess/impl/AbstractGridDataPluginFactory.java b/edexOsgi/com.raytheon.uf.common.dataaccess/src/com/raytheon/uf/common/dataaccess/impl/AbstractGridDataPluginFactory.java index 2ffbeecf44..c0825aa75b 100644 --- a/edexOsgi/com.raytheon.uf.common.dataaccess/src/com/raytheon/uf/common/dataaccess/impl/AbstractGridDataPluginFactory.java +++ b/edexOsgi/com.raytheon.uf.common.dataaccess/src/com/raytheon/uf/common/dataaccess/impl/AbstractGridDataPluginFactory.java @@ -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 + * * * * @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); } diff --git a/edexOsgi/com.raytheon.uf.common.dataplugin.gfe/src/com/raytheon/uf/common/dataplugin/gfe/dataaccess/GFEGridFactory.java b/edexOsgi/com.raytheon.uf.common.dataplugin.gfe/src/com/raytheon/uf/common/dataplugin/gfe/dataaccess/GFEGridFactory.java index 9a0eb09dd5..d485e08dc3 100644 --- a/edexOsgi/com.raytheon.uf.common.dataplugin.gfe/src/com/raytheon/uf/common/dataplugin/gfe/dataaccess/GFEGridFactory.java +++ b/edexOsgi/com.raytheon.uf.common.dataplugin.gfe/src/com/raytheon/uf/common/dataplugin/gfe/dataaccess/GFEGridFactory.java @@ -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. * * * @@ -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 attrs = new HashMap(); - 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 diff --git a/edexOsgi/com.raytheon.uf.common.dataplugin.grid/src/com/raytheon/uf/common/dataplugin/grid/dataaccess/GridDataAccessFactory.java b/edexOsgi/com.raytheon.uf.common.dataplugin.grid/src/com/raytheon/uf/common/dataplugin/grid/dataaccess/GridDataAccessFactory.java index 4ac0ecd2b9..5d3496627e 100644 --- a/edexOsgi/com.raytheon.uf.common.dataplugin.grid/src/com/raytheon/uf/common/dataplugin/grid/dataaccess/GridDataAccessFactory.java +++ b/edexOsgi/com.raytheon.uf.common.dataplugin.grid/src/com/raytheon/uf/common/dataplugin/grid/dataaccess/GridDataAccessFactory.java @@ -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. + * * * * @@ -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 requestedDatasets = Arrays.asList(identifiers.get(GridConstants.DATASET_ID) - .toString()); + List 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); diff --git a/edexOsgi/com.raytheon.uf.common.dataplugin.radar/src/com/raytheon/uf/common/dataplugin/radar/dataaccess/RadarGridFactory.java b/edexOsgi/com.raytheon.uf.common.dataplugin.radar/src/com/raytheon/uf/common/dataplugin/radar/dataaccess/RadarGridFactory.java index f359722979..d52d49f4a3 100644 --- a/edexOsgi/com.raytheon.uf.common.dataplugin.radar/src/com/raytheon/uf/common/dataplugin/radar/dataaccess/RadarGridFactory.java +++ b/edexOsgi/com.raytheon.uf.common.dataplugin.radar/src/com/raytheon/uf/common/dataplugin/radar/dataaccess/RadarGridFactory.java @@ -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. + * *
  * 
  * 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.
+ * 
  * 
  * 
* @@ -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 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. * * * diff --git a/edexOsgi/com.raytheon.uf.common.dataplugin.radar/src/com/raytheon/uf/common/dataplugin/radar/projection/RadialBinMapProjection.java b/edexOsgi/com.raytheon.uf.common.dataplugin.radar/src/com/raytheon/uf/common/dataplugin/radar/projection/RadialBinMapProjection.java index b86d245011..dc26115bab 100644 --- a/edexOsgi/com.raytheon.uf.common.dataplugin.radar/src/com/raytheon/uf/common/dataplugin/radar/projection/RadialBinMapProjection.java +++ b/edexOsgi/com.raytheon.uf.common.dataplugin.radar/src/com/raytheon/uf/common/dataplugin/radar/projection/RadialBinMapProjection.java @@ -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. * * * @@ -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); } diff --git a/edexOsgi/com.raytheon.uf.common.dataplugin.satellite/src/com/raytheon/uf/common/dataplugin/satellite/dataaccess/SatelliteGridFactory.java b/edexOsgi/com.raytheon.uf.common.dataplugin.satellite/src/com/raytheon/uf/common/dataplugin/satellite/dataaccess/SatelliteGridFactory.java index a889423407..274342c8e8 100644 --- a/edexOsgi/com.raytheon.uf.common.dataplugin.satellite/src/com/raytheon/uf/common/dataplugin/satellite/dataaccess/SatelliteGridFactory.java +++ b/edexOsgi/com.raytheon.uf.common.dataplugin.satellite/src/com/raytheon/uf/common/dataplugin/satellite/dataaccess/SatelliteGridFactory.java @@ -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. * * * @@ -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()); diff --git a/edexOsgi/com.raytheon.uf.common.geospatial/src/com/raytheon/uf/common/geospatial/interpolation/data/OffsetDataSource.java b/edexOsgi/com.raytheon.uf.common.geospatial/src/com/raytheon/uf/common/geospatial/interpolation/data/OffsetDataSource.java new file mode 100644 index 0000000000..8371113fd4 --- /dev/null +++ b/edexOsgi/com.raytheon.uf.common.geospatial/src/com/raytheon/uf/common/geospatial/interpolation/data/OffsetDataSource.java @@ -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. + * + *
+ * 
+ * SOFTWARE HISTORY
+ * 
+ * Date          Ticket#  Engineer    Description
+ * ------------- -------- ----------- --------------------------
+ * Feb 04, 2014  2672     bsteffen    Initial creation
+ * 
+ * 
+ * + * @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); + } + +} diff --git a/edexOsgi/com.raytheon.uf.common.geospatial/src/com/raytheon/uf/common/geospatial/util/SubGridGeometryCalculator.java b/edexOsgi/com.raytheon.uf.common.geospatial/src/com/raytheon/uf/common/geospatial/util/SubGridGeometryCalculator.java new file mode 100644 index 0000000000..f1e77e4f7a --- /dev/null +++ b/edexOsgi/com.raytheon.uf.common.geospatial/src/com/raytheon/uf/common/geospatial/util/SubGridGeometryCalculator.java @@ -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. + * + *
+ * 
+ * SOFTWARE HISTORY
+ * 
+ * Date          Ticket#  Engineer    Description
+ * ------------- -------- ----------- --------------------------
+ * Feb 04, 2014  2672     bsteffen    Initial creation
+ * 
+ * 
+ * + * @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; + } + +}